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 Complete example
below.
The tutorial assumes that you have installed Mull on your system and have the mull-cxx executable available:
$ mull-cxx -version
Mull: LLVM-based mutation testing
https://github.com/mull-project/mull
Version: 0.7.0
Commit: 1638698
Date: 28 Mar 2020
LLVM: 9.0.0
The most important thing that Mull needs to know is the path to your program which must be a valid C or C++ executable. Let’s create a C program:
int main() {
return 0;
}
and compile it:
clang main.cpp -o hello-world
We can already try running mull-cxx
and see what happens:
$ mull-cxx hello-world
mull-cxx: for the -test-framework option: must be specified at least once!
This is the second important thing that Mull needs: we have to specify which kind of test framework Mull should assume our program uses.
We specify CustomTest
:
mull-cxx -test-framework=CustomTest hello-world
-test-framework=CustomTest
parameter tells Mull that it should not expect
a real test framework such as Google Test or any kind of advanced test suite.
Instead Mull will simply consider that our tests will be simple test functions
which we will call from the main()
function.
Now the output is different:
[info] Extracting bitcode from executable (threads: 1)
[warning] No bitcode: x86_64
[################################] 1/1. Finished in 1ms
[info] Loading dynamic libraries (threads: 1)
[################################] 1/1. Finished in 0ms
[info] Searching tests (threads: 1)
[################################] 1/1. Finished in 0ms
[info] No mutants found. Mutation score: infinitely high
Notice the No bitcode: x86_64
warning! Now Mull is already trying to work
with our executable but there is still one important detail that is missing: we
haven’t compiled the program with a special option that embeds LLVM bitcode
into our executable.
Mull works on a level of LLVM Bitcode relying on debug information to show
results, therefore you should build your project with -fembed-bitcode
and
-g
flags enabled.
Let’s try again:
$ clang -fembed-bitcode -g main.cpp -o hello-world
$ mull-cxx -test-framework CustomTest hello-world
[info] Extracting bitcode from executable (threads: 1)
[################################] 1/1. Finished in 3ms
[info] Loading bitcode files (threads: 1)
[################################] 1/1. Finished in 11ms
[info] Compiling instrumented code (threads: 1)
[################################] 1/1. Finished in 11ms
[info] Loading dynamic libraries (threads: 1)
[################################] 1/1. Finished in 0ms
[info] Searching tests (threads: 1)
[################################] 1/1. Finished in 0ms
[info] Preparing original test run (threads: 1)
[################################] 1/1. Finished in 1ms
[info] Running original tests (threads: 1)
[################################] 1/1. Finished in 12ms
[info] No mutants found. Mutation score: infinitely high
The No bitcode: x86_64
warning has gone and now we can focus on another
important part of the output: No mutants found. Mutation score: infinitely
high
. We have our executable but we don’t have any code so there is nothing
Mull could work on.
Let’s add some code:
bool valid_age(int age) {
if (age >= 21) {
return true;
}
return false;
}
int main() {
int test1 = valid_age(25) == true;
if (!test1) {
/// test failed
return 1;
}
int test2 = valid_age(20) == false;
if (!test2) {
/// test failed
return 1;
}
/// success
return 0;
}
We compile this new code using the bitcode flags and run the Mull again. This
time we also want to add additional flag -ide-reporter-show-killed
which
tells Mull to print killed mutations. Normally we are not interested in seeing
killed mutations in console input but in this tutorial we want to be more
verbose.
$ clang -fembed-bitcode -g main.cpp -o hello-world
$ mull-cxx -test-framework=CustomTest -ide-reporter-show-killed hello-world
[info] Extracting bitcode from executable (threads: 1)
[################################] 1/1. Finished in 10ms
[info] Loading bitcode files (threads: 1)
[################################] 1/1. Finished in 12ms
[info] Compiling instrumented code (threads: 1)
[################################] 1/1. Finished in 12ms
[info] Loading dynamic libraries (threads: 1)
[################################] 1/1. Finished in 0ms
[info] Searching tests (threads: 1)
[################################] 1/1. Finished in 0ms
[info] Preparing original test run (threads: 1)
[################################] 1/1. Finished in 1ms
[info] Running original tests (threads: 1)
[################################] 1/1. Finished in 11ms
[info] Applying function filter: no debug info (threads: 1)
[################################] 1/1. Finished in 1ms
[info] Applying function filter: file path (threads: 1)
[################################] 1/1. Finished in 10ms
[info] Instruction selection (threads: 1)
[################################] 1/1. Finished in 0ms
[info] Searching mutants across functions (threads: 1)
[################################] 1/1. Finished in 13ms
[info] Applying filter: no debug info (threads: 2)
[################################] 2/2. Finished in 0ms
[info] Applying filter: file path (threads: 2)
[################################] 2/2. Finished in 10ms
[info] Prepare mutations (threads: 1)
[################################] 1/1. Finished in 0ms
[info] Cloning functions for mutation (threads: 1)
[################################] 1/1. Finished in 11ms
[info] Removing original functions (threads: 1)
[################################] 1/1. Finished in 10ms
[info] Redirect mutated functions (threads: 1)
[################################] 1/1. Finished in 1ms
[info] Applying mutations (threads: 1)
[################################] 2/2. Finished in 0ms
[info] Compiling original code (threads: 1)
[################################] 1/1. Finished in 10ms
[info] Running mutants (threads: 2)
[################################] 2/2. Finished in 12ms
[info] Killed mutants (1/2):
/tmp/sc-b3yQyijWP/main.cpp:2:11: warning: Killed: Replaced >= with < [cxx_ge_to_lt]
if (age >= 21) {
^
[info] Survived mutants (1/2):
/tmp/sc-b3yQyijWP/main.cpp:2:11: warning: Survived: Replaced >= with > [cxx_ge_to_gt]
if (age >= 21) {
^
[info] Mutation score: 50%
What we are seeing now is two mutations: one mutation is 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 -fembed-bitcode -g main.cpp -o hello-world
$ mull-cxx -test-framework=CustomTest -ide-reporter-show-killed hello-world
[info] Extracting bitcode from executable (threads: 1)
[################################] 1/1. Finished in 4ms
[info] Loading bitcode files (threads: 1)
[################################] 1/1. Finished in 12ms
[info] Compiling instrumented code (threads: 1)
[################################] 1/1. Finished in 11ms
[info] Loading dynamic libraries (threads: 1)
[################################] 1/1. Finished in 0ms
[info] Searching tests (threads: 1)
[################################] 1/1. Finished in 0ms
[info] Preparing original test run (threads: 1)
[################################] 1/1. Finished in 1ms
[info] Running original tests (threads: 1)
[################################] 1/1. Finished in 10ms
[info] Applying function filter: no debug info (threads: 1)
[################################] 1/1. Finished in 11ms
[info] Applying function filter: file path (threads: 1)
[################################] 1/1. Finished in 11ms
[info] Instruction selection (threads: 1)
[################################] 1/1. Finished in 11ms
[info] Searching mutants across functions (threads: 1)
[################################] 1/1. Finished in 0ms
[info] Applying filter: no debug info (threads: 2)
[################################] 2/2. Finished in 0ms
[info] Applying filter: file path (threads: 2)
[################################] 2/2. Finished in 0ms
[info] Prepare mutations (threads: 1)
[################################] 1/1. Finished in 1ms
[info] Cloning functions for mutation (threads: 1)
[################################] 1/1. Finished in 11ms
[info] Removing original functions (threads: 1)
[################################] 1/1. Finished in 12ms
[info] Redirect mutated functions (threads: 1)
[################################] 1/1. Finished in 0ms
[info] Applying mutations (threads: 1)
[################################] 2/2. Finished in 0ms
[info] Compiling original code (threads: 1)
[################################] 1/1. Finished in 12ms
[info] Running mutants (threads: 2)
[################################] 2/2. Finished in 10ms
[info] Killed mutants (2/2):
/tmp/sc-b3yQyijWP/main.cpp:2:11: warning: Killed: Replaced >= with > [cxx_ge_to_gt]
if (age >= 21) {
^
/tmp/sc-b3yQyijWP/main.cpp:2:11: warning: Killed: Replaced >= with < [cxx_ge_to_lt]
if (age >= 21) {
^
[info] All mutations have been killed
[info] Mutation score: 100%
This is a short summary of what we have learned in tutorial:
Your code has to be compiled with -fembed-bitcode -g
compile flags:
Mull expects embedded bitcode files to be present in binary executable
(ensured by -fembed-bitcode
).
Mull needs debug information to be included by the compiler (enabled by
-g
). Mull uses this information to find mutations in bitcode and source
code.
Mull expects the following arguments to be always provided:
Your executable program
-test-framework
parameter that tells Mull which kind of testing
framework to expect. In this tutorial we have been using the CustomTest
framework.