Note
Since the version 0.15.0, mull-cxx
tool is deprecated
in favour of a compiler plugin: Mull IR Frontend.
If you are looking for mull-cxx
tutorial, refer to this page instead.
Note
We would love to hear from you! ❤️
Please, report any issues on GitHub, or bring your questions to the #mull channel on Discord.
The goal of this tutorial is to demonstrate how to run Mull on minimal C programs. After reading it you should have a basic understanding of what arguments Mull needs in order to create mutations in your programs, run the mutants and generate mutation testing reports.
TL;DR version: if you want to run a single copy and paste example, scroll
down to Killing mutants again, all killed
below.
Note
Clang 9 or newer is required!
Mull comes in a form of a compiler plugin and therefore tied to specific versions of Clang and LLVM. As a consequence of that, tools and plugins have a suffix with the actual Clang/LLVM version.
This tutorial assumes that you are using Clang 12 and that you have
installed Mull on your system and have the mull-runner-12
executable available:
$ mull-runner-12 -version
Mull: LLVM-based mutation testing
https://github.com/mull-project/mull
Version: 0.15.0
Commit: a4be349e
Date: 18 Jan 2022
LLVM: 12.0.1
Let’s create a C++ program:
int main() {
return 0;
}
and compile it:
$ clang-12 main.cpp -o hello-world
We can already try using mull-runner
and see what happens:
$ mull-runner-12 ./hello-world
[info] Warm up run (threads: 1)
[################################] 1/1. Finished in 5ms
[info] Baseline run (threads: 1)
[################################] 1/1. Finished in 4ms
[info] No mutants found. Mutation score: infinitely high
[info] Total execution time: 10ms
Notice the No mutants found
message! Now, Mull is ready to work with the executable
but there are no mutants: we haven’t compiled the program with the compiler plugin that embeds
mutants into our executable.
Let’s fix that! To pass the plugin to Clang, you need to add a few compiler flags.
Note
For Clang 9, 10, and 11 also pass -O1
, otherwise the plugin won’t be called.
$ clang-12 -fexperimental-new-pass-manager \
-fpass-plugin=/usr/local/lib/mull-ir-frontend-12 \
-g -grecord-command-line \
main.cpp -o hello-world
[warning] Mull cannot find config (mull.yml). Using some defaults.
Notice the warning: Mull needs a config. However, in this tutorial we can ignore the warning and rely on the defaults.
You can learn more about the config here.
Let’s run mull-runner
again:
$ mull-runner-12 ./hello-world
[info] Warm up run (threads: 1)
[################################] 1/1. Finished in 4ms
[info] Baseline run (threads: 1)
[################################] 1/1. Finished in 6ms
[info] No mutants found. Mutation score: infinitely high
[info] Total execution time: 12ms
Still no mutants, but this time it is because we don’t have any code Mull can mutate.
Let’s add some code:
bool valid_age(int age) {
if (age >= 21) {
return true;
}
return false;
}
int main() {
bool test1 = valid_age(25) == true;
if (!test1) {
/// test failed
return 1;
}
bool test2 = valid_age(20) == false;
if (!test2) {
/// test failed
return 1;
}
/// success
return 0;
}
We re-compile this new code using the plugin and run the Mull again. This
time we also want to add an additional flag -ide-reporter-show-killed
which
tells Mull to print killed mutations. Normally we are not interested in seeing
killed mutants in console output but in this tutorial we want to be more
verbose.
$ clang-12 -fexperimental-new-pass-manager \
-fpass-plugin=/usr/local/lib/mull-ir-frontend-12 \
-g -grecord-command-line \
main.cpp -o hello-world
$ mull-runner-12 -ide-reporter-show-killed hello-world
[info] Warm up run (threads: 1)
[################################] 1/1. Finished in 151ms
[info] Baseline run (threads: 1)
[################################] 1/1. Finished in 3ms
[info] Running mutants (threads: 4)
[################################] 4/4. Finished in 10ms
[info] Killed mutants (3/4):
/tmp/sc-tTV8a84lL/main.cpp:2:11: warning: Killed: Replaced >= with < [cxx_ge_to_lt]
if (age >= 21) {
^
/tmp/sc-tTV8a84lL/main.cpp:9:30: warning: Killed: Replaced == with != [cxx_eq_to_ne]
bool test1 = valid_age(25) == true;
^
/tmp/sc-tTV8a84lL/main.cpp:15:30: warning: Killed: Replaced == with != [cxx_eq_to_ne]
bool test2 = valid_age(20) == false;
^
[info] Survived mutants (1/4):
/tmp/sc-tTV8a84lL/main.cpp:2:11: warning: Survived: Replaced >= with > [cxx_ge_to_gt]
if (age >= 21) {
^
[info] Mutation score: 75%
[info] Total execution time: 167ms
What we are seeing now is four mutations: three mutations are Killed
, another
one is Survived
. If we take a closer look at the code and the contents
of the tests test1
and test2
we will see that one important test case
is missing: the one that would test the age 21
and this is exactly
what the survived mutation is about: Mull has replaced age >= 21
with
age > 21
and neither of the two tests have detected the mutation.
Let’s add the third test case and see what happens.
The code:
bool valid_age(int age) {
if (age >= 21) {
return true;
}
return false;
}
int main() {
bool test1 = valid_age(25) == true;
if (!test1) {
/// test failed
return 1;
}
bool test2 = valid_age(20) == false;
if (!test2) {
/// test failed
return 1;
}
bool test3 = valid_age(21) == true;
if (!test3) {
/// test failed
return 1;
}
/// success
return 0;
}
$ clang-12 -fexperimental-new-pass-manager \
-fpass-plugin=/usr/local/lib/mull-ir-frontend-12 \
-g -grecord-command-line \
main.cpp -o hello-world
$ mull-runner-12 -ide-reporter-show-killed hello-world
[info] Warm up run (threads: 1)
[################################] 1/1. Finished in 469ms
[info] Baseline run (threads: 1)
[################################] 1/1. Finished in 4ms
[info] Running mutants (threads: 5)
[################################] 5/5. Finished in 12ms
[info] Killed mutants (5/5):
/tmp/sc-tTV8a84lL/main.cpp:2:11: warning: Killed: Replaced >= with > [cxx_ge_to_gt]
if (age >= 21) {
^
/tmp/sc-tTV8a84lL/main.cpp:2:11: warning: Killed: Replaced >= with < [cxx_ge_to_lt]
if (age >= 21) {
^
/tmp/sc-tTV8a84lL/main.cpp:9:30: warning: Killed: Replaced == with != [cxx_eq_to_ne]
bool test1 = valid_age(25) == true;
^
/tmp/sc-tTV8a84lL/main.cpp:15:30: warning: Killed: Replaced == with != [cxx_eq_to_ne]
bool test2 = valid_age(20) == false;
^
/tmp/sc-tTV8a84lL/main.cpp:21:30: warning: Killed: Replaced == with != [cxx_eq_to_ne]
bool test3 = valid_age(21) == true;
^
[info] All mutations have been killed
[info] Mutation score: 100%
[info] Total execution time: 487ms
In this last run, we see that all mutants were killed since we covered with tests
all cases around the <=
.
As a summary, all you need to enable Mull is to add a few compiler flags to the
build system and then run mull-runner
against the resulting executable.
Just to recap:
$ clang-12 -fexperimental-new-pass-manager \
-fpass-plugin=/usr/local/lib/mull-ir-frontend-12 \
-g -grecord-command-line \
main.cpp -o hello-world
$ mull-runner-12 hello-world