Module QCheck2.Gen
A generator is responsible for generating pseudo-random values and provide shrinks (smaller values) when a test fails.
type 'a sized= int -> 'a tRandom generator with a size bound.
Primitive generators
val unit : unit tThe unit generator.
Does not shrink.
val bool : bool tThe boolean generator.
Shrinks towards
false.
val int : int tGenerates integers uniformly.
Shrinks towards
0.
val pint : ?origin:int -> int tGenerates non-strictly positive integers uniformly (
0included).Shrinks towards
originif specified, otherwise towards0.
val small_nat : int tSmall positive integers (<
100,0included).Non-uniform: smaller numbers are more likely than bigger numbers.
Shrinks towards
0.- since
- 0.5.1
val nat : int tGenerates natural numbers (<
10_000).Non-uniform: smaller numbers are more likely than bigger numbers.
Shrinks towards
0.
val big_nat : int tGenerates natural numbers, possibly large (<
1_000_000).Non-uniform: smaller numbers are more likely than bigger numbers.
Shrinks towards
0.- since
- 0.10
val neg_int : int tGenerates non-strictly negative integers (
0included).Non-uniform: smaller numbers (in absolute value) are more likely than bigger numbers.
Shrinks towards
0.
val small_int : int tSmall UNSIGNED integers, for retrocompatibility.
Shrinks towards
0.- deprecated
use
small_nat.
val small_signed_int : int tSmall SIGNED integers, based on
small_nat.Non-uniform: smaller numbers (in absolute value) are more likely than bigger numbers.
Shrinks towards
0.- since
- 0.5.2
val small_int_corners : unit -> int tAs
small_int, but each newly created generator starts with a list of corner cases before falling back on random generation.
val ui32 : int32 tGenerates
int32values.Shrinks towards
0l.- deprecated
use
int32instead, the name is wrong, values are signed.
val ui64 : int64 tGenerates
int64values.Shrinks towards
0L.- deprecated
use
int64instead, the name is wrong, values are signed.
val float : float tGenerates floating point numbers.
Shrinks towards
0..
val pfloat : float tGenerates positive floating point numbers (
0.included).Shrinks towards
0..
val nfloat : float tGenerates negative floating point numbers. (
-0.included).Shrinks towards
-0..
val char : char tGenerates characters in the
0..255range.Shrinks towards
'a'.
val printable : char tGenerates printable characters.
The exhaustive list of character codes is:
32to126, inclusive'\n'
Shrinks towards
'a'.
val numeral : char tGenerates numeral characters
'0'..'9'.Shrinks towards
'0'.
val string_size : ?gen:char t -> int t -> string tBuilds 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.
val string : string tBuilds a string generator. String size is generated by
nat. The default character generator ischar. See alsostring_ofandstring_printablefor versions with custom char generator.Shrinks on the number of characters first, then on the characters.
val string_of : char t -> string tBuilds a string generator using the given character generator.
Shrinks on the number of characters first, then on the characters.
- since
- 0.11
val string_printable : string tBuilds a string generator using the
printablecharacter generator.Shrinks on the number of characters first, then on the characters.
- since
- 0.11
val small_string : ?gen:char t -> string tBuilds a string generator, length is
small_nat. Accepts an optional character generator (the default ischar).Shrinks on the number of characters first, then on the characters.
val pure : 'a -> 'a tpure acreates a generator that always returnsa.Does not shrink.
- since
- 0.8
val make_primitive : gen:(Stdlib.Random.State.t -> 'a) -> shrink:('a -> 'a Stdlib.Seq.t) -> 'a tmake_primitive ~gen ~shrinkcreates a generator from a functiongenthat creates a random value (this function must only use the givenRandom.State.t for randomness) and a functionshrinkthat, given a valuea, 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.
shrinkmust obey the following rules (for your own definition of "small"):shrink a = Seq.emptywhenais the smallest possible valueshrink amust return values strictly smaller thana, ideally from smallest to largest (for faster shrinking)let rec loop a = match shrink a () with | Nil -> () | Cons (smaller_a, _) -> loop smaller_amust end for all valuesaof 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.tmay 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 tadd_shrink_invariant f genreturns a generator similar togenexcept all shrinks satisfyf. This way it's easy to preserve invariants that are enforced by generators, when shrinking values- since
- 0.8
- deprecated
is this function still useful? I feel like it is either useless (invariants should already be part of the shrinking logic, not be added later) or a special, incomplete case of
Gen.tbeing an Alternative (not implemented yet). For now we keep it and wait for users feedback (hence deprecation to raise attention).
Ranges
val int_bound : int -> int tUniform integer generator producing integers within
0..bound.Shrinks towards
0.- raises Invalid_argument
if the argument is negative.
val int_range : ?origin:int -> int -> int -> int tint_range ?origin low highis an uniform integer generator producing integers withinlow..high(inclusive).Shrinks towards
originif specified, otherwise towards0(but always stays within the range).Examples:
int_range ~origin:6 (-5) 15will shrink towards6int_range (-5) 15will shrink towards0int_range 8 20will shrink towards8(closest to0within range)int_range (-20) (-8)will shrink towards-8(closest to0within range)
- raises Invalid_argument
if any of the following holds:
low > highorigin < loworigin > high
val (--) : int -> int -> int ta -- bis an alias forint_range a b. Seeint_rangefor more information.
val float_bound_inclusive : ?origin:float -> float -> float tfloat_bound_inclusive ?origin boundreturns a random floating-point number between0.andbound(inclusive). Ifboundis negative, the result is negative or zero. Ifboundis0., the result is0..Shrinks towards
originif given, otherwise towards0..- since
- 0.11
val float_bound_exclusive : ?origin:float -> float -> float tfloat_bound_exclusive origin boundreturns a random floating-point number between0.andbound(exclusive). Ifboundis negative, the result is negative or zero.Shrinks towards
originif given, otherwise towards0..- raises Invalid_argument
if
boundis0..
- since
- 0.11
val float_range : ?origin:float -> float -> float -> float tfloat_range ?origin low highgenerates floating-point numbers withinlowandhigh(inclusive).Shrinks towards
originif specified, otherwise towards0.(but always stays within the range).Examples:
float_range ~origin:6.2 (-5.8) 15.1will shrink towards6.2float_range (-5.8) 15.1will shrink towards0.float_range 8.5 20.1will shrink towards8.5(closest to0.within range)float_range (-20.1) (-8.5)will shrink towards-8.5(closest to0.within range)
- raises Invalid_argument
if any of the following holds:
low > highhigh -. low > max_floatorigin < loworigin > high
- since
- 0.11
val (--.) : float -> float -> float ta --. bis an alias forfloat_range ~origin:a a b. Seefloat_rangefor more information.- since
- 0.11
val char_range : ?origin:char -> char -> char -> char tchar_range ?origin low highgenerates chars betweenlowandhigh, inclusive. Example:char_range 'a' 'z'for all lower case ASCII letters.Shrinks towards
originif specified, otherwise towardslow.- raises Invalid_argument
if
low > high.
- since
- 0.13
Choosing elements
val oneof : 'a t list -> 'a toneof lconstructs a generator that selects among the given list of generatorsl.Shrinks towards the first generator of the list.
- raises Invalid_argument
or Failure if
lis empty
val oneofl : 'a list -> 'a toneofl lconstructs a generator that selects among the given list of valuesl.Shrinks towards the first element of the list.
- raises Invalid_argument
or Failure if
lis empty
val oneofa : 'a array -> 'a toneofa aconstructs a generator that selects among the given array of valuesa.Shrinks towards the first element of the array.
- raises Invalid_argument
or Failure if
lis empty
val frequency : (int * 'a t) list -> 'a tConstructs 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 tConstructs 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 tConstructs 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 tReturns a copy of the array with its elements shuffled.
val shuffle_l : 'a list -> 'a list tCreates a generator of shuffled lists.
val shuffle_w_l : (int * 'a) list -> 'a list tCreates 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".- since
- 0.11
Corner cases
Lists, arrays and options
val list : 'a t -> 'a list tBuilds 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 tGenerates lists of small size (see
small_nat).Shrinks on the number of elements first, then on elements.
- since
- 0.5.3
val list_size : int t -> 'a t -> 'a list tBuilds 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 tlist_repeat i gbuilds a list generator from exactlyielements generated byg.Shrinks on elements only.
val array : 'a t -> 'a array tBuilds 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 tBuilds 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 tGenerates arrays of small size (see
small_nat).Shrinks on the number of elements first, then on elements.
- since
- 0.10
Combining generators
Convert a structure of generator to a generator of structure
val flatten_l : 'a t list -> 'a list tGenerate a list of elements from individual generators.
Shrinks on the elements of the list, in the list order.
- since
- 0.13
val flatten_a : 'a t array -> 'a array tGenerate an array of elements from individual generators.
Shrinks on the elements of the array, in the array order.
- since
- 0.13
val flatten_opt : 'a t option -> 'a option tGenerate an option from an optional generator.
Shrinks towards
Nonethen shrinks on the value.- since
- 0.13
Influencing the size of generated values
Recursive data structures
val fix : (('a -> 'b t) -> 'a -> 'b t) -> 'a -> 'b tParametrized 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
'aasint). The passed size-parameter should decrease to ensure termination.
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_printableApplicatives 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 tmap f gentransforms a generatorgenby applyingfto each generated element.Shrinks towards the shrinks of
genwithfapplied to them.
val (>|=) : 'a t -> ('a -> 'b) -> 'b tAn infix synonym for
map. Note the order of arguments is reversed (usually more convenient for composing).
val map2 : ('a -> 'b -> 'c) -> 'a t -> 'b t -> 'c tmap2 f gen1 gen2transforms two generatorsgen1andgen2by applyingfto each pair of generated elements.Shrinks on
gen1and thengen2.
val map3 : ('a -> 'b -> 'c -> 'd) -> 'a t -> 'b t -> 'c t -> 'd tmap3 f gen1 gen2 gen3transforms three generatorsgen1,gen2, andgen3by applyingfto each triple of generated elements.Shrinks on
gen1, thengen2, and thengen3.
val ap : ('a -> 'b) t -> 'a t -> 'b tap fgen gencomposes a function generator and an argument generator into a result generator.Shrinks on
fgenand thengen.
val bind : 'a t -> ('a -> 'b t) -> 'b tbind gen ffirst generates a value of type'awithgenand then passes it tofto generate a value of type'b. This is typically useful when a generator depends on the value generated by another generator.Shrinks on
genand then on the resulting generator.
val let+ : 'a t -> ('a -> 'b) -> 'b tBinding 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) tBinding 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 tBinding 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) tBinding 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.
val generate : ?rand:Stdlib.Random.State.t -> n:int -> 'a t -> 'a listgenerate ~n gengeneratesnvalues usinggen(shrinks are discarded).
val generate1 : ?rand:Stdlib.Random.State.t -> 'a t -> 'agenerate1 gengenerates one instance ofgen(shrinks are discarded).