Mutation testing

This page describes how to use mutation testing to improve unit tests.

Setup

We already have a mutation testing tool in the Prosody repo, but you’ll need to make sure you have some of its dependencies installed.

Also, if you haven’t already, you need to have everything you need to compile Prosody. See [

busted

On Debian, apt install lua-busted or on other platforms you can try luarocks install busted if you have luarocks installed.

ltokenp

Next, you’ll need to download, compile and install ltokenp. Don’t worry, it’s easy. Head to this URL and click the ‘5.x’ link to download the source tarball for Lua 5.x.

tar xzf ltokenp-103.tar.gz
cd ltokenp-103
make all

This will create an ltokenp executable in the current directory and test that it works.

To install it, simply copy it to somewhere in your $PATH. You can do this easily (to ‘/usr/local/bin/ltokenp’) with this command:

sudo make install

Generating mutants

We’re going to use ‘util.array’ as an example in this section, but replace it with whichever module you want to test. The tool will automatically identify the correct filename and tests to use from spec/.

./tools/test_mutants.sh.lua util.array

All being well, the tests will run and report the mutation score at the end. Any mutants that were successfully caught by the existing tests will have been deleted. Mutants that were not caught are kept on the disk for inspection.

Dealing with uncaught mutants

List the uncaught mutants:

ls util/array.mutant*.lua

Open one of the mutant files. The source code will be unformatted, but there will also be a comment explaining the change that was made in that mutant.

Now, open up the original file (e.g. util/array.lua) and go to the modified line (which will show you the original non-mutated source code).

The first thing to check is: should this change have caused the tests to fail? Usually the answer is “yes”, but often changes are harmless (this often happens when the tool modifies parameters of a string.sub() or string.byte() call, or causes a performance optimization to be skipped without changing the program behaviour). We call these “equivalent mutants”, and they can be ignored. If you’re not sure, don’t be afraid to ask for a second opinion - it’s not always easy, especially if it’s unfamiliar code.

Once you’re convinced that the mutant introduced a bug that should have been caught by the tests, your challenge is to add extra tests that will catch the mutant. Open up the spec file (e.g. spec/util_array_spec.lua) containing all the tests. Figure out what tests are missing, and add them.

Once you think the new tests are added, re-run the mutation tests to confirm:

./tools/test_mutants.sh.lua util.array

If all is well, the mutation score will have increased. Often, trying to fix one mutant will also fix others too!

Sometimes your extra tests may cause the unmodified source to fail. The tool will warn you if this happens, and refuse to run the mutation tests. You’ll need to check whether your tests are correct, or if maybe you found a bug in the module. If so, congratulations :)

Once only equivalent mutants are remaining for a module, you can move on. Run make test lint, and if everything looks good you can commit your new tests along with any bug fixes, and move on to another module.

If this is your first time working on Prosody, check our contributing guidelines for helpful advice and tips. And thank you!