A generator is responsible for generating pseudo-random values and provide shrinks (smaller values) when a test fails.
This module provides some of the most important features of QCheck:
A random generator for values of type 'a
.
type 'a sized = int -> 'a t
Random generator with a size bound.
Primitive generators
The unit generator.
Does not shrink.
The boolean generator.
Shrinks towards false
.
Generates integers uniformly.
Shrinks towards 0
.
val pint : ?origin:int -> int t
Generates non-strictly positive integers uniformly (0
included).
Shrinks towards origin
if specified, otherwise towards 0
.
Small positive integers (< 100
, 0
included).
Non-uniform: smaller numbers are more likely than bigger numbers.
Shrinks towards 0
.
Generates natural numbers (< 10_000
).
Non-uniform: smaller numbers are more likely than bigger numbers.
Shrinks towards 0
.
Generates natural numbers, possibly large (< 1_000_000
).
Non-uniform: smaller numbers are more likely than bigger numbers.
Shrinks towards 0
.
Generates non-strictly negative integers (0
included).
Non-uniform: smaller numbers (in absolute value) are more likely than bigger numbers.
Shrinks towards 0
.
Small UNSIGNED integers, for retrocompatibility.
Shrinks towards 0
.
val small_signed_int : int t
Small SIGNED integers, based on small_nat
.
Non-uniform: smaller numbers (in absolute value) are more likely than bigger numbers.
Shrinks towards 0
.
val small_int_corners : unit -> int t
As small_int
, but each newly created generator starts with a list of corner cases before falling back on random generation.
Generates uniform int32
values.
Shrinks towards 0l
.
Generates int32
values.
Shrinks towards 0l
.
Generates uniform int64
values.
Shrinks towards 0L
.
Generates int64
values.
Shrinks towards 0L
.
Generates floating point numbers.
Shrinks towards 0.
.
Generates positive floating point numbers (0.
included).
Shrinks towards 0.
.
Generates negative floating point numbers. (-0.
included).
Shrinks towards -0.
.
Generates characters in the 0..255
range.
Shrinks towards 'a'
.
Generates printable characters.
The exhaustive list of character codes is:
Shrinks towards 'a'
or lower character codes.
Generates numeral characters '0'..'9'
.
Shrinks towards '0'
.
val string_size : ?gen:char t -> int t -> string t
Builds a string generator from a (non-negative) size generator. Accepts an optional character generator (the default is char
).
Shrinks on the number of characters first, then on the characters.
Builds a string generator. String size is generated by nat
. The default character generator is char
. See also string_of
and string_printable
for versions with custom char generator.
Shrinks on the number of characters first, then on the characters.
val string_of : char t -> string t
Builds a string generator using the given character generator.
Shrinks on the number of characters first, then on the characters.
val string_printable : string t
Builds a string generator using the printable
character generator.
Shrinks on the number of characters first, then on the characters.
val small_string : ?gen:char t -> string t
Builds a string generator, length is small_nat
. Accepts an optional character generator (the default is char
).
Shrinks on the number of characters first, then on the characters.
pure a
creates a generator that always returns a
.
Does not shrink.
make_primitive ~gen ~shrink
creates a generator from a function gen
that creates a random value (this function must only use the given Random.State.t
for randomness) and a function shrink
that, given a value a
, returns a lazy list of "smaller" values (used when a test fails).
This lower-level function is meant to build generators for "primitive" types that can neither be built with other primitive generators nor through composition, or to have more control on the shrinking steps.
shrink
must obey the following rules (for your own definition of "small"):
shrink a = Seq.empty
when a
is the smallest possible valueshrink a
must return values strictly smaller than a
, ideally from smallest to largest (for faster shrinking)let rec loop a = match shrink a () with | Nil -> () | Cons (smaller_a, _) -> loop smaller_a
must end for all values a
of type 'a
(i.e. there must not be an infinite number of shrinking steps).
⚠️ This is an unstable API as it partially exposes the implementation. In particular, the type of Random.State.t
may very well change in a future version, e.g. if QCheck switches to another randomness library.
val add_shrink_invariant : ('a -> bool) -> 'a t -> 'a t
add_shrink_invariant f gen
returns a generator similar to gen
except all shrinks satisfy f
. This way it's easy to preserve invariants that are enforced by generators, when shrinking values
Ranges
val int_bound : int -> int t
Uniform integer generator producing integers within 0..bound
.
Shrinks towards 0
.
val int_range : ?origin:int -> int -> int -> int t
int_range ?origin low high
is an uniform integer generator producing integers within low..high
(inclusive).
Shrinks towards origin
if specified, otherwise towards 0
(but always stays within the range).
Examples:
int_range ~origin:6 (-5) 15
will shrink towards 6
int_range (-5) 15
will shrink towards 0
int_range 8 20
will shrink towards 8
(closest to 0
within range)int_range (-20) (-8)
will shrink towards -8
(closest to 0
within range)
val (--) : int -> int -> int t
a -- b
is an alias for int_range a b
. See int_range
for more information.
val float_bound_inclusive : ?origin:float -> float -> float t
float_bound_inclusive ?origin bound
returns a random floating-point number between 0.
and bound
(inclusive). If bound
is negative, the result is negative or zero. If bound
is 0.
, the result is 0.
.
Shrinks towards origin
if given, otherwise towards 0.
.
val float_bound_exclusive : ?origin:float -> float -> float t
float_bound_exclusive origin bound
returns a random floating-point number between 0.
and bound
(exclusive). If bound
is negative, the result is negative or zero.
Shrinks towards origin
if given, otherwise towards 0.
.
val float_range : ?origin:float -> float -> float -> float t
float_range ?origin low high
generates floating-point numbers within low
and high
(inclusive).
Shrinks towards origin
if specified, otherwise towards 0.
(but always stays within the range).
Examples:
float_range ~origin:6.2 (-5.8) 15.1
will shrink towards 6.2
float_range (-5.8) 15.1
will shrink towards 0.
float_range 8.5 20.1
will shrink towards 8.5
(closest to 0.
within range)float_range (-20.1) (-8.5)
will shrink towards -8.5
(closest to 0.
within range)
val (--.) : float -> float -> float t
a --. b
is an alias for float_range ~origin:a a b
. See float_range
for more information.
val char_range : ?origin:char -> char -> char -> char t
char_range ?origin low high
generates chars between low
and high
, inclusive. Example: char_range 'a' 'z'
for all lower case ASCII letters.
Shrinks towards origin
if specified, otherwise towards low
.
Choosing elements
val oneof : 'a t list -> 'a t
oneof l
constructs a generator that selects among the given list of generators l
.
Shrinks towards the first generator of the list.
val oneofl : 'a list -> 'a t
oneofl l
constructs a generator that selects among the given list of values l
.
Shrinks towards the first element of the list.
val oneofa : 'a array -> 'a t
oneofa a
constructs a generator that selects among the given array of values a
.
Shrinks towards the first element of the array.
val frequency : (int * 'a t) list -> 'a t
Constructs a generator that selects among a given list of generators. Each of the given generators are chosen based on a positive integer weight.
Shrinks towards the first element of the list.
val frequencyl : (int * 'a) list -> 'a t
Constructs a generator that selects among a given list of values. Each of the given values are chosen based on a positive integer weight.
Shrinks towards the first element of the list.
val frequencya : (int * 'a) array -> 'a t
Constructs a generator that selects among a given array of values. Each of the array entries are chosen based on a positive integer weight.
Shrinks towards the first element of the array.
Shuffling elements
val shuffle_a : 'a array -> 'a array t
Returns a copy of the array with its elements shuffled.
val shuffle_l : 'a list -> 'a list t
Creates a generator of shuffled lists.
val shuffle_w_l : (int * 'a) list -> 'a list t
Creates a generator of weighted shuffled lists. A given list is shuffled on each generation according to the weights of its elements. An element with a larger weight is more likely to be at the front of the list than an element with a smaller weight. If we want to pick random elements from the (head of) list but need to prioritize some elements over others, this generator can be useful.
Example: given a weighted list [1, "one"; 5, "five"; 10, "ten"]
, the generator is more likely to generate ["ten"; "five"; "one"]
or ["five"; "ten"; "one"]
than ["one"; "ten"; "five"]
because "ten" and "five" have larger weights than "one".
Corner cases
val graft_corners : 'a t -> 'a list -> unit -> 'a t
graft_corners gen l ()
makes a new generator that enumerates the corner cases in l
and then behaves like g
.
Does not shrink if the test fails on a grafted value. Shrinks towards gen
otherwise.
val int_pos_corners : int list
Non-negative corner cases for int.
val int_corners : int list
All corner cases for int.
Lists, arrays and options
val list : 'a t -> 'a list t
Builds a list generator from an element generator. List size is generated by nat
.
Shrinks on the number of elements first, then on elements.
val small_list : 'a t -> 'a list t
Generates lists of small size (see small_nat
).
Shrinks on the number of elements first, then on elements.
val list_size : int t -> 'a t -> 'a list t
Builds a list generator from a (non-negative) size generator and an element generator.
Shrinks on the number of elements first, then on elements.
val list_repeat : int -> 'a t -> 'a list t
list_repeat i g
builds a list generator from exactly i
elements generated by g
.
Shrinks on elements only.
val array : 'a t -> 'a array t
Builds an array generator from an element generator. Array size is generated by nat
.
Shrinks on the number of elements first, then on elements.
val array_size : int t -> 'a t -> 'a array t
Builds an array generator from a (non-negative) size generator and an element generator.
Shrinks on the number of elements first, then on elements.
val small_array : 'a t -> 'a array t
Generates arrays of small size (see small_nat
).
Shrinks on the number of elements first, then on elements.
val array_repeat : int -> 'a t -> 'a array t
array_repeat i g
builds an array generator from exactly i
elements generated by g
.
Shrinks on elements only.
val option : ?ratio:float -> 'a t -> 'a option t
option gen
is an option
generator that uses gen
when generating Some
values.
Shrinks towards None
then towards shrinks of gen
.
val opt : ?ratio:float -> 'a t -> 'a option t
opt
is an alias of option
for backward compatibility.
Combining generators
val pair : 'a t -> 'b t -> ('a * 'b) t
pair gen1 gen2
generates pairs.
Shrinks on gen1
and then gen2
.
val triple : 'a t -> 'b t -> 'c t -> ('a * 'b * 'c) t
triple gen1 gen2 gen3
generates triples.
Shrinks on gen1
, then gen2
and then gen3
.
val quad : 'a t -> 'b t -> 'c t -> 'd t -> ('a * 'b * 'c * 'd) t
quad gen1 gen2 gen3 gen4
generates quadruples.
Shrinks on gen1
, then gen2
, then gen3
and then gen4
.
Tuple of generators
Shrinks on gen1
, then gen2
, then ...
val tup2 : 'a t -> 'b t -> ('a * 'b) t
val tup3 : 'a t -> 'b t -> 'c t -> ('a * 'b * 'c) t
val tup4 : 'a t -> 'b t -> 'c t -> 'd t -> ('a * 'b * 'c * 'd) t
val tup5 : 'a t -> 'b t -> 'c t -> 'd t -> 'e t -> ('a * 'b * 'c * 'd * 'e) t
val tup6 :
'a t ->
'b t ->
'c t ->
'd t ->
'e t ->
'f t ->
('a * 'b * 'c * 'd * 'e * 'f) t
val tup7 :
'a t ->
'b t ->
'c t ->
'd t ->
'e t ->
'f t ->
'g t ->
('a * 'b * 'c * 'd * 'e * 'f * 'g) t
val tup8 :
'a t ->
'b t ->
'c t ->
'd t ->
'e t ->
'f t ->
'g t ->
'h t ->
('a * 'b * 'c * 'd * 'e * 'f * 'g * 'h) t
val tup9 :
'a t ->
'b t ->
'c t ->
'd t ->
'e t ->
'f t ->
'g t ->
'h t ->
'i t ->
('a * 'b * 'c * 'd * 'e * 'f * 'g * 'h * 'i) t
Convert a structure of generator to a generator of structure
val flatten_l : 'a t list -> 'a list t
Generate a list of elements from individual generators.
Shrinks on the elements of the list, in the list order.
val flatten_a : 'a t array -> 'a array t
Generate an array of elements from individual generators.
Shrinks on the elements of the array, in the array order.
val flatten_opt : 'a t option -> 'a option t
Generate an option from an optional generator.
Shrinks towards None
then shrinks on the value.
Generate a result from Ok gen
, an error from Error e
.
Shrinks on gen
if Ok gen
. Does not shrink if Error e
.
val join : 'a t t -> 'a t
Collapses a generator of generators to a generator.
Shrinks on the generated generators.
Influencing the size of generated values
Creates a generator from a size-bounded generator by first generating a size using nat
and passing the result to the size-bounded generator.
Shrinks on the size first, then on the generator.
val sized_size : int t -> 'a sized -> 'a t
Creates a generator from a size-bounded generator by first generating a size using the integer generator and passing the result to the size-bounded generator.
Shrinks on the size first, then on the generator.
Recursive data structures
val fix : (('a -> 'b t) -> 'a -> 'b t) -> 'a -> 'b t
Parametrized fixpoint combinator for generating recursive values.
The fixpoint is parametrized over an generator state 'a
, and the fixpoint computation may change the value of this state in the recursive calls.
In particular, this can be used for size-bounded generators (with 'a
as int
). The passed size-parameter should decrease to ensure termination.
Example:
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))]
))
fix f
shrinks on the generators returned by f
.
val delay : (unit -> 'a t) -> 'a t
Delay execution of some code until the generator is actually called. This can be used to manually implement recursion or control flow in a generator.
Composing generators
QCheck generators compose well: it means one can easily craft generators for new values or types from existing generators.
Part of the following documentation is greatly inspired by Gabriel Scherer's excellent Generator module documentation.
Functor
Gen.t
is a functor (in the Haskell sense of "mappable"): it has a map
function to transform a generator of 'a
into a generator of 'b
, given a simple function 'a -> 'b
.
let even_gen : int Gen.t = Gen.map (fun n -> n * 2) Gen.int
let odd_gen : int Gen.t = Gen.map (fun n -> n * 2 + 1) Gen.int
let lower_case_string_gen : string Gen.t = Gen.map String.lowercase Gen.string_printable
type foo = Foo of string * int
let foo_gen : foo Gen.t =
Gen.map (fun (s, n) -> Foo (s, n)) Gen.(pair string_printable int)
Applicative
Gen.t
is applicative: it has a map2
function to apply a function of 2 (or more) arguments to 2 (or more) generators.
Another equivalent way to look at it is that it has an ap
function to apply a generator of functions to a generator of values. While at first sight this may look almost useless, it actually permits a nice syntax (using the operator alias <*>
) for functions of any number of arguments.
(* Notice that this looks suspiciously like the [foo] example above:
this is no coincidence! [pair] is a special case of [map2] where
the function wraps arguments in a tuple. *)
type foo = Foo of string * int
let foo_gen : foo Gen.t =
Gen.map2 (fun s n -> Foo (s, n)) Gen.string_printable Gen.int
let string_prefixed_with_keyword_gen : string Gen.t =
Gen.map2 (fun prefix s -> prefix ^ s)
(Gen.oneofl ["foo"; "bar"; "baz"])
Gen.string_printable
Applicatives are useful when you need several random values to build a new generator, and the values are unrelated. A good rule of thumb is: if the values could be generated in parallel, then you can use an applicative function to combine those generators.
Note that while map2
and map3
are provided, you can use functions with more than 3 arguments (and that is where the <*>
operator alias really shines):
val complex_function : bool -> string -> int -> string -> int64 -> some_big_type
(* Verbose version, using map3 and ap *)
let big_type_gen : some_big_type Gen.t = Gen.(
ap (
ap (
map3 complex_function
bool
string_printable
int)
string_printable)
int64)
(* Sleeker syntax, using operators aliases for map and ap *)
let big_type_gen : some_big_type Gen.t = Gen.(
complex_function
<$> bool
<*> string_printable
<*> int
<*> string_printable
<*> int64)
Monad
Gen.t
is a monad: it has a bind
function to return a generator (not a value) based on another generated value.
As an example, imagine you want to create a generator of (int, string) result
that is an Ok
90% of the time and an Error
10% of the time. You can generate a number between 0 and 9 and return a generator of int
(wrapped in an Ok
using map
) if the generated number is lower than 9, otherwise return a generator of string
(wrapped in an Error
using map
):
let int_string_result : (int, string) result Gen.t = Gen.(
bind (int_range 0 9) (fun n ->
if n < 9
then map Result.ok int
else map Result.error string_printable))
(* Alternative syntax with operators *)
let int_string_result : (int, string) result Gen.t = Gen.(
int_range 0 9 >>= fun n ->
if n < 9
then int >|= Result.ok
else string_printable >|= Result.error)
(* Another allternative syntax with OCaml 4.08+ binding operators *)
let int_string_result : (int, string) result Gen.t = Gen.(
let* n = int_range 0 9 in
if n < 9
then int >|= Result.ok
else string_printable >|= Result.error)
Note that this particular use case can be simplified by using frequency
:
let int_string_result : (int, string) result Gen.t = Gen.(
frequency [
(9, int >|= Result.ok);
(1, string_printable >|= Result.error)
])
val map : ('a -> 'b) -> 'a t -> 'b t
map f gen
transforms a generator gen
by applying f
to each generated element.
Shrinks towards the shrinks of gen
with f
applied to them.
val (>|=) : 'a t -> ('a -> 'b) -> 'b t
An infix synonym for map
. Note the order of arguments is reversed (usually more convenient for composing).
val (<$>) : ('a -> 'b) -> 'a t -> 'b t
val map2 : ('a -> 'b -> 'c) -> 'a t -> 'b t -> 'c t
map2 f gen1 gen2
transforms two generators gen1
and gen2
by applying f
to each pair of generated elements.
Shrinks on gen1
and then gen2
.
val map3 : ('a -> 'b -> 'c -> 'd) -> 'a t -> 'b t -> 'c t -> 'd t
map3 f gen1 gen2 gen3
transforms three generators gen1
, gen2
, and gen3
by applying f
to each triple of generated elements.
Shrinks on gen1
, then gen2
, and then gen3
.
val ap : ('a -> 'b) t -> 'a t -> 'b t
ap fgen gen
composes a function generator and an argument generator into a result generator.
Shrinks on fgen
and then gen
.
val (<*>) : ('a -> 'b) t -> 'a t -> 'b t
val bind : 'a t -> ('a -> 'b t) -> 'b t
bind gen f
first generates a value of type 'a
with gen
and then passes it to f
to generate a value of type 'b
. This is typically useful when a generator depends on the value generated by another generator.
Shrinks on gen
and then on the resulting generator.
val (>>=) : 'a t -> ('a -> 'b t) -> 'b t
val let+ : 'a t -> ('a -> 'b) -> 'b t
Binding operator alias for map
.
Example:
let+ n = int_range 0 10 in
string_of_int n
(* is equivalent to *)
map (fun n -> string_of_int n) (int_range 0 10)
val and+ : 'a t -> 'b t -> ('a * 'b) t
Binding operator alias for pair
.
Example:
let+ n = int_range 0 10
and+ b = bool in
if b then string_of_int n else "Not a number"
(* is equivalent to *)
map
(fun (n, b) -> if b then string_of_int n else "Not a number")
(pair (int_range 0 10) bool)
val let* : 'a t -> ('a -> 'b t) -> 'b t
Binding operator alias for bind
.
Example:
let* n = int_range 0 9 in
if n < 9
then int >|= Result.ok
else string_printable >|= Result.error
(* is equivalent to *)
bind (int_range 0 9) (fun n ->
if n < 9
then map Result.ok int
else map Result.error string_printable)
val and* : 'a t -> 'b t -> ('a * 'b) t
Binding operator alias for pair
.
Example:
let* n = int_range 0 9
and* b = bool in
if n < 9 then int >|= Result.ok
else if b then pure (Error "Some specific error")
else string_printable >|= Result.error
(* is equivalent to *)
bind (pair (int_range 0 9) bool) (fun (n, b) ->
if n < 9 then map Result.ok int
else if b then pure (Error "Some specific error")
else map Result.error string_printable)
Debug generators
These functions should not be used in tests: they are provided for convenience to debug/investigate what values and shrinks a generator produces.
generate ~n gen
generates n
values using gen
(shrinks are discarded).
generate1 gen
generates one instance of gen
(shrinks are discarded).
generate_tree ?rand gen
generates a random value and its shrinks using gen
.