Code Monkey home page Code Monkey logo

cuberitepluginchecker's Introduction

CuberitePluginChecker

This is a set of scripts that can automatically check a Cuberite plugin for some basic errors:

  • syntax errors (the plugin is loaded into a Lua engine)
  • API errors
  • type errors
  • storing a value from a callback for later reuse

The Checker has a simulator that mimics the Cuberite API (by sourcing the API description from Cuberite itself), so it has a pretty good picture about what API functions are available, what types of parameters they take etc. From this, a workable testing platform can be built.

In order to be able to check the plugin more thoroughly, the Checker needs a Scenario file that governs each individual check. The file specifies which actions and in what order the simulator performs.

The checking methods

The Checker can detect if your plugin stores a value received in a callback for later reuse (which is an operation not permitted by Cuberite). There are two ways to detect this - GarbageCollecting and Clearing.

GarbageCollecting means that after each callback, the Checker will call Lua's garbage collector and see whether all of the values that have been passed to the callback have been collected. This detects all instances when a value is stored for later. It is expected that this method may become too slow for large project / many scenarios, but until you actually experience an unacceptable slowdown, you should prefer to use this method.

Clearing means that after each callback the Checker will remove all API functions from the object given to the callback. So if a plugin tries to access such an object later on, it will trigger an error. This method is expected to be faster than GarbageCollecting, but it will only detect when the object is actually used later on. If not all codepaths are tested, it is possible for this method to miss the store operation. Use this method only when GarbageCollecting is too slow for you.

Setting up

The Checker requires a bit of a setup in order to run. It needs the API descriptions from Cuberite, which come from two separate sources - the AutoAPI (symbols exported automatically through a script during Cuberite build process); and the ManualAPI (symbols exported manually and documented in the APIDump built-in Cuberite plugin). Because the API evolves all the time, it is recommended to grab the latest version, rather than storing a fixed version somewhere.

AutoAPI

During Cuberite build, the build process generates the API descriptions for all automatically-exported API symbols. This is output into the src/Bindings/docs folder, and this entire folder is needed by the Checker. You can also download it from the build server which is updated several times a day.

ManualAPI

If you install the ManualApiDump plugin (https://github.com/madmaxoft/ManualApiDump) into your Cuberite, executing the manualapi console command will generate a ManualAPI.lua file next to the server executable. This file contains the descriptions for all manually-exported API symbols (Note that the APIDump plugin needs to be present for this to work). Again, you can also download it from the build server which is updated several times a day.

Running

To sum up, before running you need these:

  • your plugin files
  • AutoAPI files
  • ManualAPI file
  • CuberitePluginChecker
  • One or more Scenario files specific for your plugin
  • Lua interpreter, with luafilesystem, lsqlite3, luasocket and luasec modules installed (LuaRocks preferred)

To run the Checker, execute it in your Lua interpreter, while in the Checker's folder:

lua CuberitePluginChecker.lua -a <path/to/AutoAPI> -e <path/to/ManualAPI> -p <path/to/your/plugin> -s <ScenarioFile> -i <path/to/mock/api/implementations> [options]

The path/to/AutoAPI should point to the folder containing the AutoAPI files (such as _all.lua). The path/to/ManualAPI should point directly to the ManualAPI.lua file. The path/to/your/plugin should point to the folder where your plugin files are. The <path/to/mock/api/implementations> should point to the file which contains the mock code for certain classes which are required to test plugins. This is usually the APIImpl/All.lua file. Options further specify the behaviour of the Checker:

  • -g uses a GarbageCollecting check for callbacks. This is the preferred check method
  • -c uses a Clearing check for callbacks.
  • -l <loglevel> specifies the amount of diagnostic output that the plugin generates:
    • 1 = trace, everything is logged in great detail
    • 3 = debug (default)
    • 4 = info
    • 5 = warning
    • 9 = fatal error

When the Checker detects a problem, it displays the stack-trace where the problem occured and exits with a non-zero exit code. When no problem is detected, it exits with a zero exit code.

Scenario file

The scenario file is used to tell the simulator what actions it should perform and in what order. It can specify the worlds available, players connecting and disconnecting, executing commands and more. The Checker supports only one scenario file per session, but you can test multiple scenarios by executing the Checker multiple times, each time with a different scenario file.

If you don't provide a scenario file, the Checker only loads the plugin and executes its Initialize function.

See the ScenarioFiles.md file for detailed information about scenario files.

cuberitepluginchecker's People

Contributors

madmaxoft avatar mathiascode avatar nilspace avatar seadragon91 avatar tigerw avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cuberitepluginchecker's Issues

Scenario: Add player actions

More player actions should be available in the Scenario file:

  • movement
  • placing block
  • breaking block
  • using a block (entity)
  • damaging an entity
  • chat message

Load API generated by Cuberite's scripts

Cuberite can currently generate a list of API functions that are automatically exported by ToLua++, via its src/Bindings/GenerateBindings.lua script. The ApiLoader should load the API from that format, as well as have a way to specify more API description files to read (for the manual bindings).

Improve "stored value" reporting

If a plugin stores a parameter value from a callback for later, it should be possible to provide better diagnostics about where exactly the parameter was stored. If the callback is a 100+ line function, it can be quite difficult for a human to analyze it.

Using the debug library it should be possible to examine the function's locals and upvalues, seeing which one contains the saved instance. For example if the callback creates a new closure with the value captured into the closure, it should be possible to see that - there'll be a local value (the closure) of type function whose upvalue would be equal to the saved object.

It may be necessary to re-execute the handler in order to be able to see its internals via the debug library, possibly even installing a debug hook so that code can be executed while the function is still on the stack. Perhaps this could be made into a new detection mode that installs a hook for each callback's return and checks all its locals / upvalues.

Using cPlayer:GetWorld() crashes the checker

It seems that using cPlayer:GetWorld() crashes the checker, because it attempts to convert it to a cBroadcastInterface somehow:

Attempting to use an unknown Global value "cBroadcastInterface", nil will be returned.
stack traceback:
	Simulator.lua:1168: in function <Simulator.lua:1152>
	Simulator.lua:501: in function 'createApiEndpoint'
	Simulator.lua:554: in function <Simulator.lua:548>
	/home/ubuntu/Core/spawn.lua:6: in function 'SendPlayerToWorldSpawn'
	/home/ubuntu/Core/spawn.lua:57: in function 'Function'
	Simulator.lua:1392: in function 'processCallbackRequest'
	Simulator.lua:966: in function 'executePlayerCommand'
	Scenario.lua:179: in function 'fuzzSingleCommand'
	...
lua: Simulator.lua:501: attempt to index field '?' (a nil value)
stack traceback:
	Simulator.lua:501: in function 'createApiEndpoint'
	Simulator.lua:554: in function <Simulator.lua:548>
	/home/ubuntu/Core/spawn.lua:6: in function 'SendPlayerToWorldSpawn'
	/home/ubuntu/Core/spawn.lua:57: in function 'Function'
	Simulator.lua:1392: in function 'processCallbackRequest'
	Simulator.lua:966: in function 'executePlayerCommand'
	Scenario.lua:179: in function 'fuzzSingleCommand'
	...

Plugin: cuberite/Core#197 (comment)
CI log: https://circleci.com/gh/cuberite/Core/57

Add webadmin testing

Scenarios should allow testing (and fuzzing?) the webadmin handlers.

If possible, the webadmin tests should also detect cWorld methods usage and log a warning for these (world thread vs webadmin thread deadlock)

Scenario: Support file location relative to scenario folder

When adding redirection or using FS operations in the scenario file, the paths default to relative-to-Checker. However, it would be better to use relative-to-ScenarioFile paths, not to depend on specific Checker location.

Inspired by the difference between TravisCI and CircleCI, each clone the project out to a different folder structure.

Use a queue for callbacks

When an API function takes a callback, the call to that callback should be postponed until the current scenario step is finished, and only then should it be executed. This helps find the following bugs:

local function commandHandler(a_Player, a_Split)
  a_Player:GetWorld():QueueTask(
    function()
      a_Player:SendMessage("BUG!")
      -- a_Player may be invalid here
    end
  )
end

Add support for testing console commands

Console commands should get registered and should have Scenario-file support similar to in-game commands - executing a console command and fuzzing the console command handlers.

Add usage documentation

We need to document how this thing is to be used, what the various command line parameters do, what checking methods are supported and how to integrate this for checking plugins in a CI.

Add scenario support for FS operations

The scenario file should be able to manipulate files and folders:

  • Create file with (text) contents
  • Copy file
  • Rename file
  • Delete file
  • Create folder (recursive)
  • Rename folder
  • Delete folder (recursive)

Add multi-plugin testing

Using scenario files' initializePlugin action it would be possible to load multiple plugins and test them together (allowing testing the inter-plugin calls).

However, it should still be possible to use the Checker in the classic single-plugin way, using the -p parameter.

Implement intelligent command fuzzing

Command fuzzing could be made much more intelligently:

  1. Execute a command with (unique) random parameters
  2. If the handler just outputs a message to the player, try to see if it contains the command name, if so, try to parse usage (number of parameters) from it, otherwise just retry a few times with a different count of parameters
  3. If the command handler accesses special APIs, take note which parameter is used for the API call and use a proper value for the next retry.
    • cRoot:GetWorld() -> the param is a world name
    • cWorld:GetPlayer(), cWorld:DoWithPlayer() -> the param is a player name
    • tonumber -> a number is expected for this param
  4. Retry a few times with params adjusted based on the heuristics in step 3.

Add testcase definitions

The checker will need more information on how to test the plugin as much as possible. While the special API implementations can add callback requests (such as when registering a command, we can add a request to call the handler with various params), the plugins will need more specific testing, such as specific commands in specific sequences. Or perhaps generating in-game events in a specific order (player connected, player executing a command, player interacting with the blocks, ...)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.