QuickChickToolThe QuickChick Command-Line Tool
Overview
- Batch processing, compilation and execution of tests
- Mutation testing
- Sectioning of tests and mutants
quickChick -color -top Stack
Testing base...or
Testing mutant 2 (./Exp.v: line 20): Plus-copy-paste-errorLet'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 make[1]: 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)
All tests produced the expected results
Arithmetic Expressions
(*! Section arithmetic_expressions *)
Inductive exp : Type :=
| ANum : nat → exp
| APlus : exp → exp → exp
| AMinus : exp → exp → exp
| AMult : exp → exp → exp.
| ANum : nat → exp
| APlus : exp → exp → exp
| AMinus : exp → exp → exp
| AMult : exp → exp → exp.
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.
Fixpoint eval (e : exp) : nat :=
match e with
| ANum n ⇒ n
| APlus e1 e2 ⇒ (eval e1) + (eval e2)
| AMinus e1 e2 ⇒ (eval e1) - (eval e2)
| AMult e1 e2 ⇒ (eval e1) × (eval e2)
end.
match e with
| ANum n ⇒ n
| APlus e1 e2 ⇒ (eval e1) + (eval e2)
| AMinus e1 e2 ⇒ (eval e1) - (eval e2)
| AMult e1 e2 ⇒ (eval e1) × (eval e2)
end.
(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.
Fixpoint optimize (e : exp) : exp :=
match e with
| ANum n ⇒ ANum n
| APlus e (ANum 0) ⇒ optimize e
| APlus (ANum 0) e ⇒ optimize e
| APlus e1 e2 ⇒ APlus (optimize e1) (optimize e2)
| AMinus e (ANum 0) ⇒ optimize e
| AMinus e1 e2 ⇒ AMinus (optimize e1) (optimize e2)
| AMult e1 e2 ⇒ AMult (optimize e1) (optimize e2)
end.
match e with
| ANum n ⇒ ANum n
| APlus e (ANum 0) ⇒ optimize e
| APlus (ANum 0) e ⇒ optimize e
| APlus e1 e2 ⇒ APlus (optimize e1) (optimize e2)
| AMinus e (ANum 0) ⇒ optimize e
| AMinus e1 e2 ⇒ AMinus (optimize e1) (optimize e2)
| AMult e1 e2 ⇒ AMult (optimize e1) (optimize e2)
end.
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.
(*! QuickChick optimize_correct_prop. *)
QuickChecking optimize_correct_prop +++ Passed 10000 tests (0 discards)
QuickChick Test Annotations
QuickChick (fun e ⇒ eval (optimize e) = eval e?).
Sections
(*! Section arithmetic_expressions *)
(*! Section optimizations *)(*! extends arithmetic_expressions *)
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 make[1]: Leaving directory '/home/lemonidas/sfdev/qc/_qc_stack-compiler.tmp' Checking Exp.optimize_correct_prop... +++ Passed 10000 tests (0 discards) ... etc ...
Mutation Testing
(*! *)
| APlus e1 e2 ⇒ (eval e1) + (eval e2)
(*!! Plus-copy-paste-error *)
(*! | APlus e1 e2 => (eval e1) + (eval e1) *)
(*! *)
(*!! Plus-copy-paste-error *)
(*! *)
| AMinus e (ANum 0) ⇒ optimize e
(*!! Minus-Reverse *)
(*!
| AMinus (ANum 0) e => optimize e
*)
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 make[1]: 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 make[1]: Entering directory '/home/lemonidas/sfdev/qc/_qc_stack-compiler.tmp' COQDEP VFILES COQC Exp.v COQC Stack.v COQC QuickChickTop.v make[1]: 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
A Low-Level Stack Machine
Inductive sinstr : Type :=
| SPush : nat → sinstr
| SPlus : sinstr
| SMinus : sinstr
| SMult : sinstr.
| SPush : nat → sinstr
| SPlus : sinstr
| SMinus : sinstr
| SMult : sinstr.
Then we describe how to execute the stack machine.
Fixpoint execute (stack : list nat) (prog : list sinstr) : list nat :=
match (prog, stack) with
| (nil, _ ) ⇒ stack
| (SPush n::prog', _ ) ⇒ execute (n::stack) prog'
| (SPlus::prog', m::n::stack') ⇒ execute ((m+n)::stack') prog'
| (SMinus::prog', m::n::stack') ⇒ execute ((m-n)::stack') prog'
| (SMult::prog', m::n::stack') ⇒ execute ((m×n)::stack') prog'
| (_::prog', _ ) ⇒ execute stack prog'
end.
Given the current stack (a list of natural numbers) and the
program to be executed, we will produce a resulting stack.
Now we can compile expressions to their corresponding
stack-instruction sequences.
- 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.
Fixpoint compile (e : exp) : list sinstr :=
match e with
| ANum n ⇒ [SPush n]
| APlus e1 e2 ⇒ compile e1 ++ compile e2 ++ [SPlus]
| AMinus e1 e2 ⇒ compile e1 ++ compile e2 ++ [SMinus]
| AMult e1 e2 ⇒ compile e1 ++ compile e2 ++ [SMult]
end.
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.
Definition compiles_correctly (e : exp) := (execute [] (compile e)) = [eval e]?.
(*! QuickChick compiles_correctly. *)
===>
QuickChecking compiles_correctly
AMinus (ANum 0) (ANum 1)
*** Failed after 3 tests and 2 shrinks. (0 discards)
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]
(*!! Wrong associativity *)
(*!
| APlus e1 e2 => compile e1 ++ compile e2 ++ SPlus
| AMinus e1 e2 => compile e1 ++ compile e2 ++ SMinus
| AMult e1 e2 => compile e1 ++ compile e2 ++ SMult
*)
end.
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 make[1]: 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 make[1]: Entering directory '/home/lemonidas/sfdev/qc/_qc_stack-compiler.tmp' COQDEP VFILES COQC Stack.v COQC QuickChickTop.v make[1]: 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 make[1]: Entering directory '/home/lemonidas/sfdev/qc/_qc_stack-compiler.tmp' COQDEP VFILES COQC Exp.v COQC Stack.v COQC QuickChickTop.v make[1]: 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 resultsWe 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
(* 2022-03-14 05:33:04 (UTC+00) *)