The library takes inspiration from Haskell's QuickCheck library. The rough idea is that the programmer describes invariants that values of a certain type need to satisfy ("properties"), as functions from this type to bool. She also needs to describe how to generate random values of the type, so that the property is tried and checked on a number of random instances.
This explains the organization of this module:
'a -> bool combined with an 'a arbitrary that is used to generate
the test cases for this property. Optional parameters
allow to specify the random generator state, number of instances to generate
and test, etc.Examples:
let test =
QCheck.(Test.make ~count:1000
(list int) (fun l -> List.rev (List.rev l) = l));;
QCheck.Test.check_exn test;;let test = QCheck.(
Test.make
~count:10_000 ~max_fail:3
(list small_nat)
(fun l -> l = List.sort compare l));;
QCheck.Test.check_exn test;;type tree = Leaf of int | Node of tree * tree
let leaf x = Leaf x
let node x y = Node (x,y)
let g = QCheck.Gen.(sized @@ fix
(fun self n -> match n with
| 0 -> map leaf nat
| n ->
frequency
[1, map leaf nat;
2, map2 node (self (n/2)) (self (n/2))]
))
Gen.generate ~n:20 g;;More complex and powerful combinators can be found in Gabriel Scherer's Generator module. Its documentation can be found here.
val (==>) : bool ‑> bool ‑> boolb1 ==> b2 is the logical implication b1 => b2
ie not b1 || b2 (except that it is strict and will interact
better with Test.check_exn and the likes, because they will know
the precondition was not satisfied.).
WARNING: this function should only be used in a property
(see Test.make), because it raises a special exception in case of
failure of the first argument, to distinguish between failed test
and failed precondition. Because of OCaml's evaluation order,
both b1 and b2 are always evaluated; if b2 should only be
evaluated when b1 holds, see assume.
val assume : bool ‑> unitassume cond checks the precondition cond, and does nothing
if cond=true. If cond=false, it interrupts the current test.
WARNING This function, like (==>), should only be used in a test, not outside. Example:
Test.make (list int) (fun l ->
assume (l <> []);
List.hd l :: List.tl l = l)val assume_fail : unit ‑> 'aassume_fail () is like assume false, but can take any type
since we know it always fails (like assert false).
This is useful to ignore some branches in if or match.
Example:
Test.make (list int) (function
| [] -> assume_fail ()
| _::_ as l -> List.hd l :: List.tl l = l)Observables are usable as arguments for random functions. The random function will observe its arguments in a way that is determined from the observable instance.
Inspired from https://blogs.janestreet.com/quickcheck-for-core/ and Koen Claessen's "Shrinking and Showing functions".
module Observable : sig ... endA value of type 'a arbitrary glues together a random generator,
and optional functions for shrinking, printing, computing the size,
etc. It is the "normal" way of describing how to generate
values of a given type, to be then used in tests (see Test).
type 'a stat = string * ('a ‑> int)A statistic on a distribution of values of type 'a.
The function MUST return a positive integer.
type 'a arbitrary = private {gen : 'a Gen.t; | |
print : ('a ‑> string) option; | (** print values *) |
small : ('a ‑> int) option; | (** size of example *) |
shrink : 'a Shrink.t option; | (** shrink to smaller examples *) |
collect : ('a ‑> string) option; | (** map value to tag, and group by tag *) |
stats : 'a stat list; | (** statistics to collect and print *) |
}A value of type 'a arbitrary is an object with a method for generating random
values of type 'a, and additional methods to compute the size of values,
print them, and possibly shrink them into smaller counter-examples.
NOTE the collect field is unstable and might be removed, or moved into Test.
Made private
val make : ?print:'a Print.t ‑> ?small:('a ‑> int) ‑> ?shrink:'a Shrink.t ‑> ?collect:('a ‑> string) ‑> ?stats:'a stat list ‑> 'a Gen.t ‑> 'a arbitraryBuilder for arbitrary. Default is to only have a generator, but other arguments can be added.
A test is a universal property of type foo -> bool for some type foo,
with an object of type foo arbitrary used to generate, print, etc. values
of type foo.
See Test.make to build a test, and Test.check_exn to run one test simply. For more serious testing, it is better to create a testsuite and use QCheck_runner.
module Test : sig ... endThe infrastructure used to find counter-examples to properties can also be used to find data satisfying a predicate, within a property being tested.
See https://github.com/c-cube/qcheck/issues/31
find_example ~f gen uses gen to generate some values of type 'a,
and checks them against f. If such a value is found, it is returned.
Otherwise an exception is raised.
NOTE this should only be used from within a property in Test.make.
count tries.val find_example_gen : ?rand:Random.State.t ‑> ?name:string ‑> ?count:int ‑> f:('a ‑> bool) ‑> 'a Gen.t ‑> 'aToplevel version of find_example.
find_example_gen ~f arb ~n is roughly the same as
Gen.generate1 (find_example ~f arb |> gen).
count tries.Choose among the given list of generators. The list must not be empty; if it is Invalid_argument is raised.
val int_range : int ‑> int ‑> int arbitraryint_range a b is uniform between a and b included. b must be
larger than a.
val small_int : int arbitrarySmall unsigned integers. See Gen.small_int.
val small_int_corners : unit ‑> int arbitraryAs small_int, but each newly created generator starts with
a list of corner cases before falling back on random generation.
val neg_int : int arbitraryNegative int generator (0 included, see Gen.neg_int).
The distribution is similar to that of
small_int, not of pos_int.
val string : string arbitraryGenerates strings with a distribution of length of small_nat
and distribution of characters of char.
val printable_string : string arbitraryGenerates strings with a distribution of length of small_nat
and distribution of characters of printable_char.
val small_printable_string : string arbitraryval numeral_string : string arbitraryGenerates strings with a distribution of length of small_nat
and distribution of characters of numeral_char.
Combines two generators into a generator of pairs. Order of elements can matter (w.r.t shrinking, see Shrink.pair)
Combines three generators into a generator of 3-tuples. Order matters for shrinking, see Shrink.pair and the likes
val quad : 'a arbitrary ‑> 'b arbitrary ‑> 'c arbitrary ‑> 'd arbitrary ‑> ('a * 'b * 'c * 'd) arbitraryCombines four generators into a generator of 4-tuples. Order matters for shrinking, see Shrink.pair and the likes
Generator of functions of arity 1. The functions are always pure and total functions:
A function packed with the data required to print/shrink it. See Fn to see how to apply, print, etc. such a function.
One can also directly pattern match on it to obtain the executable function.
For example:
QCheck.Test.make
QCheck.(pair (fun1 Observable.int bool) (small_list int))
(fun (Fun (_,f), l) -> l=(List.rev_map f l |> List.rev l))val fun1 : 'a Observable.t ‑> 'b arbitrary ‑> ('a ‑> 'b) fun_ arbitraryfun1 o ret makes random functions that take an argument observable
via o and map to random values generated from ret.
To write functions with multiple arguments, it's better to use Tuple
or Observable.pair rather than applying fun_ several times
(shrinking will be faster).
module Tuple : sig ... endfun_nary makes random n-ary functions.
Example:
let module O = Observable in
fun_nary Tuple.(O.int @-> O.float @-> O.string @-> o_nil) bool)val fun2 : 'a Observable.t ‑> 'b Observable.t ‑> 'c arbitrary ‑> ('a ‑> 'b ‑> 'c) fun_ arbitraryval fun3 : 'a Observable.t ‑> 'b Observable.t ‑> 'c Observable.t ‑> 'd arbitrary ‑> ('a ‑> 'b ‑> 'c ‑> 'd) fun_ arbitraryval fun4 : 'a Observable.t ‑> 'b Observable.t ‑> 'c Observable.t ‑> 'd Observable.t ‑> 'e arbitrary ‑> ('a ‑> 'b ‑> 'c ‑> 'd ‑> 'e) fun_ arbitraryval frequency : ?print:'a Print.t ‑> ?small:('a ‑> int) ‑> ?shrink:'a Shrink.t ‑> ?collect:('a ‑> string) ‑> (int * 'a arbitrary) list ‑> 'a arbitrarySimilar to oneof but with frequencies.
Same as oneofl, but each element is paired with its frequency in the probability distribution (the higher, the more likely).
Same as frequencyl, but with an array.
map f a returns a new arbitrary instance that generates values using
a#gen and then transforms them through f.
'a so that the printer,
shrinker, etc. of a can be used. We assume f is monotonic in
this case (that is, smaller inputs are transformed into smaller outputs).Specialization of map when the transformation preserves the type, which
makes shrinker, printer, etc. still relevant.
val map_keep_input : ?print:'b Print.t ‑> ?small:('b ‑> int) ‑> ('a ‑> 'b) ‑> 'a arbitrary ‑> ('a * 'b) arbitrarymap_keep_input f a generates random values from a, and maps them into
values of type 'b using the function f, but it also keeps the
original value.
For shrinking, it is assumed that f is monotonic and that smaller input
values will map into smaller values.
f's output.