Giter Club home page Giter Club logo

faketorio's Introduction

Faketorio

Logo

Travis LuaRocks Coveralls license

Support library for automated testing of Factorio mods

Purpose

The problem with modding in Factorio (or any other script language based modding environment) is testing. Usually this is solved by a huge amount of manual testing. This amount grows with every new feature and every bugfix.. because in theory you have to test EVERYTHING EVERY.SINGLE.TIME (super annoying).

This is where automated testing comes in. I'm new to lua testing, but there are a couple unit testing frameworks available.

The problem with unit testing is that it only gets you so far. The most interesting aspect (how does it behave in the real game) is hard to mimic.

Enter Faketorio. The goal is to provide a standard system for running your mod against your local Factorio installation, execute tests INSIDE Factorio and then package your mod for release.

Installation

If you use LuaRocks for your lua modules it is as easy as running luarocks install faketorio.

If you do all the dependency management yourself you will have to download the files from this github repo and place them in a convenient location. Then require them with the proper path.

Usage

For a (very) short info on how to interact with faketorio type faketorio -h. For the long version keep reading :)

There are two main aspects of faketorio regarding your mod. The first is mod management and packaging and the second is testing.

Mod management

With Faketorio you can run and build your mod. To do this we need two things: a .faketorio config file in your home folder and a certain folder structure in your mod.

Folder structure

Faketorio assumes the following structure for your mod files:

- MyModFolder/
    - info.json             // the file with all your mod info
    - src/                  // all your mod source files go here (in as many subfolders as you like)
        - control.lua
        - data.lua
        - settings.lua
        - mymod.lua
    - locale/               // all your locale files as described in [aubergine10's guide to localization](https://github.com/aubergine10/Style/wiki/Localisation)
    - spec/                 // all your test files go here (as described by [busted](https://olivinelabs.com/busted/#defining-tests)
        - mod_spec.lua      // normal [busted](https://github.com/Olivine-Labs/busted) unit tests (or any other tests for that matter)
        - mod_feature.lua   // faketorio tests that are run inside of Factorio
    - target/               // temporary folder that faketorio uses to assemble files and folders

The Factorio specific files (control.lua, data.lua, settings.lua, ...) all go into the source folder without any additional subfolders.

.faketorio config file

Faketorio requires a config file to run. You can specify the location with the -c option. If no config path is provided faketorio will search for a file named .faketorio in the current folder. This file has to have three values configured.

factorio_mod_path = "/home/jjurczok/.factorio/mods"
factorio_run_path = "/home/jjurczok/.local/share/Steam/steamapps/common/Factorio/bin/x64/factorio"

# windows based example
factorio_run_path = "D:\Spiele\Factorio\bin\x64\factorio.exe"

faketorio_path = "/usr/share/lua/5.2/faketorio"

factorio_mod_path describes the folder where all your mods are. On my machine this is in the home folder, on windows it will be somewhere else factorio_run_path is the path to the executable (Factorio.exe on windows)

faketorio_path is the path where you installed faketorio itself. If you installed it via luarocks you can find this path with luarocks show faketorio or luarocks doc faketorio

Including custom files

If you want to inlcude custom files into your zip you can specifiy it your .faketorio file like this:

include_files = {
    ["filename"] = "target file name"
}

This will look for filename relative to the current folder. If the file is found it will be copied to target/target file name.

Faketorio commands

With the configuration in place and the mod structured we can now start interacting with faketorio. Faketorio should be run from your mod folder. To do this open a terminal (or command line with start -> execute -> cmd on windows) and navigate to the folder where you do your development (ideally NOT inside the Factorio mods folder ;).

Now you can execute the faketorio commands to interact with your mod and Factorio.

Command name Description
faketorio build Creates a subfolder in target/<your mod name_version>.
The mod name and version are read from the info.json.
Copies all mod src files, locales and other resources to this folder.

Intended if you want to take a look at your mod but not produce an uploadable zip file.
faketorio clean Deletes the target folder.
faketorio copy Copies the mod to the factorio mod folder defined in the .faketorio config.
In Factorio you can click restart to see the current version of your mod.
Restarting Factorio is only necessary if you changed locales or graphics.
faketorio package Creates a zip file that is ready for uploading to the factorio mod portal.
faketorio run Runs the build and copy command.
Generates a new Factorio map.
Factorio is started and this newly generated map is loaded.
faketorio test This command runs Factorio on a new map.
Your mod will also contain all your feature files and the faketorio test engine.
Ingame type /faketorio in the debug console to run all your tests.

Tests

Now that we covered how to interact with Factorio itself it is time to talk about the real reason for this library. The actual testing.

The tests are inspired by busted and use roughly the same basic principles.

-- in mod_feature.lua
feature("My first feature", function()
    
    before_scenario(function()
        -- will be called before every scenario in this feature
        -- this is intended for setting up preconditions for this specific feature
    end)

    after_scenario(function()
        -- will be called after every scenario in this feature
        -- this is intended to bring the mod back into a state as it would be expected from the next test
    end)
    
    scenario("The first scenario", function()
        faketorio.click("todo_maximize_button")
        faketorio.log.info("my first test")
    end)

    scenario("The second scenario", function()
        faketorio.log.info("Scenario 2")
    end)
end)

As described in Folder Structure you can create feature files in your spec folder. These files have to follow the naming pattern <name>_feature.lua. It is best practice to have one feature per file.

In the scenarios you can basically write normal lua code to interact with your mod. Faketorio provides you with some additional functions that you can use.

Running tests ingame

To run your tests inside of Factorio simply invoke faketorio test in a terminal in the mod folder. Faketorio will generate a new map, copy your mod and the tests and starts Factorio.

As soon as you are in game you can open the debug console and enter /faketorio. Simply run the command and all your tests will be executed.

You will see a dialog popping up in the middle of the screen. The top bar indicates progress on feature level, the lower bar indicates progress on scenario level for the currently running feature.

If there are test errors they will be documented in the textbox below the progress bar after the testrun finishes.

Marking tests as success/failure

By default any scenario function that completes is counted as successful. If you want to mark your test as a failure (because some assertion did not work) you can just raise an error

-- in mod_feature.lua
feature("My first feature", function()
    
    scenario("The first scenario", function()
        faketorio.print("This test is successful.")
    end)

    scenario("The second scenario", function()
        faketorio.print("This test fails.")
        error("test failure")
    end)
end)

At the end of every test run you will receive a report to the console (and the logfile) indicating what worked and what did not. As usual a testrun is only successful if ALL tests pass!

TODO: provide screenshots of success and failure cases

TESTRUN FINISHED: SUCCESS
Run contained 1 features.

Interacting with Factorio

Faketorio provides the following functions

Function name Parameters Return value/Action
faketorio.click name - the name of the gui element to click on If the element exists a defines.events.on_gui_click event for the provided element will be raised.
If the element does not exist an exception is thrown.

Currently only left clicks without modifiers (ctrl, alt, shift) are supported.
player - (optional) the player who owns the element.
If not provided the first player in game will be used
faketorio.enter_text name - the name of the element to enter text in Sets the text attribute of the named element to the text.
This does NOT fire all the keystroke events you would expect from actually entering the text!
text - the text to enter
player - (optional) the player who owns the element.
If not provided the first player in game will be used
faketorio.find_element_by_id name - the name of the element Returns the element with the given name.
Returns nil if the element does not exist.
player - The player who owns the element.
If not provided the first player in game will be used
faketorio.check name - the name of the checkbox Sets the checkbox state to true
If the checkbox does not exist or is not a checkbox an exception is thrown.
player - The player who owns the element.
If not provided the first player in game will be used
faketorio.uncheck name - the name of the checkbox Sets the checkbox state to false
If the checkbox does not exist or is not a checkbox an exception is thrown.
player - The player who owns the element.
If not provided the first player in game will be used

Assertions

The following assertions are provided by faketorio

Function name Parameters Return value/Action
faketorio.assert_element_exists name - the name of the element Returns the element with the given name.
Throws exception if the element does not exist.
player - The player who owns the element.
If not provided the first player in game will be used
faketorio.assert_element_not_exists name - the name of the element Asserts that the element does not exist.
If the element is found an exception is thrown.
player - The player who owns the element.
If not provided the first player in game will be used
faketorio.assert_checked name - the name of the checkbox Asserts that the checkbox is checked.
If the element is not found or is not a checkbox an exception is thrown.
player - The player who owns the element.
If not provided the first player in game will be used
faketorio.assert_unchecked name - the name of the checkbox Asserts that the checkbox is unchecked.
If the element is not found or is not a checkbox an exception is thrown.
player - The player who owns the element.
If not provided the first player in game will be used

logging

The log system functions are intended for debug output and if you want to report something to the user while the tests are running.

The system knows four different log levels. TRACE, DEBUG, INFO and WARN. The default log level is INFO. All messages that are logged with a level lower (read left in the list) as the current log level will be ignored.

Messages passed to the logging system will be printed to every player in Factorio. Additionally it will be written to the faketorio.log file in your Factorio script-output folder in your application directory.

To create log messages use one of the following functions

-- simple logging
faketorio.log.trace("my test trace message")
faketorio.log.debug("my test debug message")
faketorio.log.info("my test info message")
faketorio.log.warn("my test warn message")

-- logging with parameter expansion (prints "my test pattern wololo.")
faketorio.log.trace("my test pattern %s.", {"wololo"})
faketorio.log.debug("my test pattern %s.", {"wololo"})
faketorio.log.info("my test pattern %s.", {"wololo"})
faketorio.log.warn("my test pattern %s.", {"wololo"})

-- to change the current log level and thus limiting the output during your tests use
faketorio.log.setTrace()
faketorio.log.setDebug()
faketorio.log.setInfo()
faketorio.log.setWarn()

mocks

Sometimes it is necessary to change the behavior of your mod, pretending certain external events happened. One example would be the user modified the mod settings. As the settings table is read only for the mod we can just mock the result.

Assuming you have a function like this

function myMod.is_setting_enabled(player)
    return settings.get_player_settings(player)["my-mod-setting"].value
end

we can now change the behavior of that function by mocking it:

local player = ...
myMod.is_setting_enabled(player) -- returns true (default value)

-- lets create a mock
when(myMod, "is_setting_enabled"):then_return(false)

myMod.is_setting_enabled(player) -- returns false (the mocked value)

myMod.is_setting_enabled:revert()
myMod.is_setting_enabled(player) -- returns true again (it calls the original function)

Credits

Faketorio is inspired by Factorio stdlib and the work Afforess did there to enhance the testing. Over time I hope to become as complete as this testing system.

faketorio's People

Contributors

jonasjurczok avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

faketorio's Issues

Load save files for tests

Some tests need setup, or rely on a specific set of data being in a save. For example, backwards compatibility tests could be made by loading a save from an earlier version of the mod.

To this end, it would be much better to be able to load a file automatically for a given set of tests than to have to do manual setup each time.

add sleep() command

Is there a way to wait one or more frames during a test? It would be nice to be able to handle interactions over time in order to avoid faking everything.

prepare release command

To generate all info needed for a release this command should do the following:

  • collect changes from the changelog (last tag -> now)
  • create changelog entry
  • changelog entry should follow a simple syntax to distinguish bugfixes from features and breaking changes
  • bump mod version accordingly

workflow:

  • work on feature branches and merge to master
  • locally create new branch
  • run faketorio prepare-release
  • commit, PR, merge
  • build release

simple workflow:

  • work on feature branch
  • when work finished run prepare release
  • include changelog and info.json changes in the PR

Fully automated test run

I've barely started using faketorio, but it looks like it always requires manual interaction in order to run tests. Is this true? It would be really nice to have it start Factorio, run the tests, and then exit with a report and appropriate exit code.

Add coverage

It would be cool to generate a luacov coverage report.
It should be fairly simple to integrate luacov

Depends on #40

Test coverage for lib.lua

The coverage for that file is abysimal.
There are two options:

  • change all other tests to call that function instead of interacting with the system directly
  • write specific tests for that file.

mocks

Sometimes it is necessary to mock certain behavior.
It would be cool to have something along the lines of when(myMod.myFunction).thenReturn()

Of course removing the mock should also be possible.

Run factorio in headless server mode

For CI systems like travis it's difficult to get the full client for a couple reasons

  • One needs to be logged in to download the full factorio
  • Downloading 800 megabytes repeatedly from Wube servers would be slow (and lots of traffic)
  • re-hosting the full version of factorio on an easier to access host seems like trouble

It would be nice if there was a way to run tests against the headless server. I got this working locally by doing the following

  • call factorio.run() on the first tick
  • intentionally error(...) after all specs have run to stop (crash) the server
  • use --start-server instead of --load-game
  • use a modified server-settings.json that does things like disabling matchmaking and auto-pause

The only major issue with this approach is that it's not possible to get a player in game. This means no GUI testing, or testing of anything that relies on a player.

The benefits are pretty nice however

  • completely automated testing is possible
  • quicker game loading
  • easier to set up on CI.

Look for config file in home directory

I think the standard place for a .faketorio file should be in the user's home directory, since it contains information which is not specific to any mod. It would be useful if faketorio looked there by default, so I didn't have to pass the path in with every invocation.

Macos/Steam issues

While working on factorio-todo-list on a mac which has factorio installed via steam, I discovered that faketorio doesn't quite work out of the box.

First Issue - confirm launch with steam

The first issue is that Steam asks the user to confirm (by clicking a button!!!) the launch with cli args every time. I don't think this is something faketorio can avoid, so feel free to disregard :)

Second Issue - spaces in paths break things

Faketorio doesn't seem to handle spaces in path names very well. This is relevant because the path to the factorio executable on mac+steam is /Users/<user_name>/Library/Application Support/Steam/steamapps/common/Factorio/factorio.app/Contents/MacOS/factorio Faketorio can't seem to follow that path. Similar issue exists with pointing to the mods directory.
workaround: I made a symlink in my home directory that points to the 'app data' directory that contains mods and saves (/Users/<user_name>/Library/Application Support/factorio/), and put a symlink in there that links to the executable directly, so it's all in one place and there're no pesky spaces in the path. Then I updated my .faketorio file to point to the symlinks. This allowed faketorio to launch the game with the new mod build, but added a third issue:

Third Issue - won't create the blank savegame

Faketorio doesnt actually create the 'testing sandbox' savegame in this environment, so upon launching it shows the 'can't find specified save' error. I suspect this also has to do with the complicated filestructure.
workaround: The error isn't fatal, so I just load up an existing game or make a new one and run /faketorio ¯\_(ツ)_/¯

before_each/after_each

It would be cool to have a before_each/after_each functionality so that controlling test behaviour is a little bit easier.

Ability to dismiss results window

Minor thing, but when I run /faketorio in-game, the results window appears, but there doesn't seem to be a way to dismiss it, if I want to run the tests again. (If I missed it, apologies)

Introduce log levels?

The faketorio code is producing a lot of debug messages.
It would be cool to be able to selectively disable some of them.

The easiest way would be a log level like mechanic so that the user can enable or disable logging during the tests as she pleases.

add UI to faketorio tests

It would be really cool to have a graphical representation of what happens during a faketorio run. And not just output in the chat.

Testreport

The ingame testing engine should recognize failures and successes and report the overall testsuite result to the user.

Asserts

Faketorio needs a way to assert results. Either we pull in a lib (complicated) or just copy the needed basic asserts (easy)

Symlink source files for testing

I really like faketorio's publishing abilities, and I've restructured my mod for it. The one thing I miss from when I just had my working copy in the mods folder, is being able to make edits to control.lua while Factorio is running.

Would you consider making a development mode for the "run" command which symlinks instead of copying script files? I understand that this probably isn't compatible with your current method of staging, but it would make iteration as fast as if the originals were in the mod folder.

print stack trace to increase findability

Currently faketorio prints only the last line of the stacktrace in the error message.
This makes understanding the context of the error quite hard.

Faketorio should print the whole stacktrace.

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.