QuickChickTool: The QuickChick Command-Line Tool
Set Warnings "-notation-overridden,-parsing".
Set Warnings "-extraction-opaque-accessed,-extraction".
From QuickChick Require Import QuickChick Tactics.
Import QcDefaultNotation.
Open Scope qc_scope.
Require Import Arith List.
Import ListNotations.
Set Bullet Behavior "Strict Subproofs".
Overview
In this chapter we will introduce the QuickChick command-line
tool, which supports testing of larger-scale projects than the
examples we have seen so far. The command-line tool offers several
useful features:
- Batch processing, compilation and execution of tests
- Mutation testing
- Sectioning of tests and mutants
This chapter reads a bit differently than most SF chapters, as it
deals with a command-line tool. The code that it discusses, a
simple compiler from a high-level expression language to a
low-level stack machine, can be found in the directory
stack-compiler, broken up into two files:
Exp.v containing the
high-level expression languages and
Stack.v containing the low-level
stack machine and the compiler. The chapter's
text will tell you what to type on the command line or where to
look in the subdirectory as needed.
To get started, let's try the tool out and see it's output! Go to
the
stack-compiler subdirectory and run the following command:
quickChick -color -top Stack
The
-color flag colors certain lines in the output for easier reading.
The
-top flag controls the namespace for the compilation and should
be the same as the
-R or
-Q command in your
_CoqProject file.
Running this command should produce quite a bit of output in your
terminal.
The output consists of four parts, delimited by (colored) headers such as
Testing base...
or
Testing mutant 2 (./Exp.v: line 20): Plus-copy-paste-error
Let's take a closer look at the first one.
Testing base...
make -f Makefile.coq
make
1: Entering directory '/home/lemonidas/sfdev/qc/
qc_stack-compiler.tmp'
COQDEP VFILES
COQC Exp.v
COQC Stack.v
COQC QuickChickTop.v
make1: Leaving directory '/home/lemonidas/sfdev/qc/qc_stack-compiler.tmp'
Checking Exp.optimize_correct_prop...
+++ Passed 10000 tests (0 discards)
Checking Stack.compiles_correctly...
+++ Passed 10000 tests (0 discards)
>>
As we will see later in this chapter, the QuickChick command-line tool
gathers QuickChick tests from the sources and runs them together in one
single, efficient extraction. To do that, it copies all the files (here Exp.v
and Stack.v) in a new subdirectory that is a "sibling" of the current one
(that is both the directory where you ran quickChick and the new directory
are subdirectories of the same parent). This new directory is always named
_qc_<DIRNAME>.tmp. QuickChick also produces a new file
QuickChickTop.v that contains all the tests that will be run and more
extraction directives.
Following the output of the QuickChick command-line tool, all it does is
compile everything in _qc_stack-compiler.tmp, using the Makefile of
the original development as is and a _CoqProject modified to include the
new QuickChickTop.v file. This compilation leads to extracting all necessary
files in separate OCaml modules, which are in turn compiled using
ocamlbuild, and then run. The separate extraction into distinct
ocaml modules allows us to reuse compilation effort across different
mutants as well as different calls to quickChick, as we can identify whether
newly extracted modules are actually different and recompile them or
not accordingly!
The rest of the 3 parts of the output are similar, with the main difference
being that instead of running all the tests on the unaltered, base development,
they run the same tests on mutated code. We will see exactly what
mutation testing is later in this chapter.
If all is well, the last line should be a reassuring success report:
All tests produced the expected results
Arithmetic Expressions
The code in the
stack-compiler subdirectory consists of two
modules,
Exp and
Stack, each containing a number of
definitions and properties. After some
Imports at the top, the
Exp module begins with a
section declaration:
We will explain what quickChick sections are and how to use them
later in this chapter.
It then defines a little arithmetic language, consisting of
natural literals, addition, subtraction and multiplication.
Since
exp is a simple datatype, QuickChick can derive a generator, a
shrinker, and a printer automatically.
Derive (Arbitrary, Show) for exp.
The eval function evaluates an expression to a number.
(The actual definition in the file
Exp.v contains a few more
annotations in comments, defining a
mutant. We will discuss
these annotations later in this chapter.
Now let's write a simple optimization: whenever we see an
unnecessary operation (adding/subtracting 0) we optimize it
away.
Again, the actual definition in
Exp.v contains again a few more
annotations in comments (another section annotation and another
mutant).
We can now write a simple correctness property for the optimizer,
namely that evaluating an optimized expression yields the same
number as evaluating the original one.
QuickChecking optimize_correct_prop
+++ Passed 10000 tests (0 discards)
QuickChick Test Annotations
In earlier chapters, we have included
QuickChick commands in
comments, with an invitation to the reader to uncomment and
execute them. This has been done to avoid executing each and every
test when compiling the volume as a whole. If we were to leave the
QuickChick commands uncommented, then for each test we would
extract the entire volume up to that point, compile the extracted
OCaml, execute the test (up to 10000 tests by default for
successes), and report the outcome. While this process is often
adequate for small developments, it quickly becomes intractable
for large Coq files, multi-file developments, or large numbers of
properties that need to be tested.
One main feature of the command line tool is to gather all
QuickChick commands, perform a
single extraction and compilation
pass, and report the results for all tests. This is achieved with
special QuickChick annotations.
Notice that this QuickChick comment, just like all
QuickChick-specific annotations in the file, begin with an
exclamation mark.
Comments that begin with an exclamation mark
are special to the QuickChick command-line tool parser and signify
a test, a section, or a mutant.
The annotation above defines a test of the property
optimize_correct_prop. For simplicity, each test annotation
requires a
named property, like
optimize_correct_prop. That
is, while inline one could successfully execute a command like the
one below, the command-line tool requires a defined constant in
the test annotation.
QuickChick (fun e => eval (optimize e) = eval e?).
Sections
When in the middle of a large development, it is useful to be able
to concentrate your tests in the parts of the development you are
actively changing. For example, if you are playing with the
optimizer for your high-level language it is not ideal to spend
time re-running the (successful) tests of the code generator. This
is where QuickChick's sections come in.
Sections are contiguous blocks of code within modules, and are
allowed to depend on earlier ones. They contain sets of
tests (and later on mutants) that correspond to a single aspect of
the development and that are meant to be run together.
Thare are two kinds of section declarations in QuickChick.
The first section declaration in the
Exp module simply defines
the start of a new block that can be identified by the name
"arithmetic_expressions".
The second also includes an
extends clause.
This signifies that this new block (until the end of the file,
in this case, since there are no further section headers), also
contains all tests and mutants from the
arithmetic_expressions
section as well.
To see sectioning in action, execute the following command from
the
stack-compiler directory:
quickChick -color -top Stack -s optimizations
Testing base...
make -f Makefile.coq
make
1: Entering directory '/home/lemonidas/sfdev/qc/
qc_stack-compiler.tmp'
COQDEP VFILES
COQC Exp.v
COQC Stack.v
COQC QuickChickTop.v
make1: Leaving directory '/home/lemonidas/sfdev/qc/qc_stack-compiler.tmp'
Checking Exp.optimize_correct_prop...
+++ Passed 10000 tests (0 discards)
... etc ...
In addition to the standard arguments (-color, -top Stack)
we also specified that we only care about the optimizations section
with the -s flag. Therefore this time, when testing the base
development, only the single test in optimizations was executed.
Mutation Testing
The last major feature of the QuickChick command-line tool is
mutation testing.
A question that naturally arises when random testing a software
artifact is whether the testing is actually good. Does our
property succeed for 10000 tests because everything is correct, or
because we are not covering enough of the input space? How much
testing is enough? Mutation testing can be used to answer such
questions, by intentionally introducing bugs in either the
artifacts or the properties we are testing and then checking that
the tests can detect them.
Here is an excerpt of the
eval function with a simple mutant annotation:
| APlus e1 e2 => (eval e1) + (eval e2)
Let's break it down into its parts.
QuickChick mutants come in three parts. First, an annotation
that signifies the beginning of a mutant. That is always the same:
This is followed by the correct code. In our example, by the evaluation
of
APlus e1 e2.
Afterwards, we can include an optional (but recommended) name for
the mutant, which begins with two exclamation marks to help the parser.
]]
Finally, we include mutations of the original code, each in a QuickChick
single-exclamation-mark annotation. The code of the mutation is meant to be
able to verbatim replace the original code. Here, a copy-paste error
has been made to evaluate
e1 twice as both operands in an addition.
Similarly, in the
optimize function we encounter the following mutant.
| AMinus e (ANum 0) => optimize e
This bug allows the optimization of
0-e to
e instead of
e-0 to
e.
To see these mutants in action, let's look at the rest of the output of the
last
quickChick command we ran:
quickChick -color -top Stack -s optimizations
Testing mutant 0 (./Exp.v: line 35): Minus-Reverse
make -f Makefile.coq
make
1: Entering directory '/home/lemonidas/sfdev/qc/
qc_stack-compiler.tmp'
COQDEP VFILES
COQC Exp.v
COQC Stack.v
COQC QuickChickTop.v
make1: Leaving directory '/home/lemonidas/sfdev/qc/qc_stack-compiler.tmp'
AMinus (ANum 0) (ANum 1)
Checking Exp.optimize_correct_prop...
Failed after 13 tests and 4 shrinks. (0 discards)
Testing mutant 1 (./Exp.v: line 20): Plus-copy-paste-error
make -f Makefile.coq
make1: Entering directory '/home/lemonidas/sfdev/qc/qc_stack-compiler.tmp'
COQDEP VFILES
COQC Exp.v
COQC Stack.v
COQC QuickChickTop.v
make1: Leaving directory '/home/lemonidas/sfdev/qc/qc_stack-compiler.tmp'
APlus (ANum 1) (ANum 0)
Checking Exp.optimize_correct_prop...
Failed after 5 tests and 3 shrinks. (0 discards)
All tests produced the expected results
After running all the tests for base (the unmutated artifact), the
quickChick tool proceeds to run the single test in the optimizations
section for each of the mutants it finds. Since the optimizations section
extends the arithmetic_expressions section, the mutants from both
sections will be included. As expected, the optimize property fails in
both cases.
A Low-Level Stack Machine
The second module of our development is
Stack.v, which describes
a low-level stack machine for arithmetic expressions. It contains
a single section,
stack_instructions which extends
arithmetic_expressions but not
optimizations. This allows us
to independently run tests for the compiler or the optimzer
without worrying about extra tests or mutants.
We begin by defining a low-level stack machine instruction set,
which closely corresponds to the high-level expression language.
Then we describe how to execute the stack machine.
Given the current
stack (a list of natural numbers) and the
program to be executed, we will produce a resulting stack.
- If prog is empty, we return the current stack.
- To Push an integer, we cons it in the front of the list
and execute the remainder of the program.
- To perform an arithmetic operation, we expect two integers
at the top of the stack, operate, push the result, and execute
the remainder of the program.
- Finally, if such a thing is not possible (i.e. we tried to
perform a binary operation with less than 2 elements on the
stack), we ignore the instruction and proceed to the rest.
Now we can compile expressions to their corresponding
stack-instruction sequences.
In the compilation above we have made a rookie compiler-writer
mistake. (Can you spot it without running QuickChick?)
The property we would expect to hold is that executing the
compiled instruction sequence of a given expression
e, would
result in a single element stack with
eval e as its only
element.
===>
QuickChecking compiles_correctly
AMinus (ANum 0) (ANum 1)
Failed after 3 tests and 2 shrinks. (0 discards)
The problem is that subtraction is not associative and we have compiled
the two operands in the wrong order! We can now log that mutant in our
development as shown in the
Stack module.
Fixpoint compile (e : exp) : list sinstr :=
match e with
| ANum n =>
SPush n
| APlus e1 e2 => compile e2 ++ compile e1 ++
SPlus
| AMinus e1 e2 => compile e2 ++ compile e1 ++
SMinus
| AMult e1 e2 => compile e2 ++ compile e1 ++
SMult
end.
We can now run a different command to test
compiles_correctly, using
-s stack to only check the
stack section.
quickChick -color -top Stack -s stack
Testing base...
make -f Makefile.coq
make
1: Entering directory '/home/lemonidas/sfdev/qc/
qc_stack-compiler.tmp'
COQDEP VFILES
COQC Exp.v
COQC Stack.v
COQC QuickChickTop.v
make1: Leaving directory '/home/lemonidas/sfdev/qc/qc_stack-compiler.tmp'
Checking Stack.compiles_correctly...
+++ Passed 10000 tests (0 discards)
Testing mutant 0 (./Stack.v: line 33): Wrong associativity
make -f Makefile.coq
make1: Entering directory '/home/lemonidas/sfdev/qc/qc_stack-compiler.tmp'
COQDEP VFILES
COQC Stack.v
COQC QuickChickTop.v
make1: Leaving directory '/home/lemonidas/sfdev/qc/qc_stack-compiler.tmp'
AMinus (ANum 0) (ANum 1)
Checking Stack.compiles_correctly...
Failed after 5 tests and 6 shrinks. (0 discards)
Testing mutant 1 (./Exp.v: line 20): Plus-copy-paste-error
make -f Makefile.coq
make1: Entering directory '/home/lemonidas/sfdev/qc/qc_stack-compiler.tmp'
COQDEP VFILES
COQC Exp.v
COQC Stack.v
COQC QuickChickTop.v
make1: Leaving directory '/home/lemonidas/sfdev/qc/qc_stack-compiler.tmp'
APlus (ANum 0) (ANum 1)
Checking Stack.compiles_correctly...
Failed after 5 tests and 2 shrinks. (0 discards)
All tests produced the expected results
We can see that the main property succeeds, while the two mutants (the one
in arithmetic_expressions that was included because of the extension
and the one in stack) both fail.
QuickChick Command-Line Tool Flags
For more information on the tool's flags, look at the reference manual
in
QuickChickInterface.