From c5c73c3c1533da82d36f6458c88d2d211f25c2a7 Mon Sep 17 00:00:00 2001 From: Jan Midtgaard Date: Mon, 9 Dec 2024 12:39:17 +0100 Subject: [PATCH 01/15] Rework QCheck introduction a bit, fixing a broken arbitrary link --- src/core/QCheck.mli | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/core/QCheck.mli b/src/core/QCheck.mli index 7d123d31..cf77c853 100644 --- a/src/core/QCheck.mli +++ b/src/core/QCheck.mli @@ -10,15 +10,17 @@ all rights reserved. (** 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, + to [bool]. The programmer 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 arbitrary} is used to describe how to generate random values, - shrink them (make counter-examples as small as possible), print - them, etc. Auxiliary modules such as {!Gen}, {!Print}, and {!Shrink} - can be used along with {!make} to build one's own arbitrary instances. + - {{!section:arbitrary}The ['a arbitrary] record type} describes how to generate random values, + shrink them (reduce counter-examples to a minimum), print them, etc. + It is the generator type expected by {!Test.make}. + + - Auxiliary modules such as {!Gen}, {!Print}, and {!Shrink} can be used along with {!make} + to build custom generators. - {!Test} is used to describe a single test, that is, a property of type ['a -> bool] combined with an ['a arbitrary] that is used to generate From c59f40bb86445e7263e9de536ce6ceba7ce16d90 Mon Sep 17 00:00:00 2001 From: Jan Midtgaard Date: Mon, 9 Dec 2024 12:44:40 +0100 Subject: [PATCH 02/15] Use Print.t type in arbitrary type --- src/core/QCheck.ml | 2 +- src/core/QCheck.mli | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/QCheck.ml b/src/core/QCheck.ml index bf5b6277..6bb11647 100644 --- a/src/core/QCheck.ml +++ b/src/core/QCheck.ml @@ -1042,7 +1042,7 @@ type 'a stat = string * ('a -> int) type 'a arbitrary = { gen: 'a Gen.t; - print: ('a -> string) option; (** print values *) + print: ('a Print.t) option; (** print values *) small: ('a -> int) option; (** size of example *) shrink: ('a -> 'a Iter.t) option; (** shrink to smaller examples *) collect: ('a -> string) option; (** map value to tag, and group by tag *) diff --git a/src/core/QCheck.mli b/src/core/QCheck.mli index cf77c853..e43f76ac 100644 --- a/src/core/QCheck.mli +++ b/src/core/QCheck.mli @@ -897,7 +897,7 @@ type 'a stat = string * ('a -> int) type 'a arbitrary = private { gen: 'a Gen.t; - print: ('a -> string) option; (** print values *) + print: ('a Print.t) 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 *) From 7981bd832dd83753be41ac72068757350762ff14 Mon Sep 17 00:00:00 2001 From: Jan Midtgaard Date: Mon, 9 Dec 2024 12:45:57 +0100 Subject: [PATCH 03/15] Use Shrink.t type in arbitrary type --- src/core/QCheck.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/QCheck.ml b/src/core/QCheck.ml index 6bb11647..cd29f021 100644 --- a/src/core/QCheck.ml +++ b/src/core/QCheck.ml @@ -1044,7 +1044,7 @@ type 'a arbitrary = { gen: 'a Gen.t; print: ('a Print.t) option; (** print values *) small: ('a -> int) option; (** size of example *) - shrink: ('a -> 'a Iter.t) option; (** shrink to smaller examples *) + 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 *) } From 78d0ff149d46e304e5128a5bcc3fea8ab829bfcd Mon Sep 17 00:00:00 2001 From: Jan Midtgaard Date: Mon, 9 Dec 2024 12:56:36 +0100 Subject: [PATCH 04/15] Replace erroneous mention of shrinking in tuple Gens by a more descriptive text --- src/core/QCheck.mli | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/QCheck.mli b/src/core/QCheck.mli index e43f76ac..34ab0652 100644 --- a/src/core/QCheck.mli +++ b/src/core/QCheck.mli @@ -369,9 +369,11 @@ module Gen : sig (** Generates quadruples. @since 0.5.1 *) - (** {3 Tuple of generators} *) + (** {3 Tuple generators} *) - (** {4 Shrinks on [gen1], then [gen2], then ... } *) + (** {4 Create tuple generators by composing individual element generators. For example, + [Gen.(tup3 int char bool)] creates a [(int * char * bool)] triple generator + by composing the [int], [char], and [bool] generators. *) val tup2 : 'a t -> 'b t -> ('a * 'b) t From ec276ad1c28e39d57759d37e90450507d8df989a Mon Sep 17 00:00:00 2001 From: Jan Midtgaard Date: Mon, 9 Dec 2024 12:57:12 +0100 Subject: [PATCH 05/15] Adjust QCheck2 introduction accordingly --- src/core/QCheck2.mli | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/QCheck2.mli b/src/core/QCheck2.mli index c6cae163..e686823e 100644 --- a/src/core/QCheck2.mli +++ b/src/core/QCheck2.mli @@ -15,7 +15,7 @@ content will appear. *) This 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. They also need to describe how to generate random values of the type, + to [bool]. The programmer 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: From 46a6a489fa35bab31adbd0181ea075456f87e14e Mon Sep 17 00:00:00 2001 From: Jan Midtgaard Date: Mon, 9 Dec 2024 13:10:18 +0100 Subject: [PATCH 06/15] Fix broken links --- src/core/QCheck.mli | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/QCheck.mli b/src/core/QCheck.mli index 34ab0652..d56cc5a4 100644 --- a/src/core/QCheck.mli +++ b/src/core/QCheck.mli @@ -847,8 +847,8 @@ end 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". + Inspired from {:https://blogs.janestreet.com/quickcheck-for-core/} + and {{:https://dl.acm.org/doi/abs/10.1145/2364506.2364516}Koen Claessen's "Shrinking and Showing Functions"}. @since 0.6 *) @@ -1169,7 +1169,7 @@ end also be used to find data satisfying a predicate, {i within a property being tested}. - See https://github.com/c-cube/qcheck/issues/31 + See {:https://github.com/c-cube/qcheck/issues/31} *) exception No_example_found of string From 05b042a9d4cb40216ebc48965ac082f64ff0fbd4 Mon Sep 17 00:00:00 2001 From: Jan Midtgaard Date: Mon, 9 Dec 2024 13:18:37 +0100 Subject: [PATCH 07/15] Don't use a sub-section in tuple generators --- src/core/QCheck.mli | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/core/QCheck.mli b/src/core/QCheck.mli index d56cc5a4..4719ed01 100644 --- a/src/core/QCheck.mli +++ b/src/core/QCheck.mli @@ -369,11 +369,12 @@ module Gen : sig (** Generates quadruples. @since 0.5.1 *) - (** {3 Tuple generators} *) + (** {3 Tuple generators} - (** {4 Create tuple generators by composing individual element generators. For example, + Create tuple generators by composing individual element generators. For example, [Gen.(tup3 int char bool)] creates a [(int * char * bool)] triple generator - by composing the [int], [char], and [bool] generators. *) + by composing the [int], [char], and [bool] generators. + *) val tup2 : 'a t -> 'b t -> ('a * 'b) t From 30212bdd92947c81a69659e03a49478ddf7026f1 Mon Sep 17 00:00:00 2001 From: Jan Midtgaard Date: Mon, 9 Dec 2024 13:19:44 +0100 Subject: [PATCH 08/15] Put Gen.generate* combinators in sub-section --- src/core/QCheck.mli | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/core/QCheck.mli b/src/core/QCheck.mli index 4719ed01..97ab8e85 100644 --- a/src/core/QCheck.mli +++ b/src/core/QCheck.mli @@ -591,12 +591,6 @@ module Gen : sig in a generator. @since 0.17 *) - val generate : ?rand:Random.State.t -> n:int -> 'a t -> 'a list - (** [generate ~n g] generates [n] instances of [g]. *) - - val generate1 : ?rand:Random.State.t -> 'a t -> 'a - (** [generate1 g] generates one instance of [g]. *) - val ( let+ ) : 'a t -> ('a -> 'b) -> 'b t val ( and+ ) : 'a t -> 'b t -> ('a * 'b) t @@ -604,6 +598,18 @@ module Gen : sig val ( let* ) : 'a t -> ('a -> 'b t) -> 'b t val ( and* ) : 'a t -> 'b t -> ('a * 'b) t + + (** {3 Debug generators} + + These functions should not be used in tests: they are provided + for convenience to debug/investigate what values a generator produces. + *) + + val generate : ?rand:Random.State.t -> n:int -> 'a t -> 'a list + (** [generate ~n g] generates [n] instances of [g]. *) + + val generate1 : ?rand:Random.State.t -> 'a t -> 'a + (** [generate1 g] generates one instance of [g]. *) end (** {2 Pretty printing} *) From c845fe4232a84992cf1bc18220459553d448baed Mon Sep 17 00:00:00 2001 From: Jan Midtgaard Date: Mon, 9 Dec 2024 13:23:00 +0100 Subject: [PATCH 09/15] Add primitive generator sub-section, moving tuple generator sub-section --- src/core/QCheck.mli | 72 +++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/src/core/QCheck.mli b/src/core/QCheck.mli index 97ab8e85..32fb54be 100644 --- a/src/core/QCheck.mli +++ b/src/core/QCheck.mli @@ -238,6 +238,8 @@ module Gen : sig @since 0.18 *) + (** {3 Primitive generators} *) + val unit : unit t (** The unit generator. *) val bool : bool t (** The boolean generator. *) @@ -361,41 +363,6 @@ module Gen : sig @since 0.18 ([?ratio] parameter) *) - val pair : 'a t -> 'b t -> ('a * 'b) t (** Generates pairs. *) - - val triple : 'a t -> 'b t -> 'c t -> ('a * 'b * 'c) t (** Generates triples. *) - - val quad : 'a t -> 'b t -> 'c t -> 'd t -> ('a * 'b * 'c * 'd) t - (** Generates quadruples. - @since 0.5.1 *) - - (** {3 Tuple generators} - - Create tuple generators by composing individual element generators. For example, - [Gen.(tup3 int char bool)] creates a [(int * char * bool)] triple generator - by composing the [int], [char], and [bool] generators. - *) - - 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 - val char : char t (** Generates characters upto character code 255. *) @@ -496,6 +463,41 @@ module Gen : sig (** Generates arrays of small size (see {!small_nat}). @since 0.10 *) + (** {3 Tuple generators} + + Create tuple generators by composing individual element generators. For example, + [Gen.(tup3 int char bool)] creates a [(int * char * bool)] triple generator + by composing the [int], [char], and [bool] generators. + *) + + val pair : 'a t -> 'b t -> ('a * 'b) t (** Generates pairs. *) + + val triple : 'a t -> 'b t -> 'c t -> ('a * 'b * 'c) t (** Generates triples. *) + + val quad : 'a t -> 'b t -> 'c t -> 'd t -> ('a * 'b * 'c * 'd) t + (** Generates quadruples. + @since 0.5.1 *) + + 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 + val join : 'a t t -> 'a t (** Collapses a generator of generators to simply a generator. @since 0.5 *) From c93dd6a1c33c3f6fd38e8e25e28d1d6e7d4fccb8 Mon Sep 17 00:00:00 2001 From: Jan Midtgaard Date: Tue, 10 Dec 2024 11:01:45 +0100 Subject: [PATCH 10/15] Rework order of src/core/QCheck.mli for nicer documentation --- src/core/QCheck.mli | 1060 +++++++++++++++++++++++-------------------- 1 file changed, 563 insertions(+), 497 deletions(-) diff --git a/src/core/QCheck.mli b/src/core/QCheck.mli index 32fb54be..fc3d8b25 100644 --- a/src/core/QCheck.mli +++ b/src/core/QCheck.mli @@ -7,7 +7,9 @@ all rights reserved. (** {1 Quickcheck inspired property-based testing} *) -(** The library takes inspiration from Haskell's QuickCheck library. The +(** {1 Introduction} + + 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]. The programmer also needs to describe how to generate random values of the type, @@ -29,7 +31,7 @@ all rights reserved. and test, etc. - Examples: + {1 Examples} - List.rev is involutive: @@ -80,6 +82,8 @@ all rights reserved. {{:http://gasche.github.io/random-generator/doc/Generator.html } here}. *) +(** {1 Assumptions} *) + val (==>) : bool -> bool -> bool (** [b1 ==> b2] is the logical implication [b1 => b2] ie [not b1 || b2] (except that it is strict and will interact @@ -125,8 +129,14 @@ val assume_fail : unit -> 'a @since 0.5.1 *) -(** {2 Generate Random Values} *) +(** {1 Generate Random Values} *) + module Gen : sig + (** The [Gen] module offers combinators to build custom generators. + Unlike the {{!section:arbitrary}the ['a arbitrary] record type}, + which comes with printers, shrinkers, etc. {!Gen.t} represents + a type for generation only. *) + type 'a t = Random.State.t -> 'a (** A random generator for values of type 'a. *) @@ -614,10 +624,12 @@ module Gen : sig (** [generate1 g] generates one instance of [g]. *) end -(** {2 Pretty printing} *) +(** {1 Printing Values} *) -(** {2 Show Values} *) module Print : sig + + (** The [Print] module offers combinators for printing generated values. *) + type 'a t = 'a -> string (** Printer for values of type ['a]. *) @@ -686,12 +698,24 @@ module Print : sig (** 9-tuple printer. Expects printers for each component. *) end -(** {2 Iterators} +(** {1 Shrinking Values} + + Shrinking is used to reduce the size of a counter-example. It tries + to make the counter-example smaller, e.g., by decreasing an integer, + or removing elements of a list, until the property to test holds again; + it then returns the smallest value that still made the test fail. + + Shrinking is defined as a type {!Shrink.t} that takes an argument to shrink + and produces an iterator of type {!Iter.t} of shrinking candidates. +*) + +(** {2 Iterators} *) - Compatible with the library "sequence". An iterator [i] is simply - a function that accepts another function [f] (of type ['a -> unit]) - and calls [f] on a sequence of elements [f x1; f x2; ...; f xn]. *) module Iter : sig + (** [Iter] is compatible with the library "sequence". An iterator [i] is simply + a function that accepts another function [f] (of type ['a -> unit]) + and calls [f] on a sequence of elements [f x1; f x2; ...; f xn]. *) + type 'a t = ('a -> unit) -> unit val empty : 'a t @@ -728,13 +752,12 @@ module Iter : sig val ( and* ) : 'a t -> 'b t -> ('a * 'b) t end -(** {2 Shrink Values} +(** {2 Shrinkers} *) - Shrinking is used to reduce the size of a counter-example. It tries - to make the counter-example smaller by decreasing it, or removing - elements, until the property to test holds again; then it returns the - smallest value that still made the test fail. *) module Shrink : sig + (** The [Shrink] module contains combinators to build up composite shrinkers + for user-defined types *) + type 'a t = 'a -> 'a Iter.t (** Given a counter-example, return an iterator on smaller versions of the counter-example. *) @@ -850,52 +873,8 @@ module Shrink : sig (** Similar to {!tup2} *) end -(** {2 Observe Values} *) - -(** 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 {{:https://dl.acm.org/doi/abs/10.1145/2364506.2364516}Koen Claessen's "Shrinking and Showing Functions"}. - - @since 0.6 -*) - -module Observable : sig - (** An observable for ['a], packing a printer and other things. *) - type -'a t - - val equal : 'a t -> 'a -> 'a -> bool - val hash : 'a t -> 'a -> int - val print : 'a t -> 'a Print.t - - val unit : unit t - val bool : bool t - val int : int t - val float : float t - val string : string t - val bytes : bytes t (** @since 0.20 *) - val char : char t - - val make : - ?eq:('a -> 'a -> bool) -> - ?hash:('a -> int) -> - 'a Print.t -> - 'a t - - val map : ('a -> 'b) -> 'b t -> 'a t - - val option : 'a t -> 'a option t - val list : 'a t -> 'a list t - val array : 'a t -> 'a array t - - val pair : 'a t -> 'b t -> ('a * 'b) t - val triple : 'a t -> 'b t -> 'c t -> ('a * 'b * 'c) t - val quad : 'a t -> 'b t -> 'c t -> 'd t -> ('a * 'b * 'c * 'd) t -end -(** {2 Arbitrary} +(** {1 Arbitrary} A value of type ['a arbitrary] glues together a random generator, and optional functions for shrinking, printing, computing the size, @@ -938,6 +917,11 @@ val make : @param shrink to shrink counter-examples *) +(** {2 Adjusting arbitrary generators } + + There is a range to [get] and [set] fields on an arbitrary record type. +*) + val set_print : 'a Print.t -> 'a arbitrary -> 'a arbitrary val set_small : ('a -> int) -> 'a arbitrary -> 'a arbitrary val set_shrink : 'a Shrink.t -> 'a arbitrary -> 'a arbitrary @@ -967,357 +951,107 @@ val get_gen : 'a arbitrary -> 'a Gen.t val get_print : 'a arbitrary -> 'a Print.t option -(** {2 Tests} - 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]. +(** {2 Primitive combinators for arbitrary} *) - The main features of this module are: - - {!Test.make} to build a test, - - {!Test.make_neg} to build a negative test that is expected not to satisfy the tested property, - - {!Test.check_exn} to run a single test with a simple runner. +val unit : unit arbitrary +(** Always generates [()], obviously. *) - A test fails if the property does not hold for a given input. The {{!Test.fail_report} simple} form or the {{!Test.fail_reportf} rich} form) offer more elaborate forms to fail a test. +val bool : bool arbitrary +(** Uniform boolean generator. *) - For more serious testing, it is recommended to create a testsuite and use a full-fledged runner: - - {!QCheck_base_runner} is a QCheck-only runner (useful if you don't have or don't need another test framework) - - {!QCheck_alcotest} interfaces to the Alcotest framework - - {!QCheck_ounit} interfaces to the to OUnit framework -*) +val float : float arbitrary +(** Generates regular floats (no nan and no infinities). *) +(* FIXME: does not generate nan nor infinity I think. *) -(** Result of running a test *) -module TestResult : sig - type 'a counter_ex = 'a QCheck2.TestResult.counter_ex = { - instance: 'a; (** The counter-example(s) *) +val pos_float : float arbitrary +(** Positive float generator (no nan and no infinities). *) - shrink_steps: int; (** How many shrinking steps for this counterex *) +val neg_float : float arbitrary +(** Negative float generator (no nan and no infinities). *) - msg_l: string list; - (** messages. - @since 0.7 *) - } +val float_bound_inclusive : float -> float arbitrary +(** [float_bound_inclusive n] is uniform between [0] and [n] included. If [bound] is + negative, the result is negative or zero. If [bound] is 0, the result is 0. + @since 0.11 *) - type 'a failed_state = 'a counter_ex list +val float_bound_exclusive : float -> float arbitrary +(** [float_bound_exclusive n] is uniform between [0] included and [n] excluded. + If [bound] is negative, the result is negative or zero. + @raise Invalid_argument if [bound] is zero. + @since 0.11 *) - (** Result state. - changed in 0.10 (move to inline records, add Fail_other) *) - type 'a state = 'a QCheck2.TestResult.state = - | Success - | Failed of { - instances: 'a failed_state; (** Failed instance(s) *) - } - | Failed_other of {msg: string} - | Error of { - instance: 'a counter_ex; - exn: exn; - backtrace: string; - } (** Error, backtrace, and instance that triggered it *) +val float_range : float -> float -> float arbitrary +(** [float_range low high] is uniform between [low] included and [high] included. + @raise Invalid_argument if [low > high] or if the range is larger than [max_float]. + @since 0.11 *) - (* result returned by running a test *) - type 'a t = 'a QCheck2.TestResult.t +val exponential : float -> float arbitrary +(** [exponential m] generates floating-point numbers following an exponential + distribution with a mean of [m]. + @raise Invalid_argument if [m] is NaN. + @since NEXT_VERSION *) - val get_count : _ t -> int - (** Get the count of a cell. - @since 0.5.3 *) +val int : int arbitrary +(** Int generator. Uniformly distributed. *) - val get_count_gen : _ t -> int +val int_bound : int -> int arbitrary +(** [int_bound n] is uniform between [0] and [n] included. *) - val get_state : 'a t -> 'a state +val int_range : int -> int -> int arbitrary +(** [int_range a b] is uniform between [a] and [b] included. [b] must be + larger than [a]. *) - val collect : _ t -> (string,int) Hashtbl.t option - (** Obtain statistics - @since 0.6 *) +val small_nat : int arbitrary +(** Small unsigned integers. + @since 0.5.1 *) - val stats : 'a t -> ('a stat * (int,int) Hashtbl.t) list - (** Obtain statistics - @since 0.6 *) +val small_int : int arbitrary +(** Small unsigned integers. See {!Gen.small_int}. + @deprecated use {!small_signed_int}. *) - val warnings : _ t -> string list - (** Obtain list of warnings - @since 0.10 *) +val small_signed_int : int arbitrary +(** Small signed integers. + @since 0.5.2 *) - val is_success : _ t -> bool - (** Returns true iff the state if [Success] - @since 0.9 *) -end +val (--) : int -> int -> int arbitrary +(** Synonym for {!int_range}. *) -(** Module related to individual tests. - @since 0.18 most of it moved to {!QCheck2}, - and the type ['a cell] was made a private implementation detail. -*) -module Test : sig - type res = QCheck2.Test.res = - | Success - | Failure - | FalseAssumption - | Error of exn * string - type 'a event = 'a QCheck2.Test.event = - | Generating - | Collecting of 'a - | Testing of 'a - | Shrunk of int * 'a - | Shrinking of int * int * 'a +val int32 : int32 arbitrary +(** Int32 generator. Uniformly distributed. *) - type 'a cell = 'a QCheck2.Test.cell - type 'a handler = 'a QCheck2.Test.handler - type 'a step = 'a QCheck2.Test.step - type 'a callback = 'a QCheck2.Test.callback +val int64 : int64 arbitrary +(** Int64 generator. Uniformly distributed. *) - type t = QCheck2.Test.t +val pos_int : int arbitrary +(** Positive int generator (0 included). Uniformly distributed. + See {!Gen.pint} *) - val fail_report : string -> 'a - (** Fail the test with some additional message that will - be reported. - @since 0.7 *) +val small_int_corners : unit -> int arbitrary +(** As [small_int], but each newly created generator starts with + a list of corner cases before falling back on random generation. *) - val fail_reportf : ('a, Format.formatter, unit, 'b) format4 -> 'a - (** Format version of {!fail_report} - @since 0.7 *) +val neg_int : int arbitrary +(** Negative int generator (0 included, see {!Gen.neg_int}). + The distribution is similar to that of + [small_int], not of [pos_int]. +*) - val make_cell : - ?if_assumptions_fail:([`Fatal | `Warning] * float) -> - ?count:int -> ?long_factor:int -> ?negative:bool -> ?max_gen:int -> ?max_fail:int -> - ?small:('a -> int) -> ?retries:int -> ?name:string -> - 'a arbitrary -> ('a -> bool) -> 'a cell - (** [make_cell arb prop] builds a test that checks property [prop] on instances - of the generator [arb]. - @param name the name of the test. - @param count number of test cases to run, counting only - the test cases which satisfy preconditions. - @param retries number of times to retry the tested property while shrinking. - @param long_factor the factor by which to multiply count, max_gen and - max_fail when running a long test (default: 1). - @param negative whether the test is expected not to satisfy the tested property. - @param max_gen maximum number of times the generation function - is called in total to replace inputs that do not satisfy - preconditions (should be >= count). - @param max_fail maximum number of failures before we stop generating - inputs. This is useful if shrinking takes too much time. - @param small kept for compatibility reasons; if provided, replaces - the field [arbitrary.small]. - If there is no shrinking function but there is a [small] - function, only the smallest failures will be printed. - @param if_assumptions_fail the minimum - fraction of tests that must satisfy the precondition for a success - to be considered valid. - The fraction should be between 0. and 1. - A warning will be emitted otherwise if - the flag is [`Warning], the test will be a failure if the flag is [`Fatal]. - (since 0.10) +val char : char arbitrary +(** Uniformly distributed on all the chars (not just ascii or + valid latin-1). *) + +val printable_char : char arbitrary +(** Uniformly distributed over a subset of printable ascii chars. + Ascii character codes 32 to 126, inclusive - or ['\n'] with code 10. *) - val get_law : 'a cell -> ('a -> bool) - (** @deprecated use {!QCheck2.Test.get_law} instead *) - val get_name : _ cell -> string - (** @deprecated use {!QCheck2.Test.get_name} instead *) - val set_name : _ cell -> string -> unit - (** @deprecated use {!QCheck2.Test.set_name} instead *) +val numeral_char : char arbitrary +(** Uniformly distributed over ['0'..'9']. *) - val get_count : _ cell -> int - (** Get the count of a cell. - @deprecated use {!QCheck2.Test.get_count} instead - @since 0.5.3 *) - - val get_long_factor : _ cell -> int - (** Get the long factor of a cell. - @deprecated use {!QCheck2.Test.get_long_factor} instead - @since 0.5.3 *) - - val make : - ?if_assumptions_fail:([`Fatal | `Warning] * float) -> - ?count:int -> ?long_factor:int -> ?max_gen:int -> ?max_fail:int -> - ?small:('a -> int) -> ?retries:int -> ?name:string -> 'a arbitrary -> - ('a -> bool) -> t - (** [make arb prop] builds a test that checks property [prop] on instances - of the generator [arb]. - See {!make_cell} for a description of the parameters. - *) - - val make_neg : - ?if_assumptions_fail:([`Fatal | `Warning] * float) -> - ?count:int -> ?long_factor:int -> ?max_gen:int -> ?max_fail:int -> - ?small:('a -> int) -> ?retries:int -> ?name:string -> 'a arbitrary -> - ('a -> bool) -> t - (** [make_neg arb prop] builds a test that checks property [prop] on instances - of the generator [arb]. - The test is considered negative, meaning that it is expected not to satisfy the tested property. - This information is recorded in an underlying test [cell] entry and interpreted suitably by test runners. - See {!make_cell} for a description of the parameters. - *) - - include module type of QCheck2.Test_exceptions - - val print_instance : 'a cell -> 'a -> string - val print_c_ex : 'a cell -> 'a TestResult.counter_ex -> string - val print_fail : 'a cell -> string -> 'a TestResult.counter_ex list -> string - val print_fail_other : string -> msg:string -> string - val print_error : ?st:string -> 'a cell -> string -> 'a TestResult.counter_ex * exn -> string - val print_test_fail : string -> string list -> string - val print_test_error : string -> string -> exn -> string -> string - - val check_cell : - ?long:bool -> ?call:'a callback -> - ?step:'a step -> ?handler:'a handler -> - ?rand:Random.State.t -> 'a cell -> 'a TestResult.t - (** See {!QCheck2.Test.check_cell}. *) - - val check_cell_exn : - ?long:bool -> ?call:'a callback -> - ?step:'a step -> ?handler:'a handler -> - ?rand:Random.State.t -> 'a cell -> unit - (** See {!QCheck2.Test.check_cell_exn}. *) - - val check_exn : ?long:bool -> ?rand:Random.State.t -> t -> unit - (** See {!QCheck2.Test.check_exn}. *) -end - -(** {2 Sub-tests} *) - -(** The infrastructure used to find counter-examples to properties can - also be used to find data satisfying a predicate, - {i within a property being tested}. - - See {:https://github.com/c-cube/qcheck/issues/31} -*) - -exception No_example_found of string - -val find_example : - ?name:string -> - ?count:int -> - f:('a -> bool) -> - 'a Gen.t -> - 'a Gen.t -(** [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. - {b NOTE} this should only be used from within a property in {!Test.make}. - @param count number of attempts. - @param name description of the example to find (used in the exception). - @param f the property that the example must satisfy. - @raise No_example_found if no example is found within [count] tries. - @since 0.6 -*) - -val find_example_gen : - ?rand:Random.State.t -> - ?name:string -> - ?count:int -> - f:('a -> bool) -> - 'a Gen.t -> - 'a -(** Toplevel version of {!find_example}. - [find_example_gen ~f arb ~n] is roughly the same as - [Gen.generate1 (find_example ~f arb |> gen)]. - @param rand the random state to use to generate inputs. - @raise No_example_found if no example was found within [count] tries. - @since 0.6 *) - -(** {2 Combinators for arbitrary} *) - -val choose : 'a arbitrary list -> 'a arbitrary -(** Choose among the given list of generators. The list must not - be empty; if it is Invalid_argument is raised. *) - -val unit : unit arbitrary -(** Always generates [()], obviously. *) - -val bool : bool arbitrary -(** Uniform boolean generator. *) - -val float : float arbitrary -(** Generates regular floats (no nan and no infinities). *) -(* FIXME: does not generate nan nor infinity I think. *) - -val pos_float : float arbitrary -(** Positive float generator (no nan and no infinities). *) - -val neg_float : float arbitrary -(** Negative float generator (no nan and no infinities). *) - -val float_bound_inclusive : float -> float arbitrary -(** [float_bound_inclusive n] is uniform between [0] and [n] included. If [bound] is - negative, the result is negative or zero. If [bound] is 0, the result is 0. - @since 0.11 *) - -val float_bound_exclusive : float -> float arbitrary -(** [float_bound_exclusive n] is uniform between [0] included and [n] excluded. - If [bound] is negative, the result is negative or zero. - @raise Invalid_argument if [bound] is zero. - @since 0.11 *) - -val float_range : float -> float -> float arbitrary -(** [float_range low high] is uniform between [low] included and [high] included. - @raise Invalid_argument if [low > high] or if the range is larger than [max_float]. - @since 0.11 *) - -val exponential : float -> float arbitrary -(** [exponential m] generates floating-point numbers following an exponential - distribution with a mean of [m]. - @raise Invalid_argument if [m] is NaN. - @since NEXT_VERSION *) - -val int : int arbitrary -(** Int generator. Uniformly distributed. *) - -val int_bound : int -> int arbitrary -(** [int_bound n] is uniform between [0] and [n] included. *) - -val int_range : int -> int -> int arbitrary -(** [int_range a b] is uniform between [a] and [b] included. [b] must be - larger than [a]. *) - -val small_nat : int arbitrary -(** Small unsigned integers. - @since 0.5.1 *) - -val small_int : int arbitrary -(** Small unsigned integers. See {!Gen.small_int}. - @deprecated use {!small_signed_int}. *) - -val small_signed_int : int arbitrary -(** Small signed integers. - @since 0.5.2 *) - -val (--) : int -> int -> int arbitrary -(** Synonym for {!int_range}. *) - -val int32 : int32 arbitrary -(** Int32 generator. Uniformly distributed. *) - -val int64 : int64 arbitrary -(** Int64 generator. Uniformly distributed. *) - -val pos_int : int arbitrary -(** Positive int generator (0 included). Uniformly distributed. - See {!Gen.pint} *) - -val small_int_corners : unit -> int arbitrary -(** As [small_int], but each newly created generator starts with - a list of corner cases before falling back on random generation. *) - -val neg_int : int arbitrary -(** Negative int generator (0 included, see {!Gen.neg_int}). - The distribution is similar to that of - [small_int], not of [pos_int]. -*) - -val char : char arbitrary -(** Uniformly distributed on all the chars (not just ascii or - valid latin-1). *) - -val printable_char : char arbitrary -(** Uniformly distributed over a subset of printable ascii chars. - Ascii character codes 32 to 126, inclusive - or ['\n'] with code 10. - *) - -val numeral_char : char arbitrary -(** Uniformly distributed over ['0'..'9']. *) - -val bytes_gen_of_size : int Gen.t -> char Gen.t -> bytes arbitrary -(** Builds a bytes generator from a (non-negative) size generator and a character generator. - @since 0.20 *) +val bytes_gen_of_size : int Gen.t -> char Gen.t -> bytes arbitrary +(** Builds a bytes generator from a (non-negative) size generator and a character generator. + @since 0.20 *) val bytes_of : char Gen.t -> bytes arbitrary (** Generates bytes with a distribution of length of {!Gen.nat}. @@ -1427,6 +1161,14 @@ val array : 'a arbitrary -> 'a array arbitrary val array_of_size : int Gen.t -> 'a arbitrary -> 'a array arbitrary (** Generates arrays with length from the given distribution. *) +val option : ?ratio:float -> 'a arbitrary -> 'a option arbitrary +(** Choose between returning Some random value with optional ratio, or None. *) + + +(** {2 Tuples of arbitrary generators} + + These shrink on [gen1], then [gen2], then ... *) + val pair : 'a arbitrary -> 'b arbitrary -> ('a * 'b) arbitrary (** Combines two generators into a generator of pairs. Order of elements can matter (w.r.t shrinking, see {!Shrink.pair}) *) @@ -1439,10 +1181,6 @@ val quad : 'a arbitrary -> 'b arbitrary -> 'c arbitrary -> 'd arbitrary -> ('a * (** Combines four generators into a generator of 4-tuples. Order matters for shrinking, see {!Shrink.pair} and the likes *) -(** {3 Tuple of generators} *) - -(** {4 Shrinks on [gen1], then [gen2], then ... } *) - val tup2 : 'a arbitrary -> 'b arbitrary -> @@ -1534,53 +1272,425 @@ val tup9 : Order of elements can matter (w.r.t shrinking, see {!Shrink.tup2}) Prints as many elements as available printers *) -val option : ?ratio:float -> 'a arbitrary -> 'a option arbitrary -(** Choose between returning Some random value with optional ratio, or None. *) -val fun1_unsafe : 'a arbitrary -> 'b arbitrary -> ('a -> 'b) arbitrary -(** Generator of functions of arity 1. - The functions are always pure and total functions: - - when given the same argument (as decided by Stdlib.(=)), it returns the same value - - it never does side effects, like printing or never raise exceptions etc. - The functions generated are really printable. +(** {2 Combinatoric arbitrary combinators } *) - renamed from {!fun1} since 0.6 +val choose : 'a arbitrary list -> 'a arbitrary +(** Choose among the given list of generators. The list must not + be empty; if it is Invalid_argument is raised. *) - @deprecated use {!fun_} instead. +val oneofl : ?print:'a Print.t -> ?collect:('a -> string) -> + 'a list -> 'a arbitrary +(** Pick an element randomly in the list. *) - @since 0.6 -*) +val oneofa : ?print:'a Print.t -> ?collect:('a -> string) -> + 'a array -> 'a arbitrary +(** Pick an element randomly in the array. *) -val fun2_unsafe : 'a arbitrary -> 'b arbitrary -> 'c arbitrary -> ('a -> 'b -> 'c) arbitrary -(** Generator of functions of arity 2. The remark about [fun1] also apply - here. - renamed from {!fun2} since 0.6 - @deprecated use {!fun_} instead since 0.6 +val oneof : 'a arbitrary list -> 'a arbitrary +(** Pick a generator among the list, randomly. + @deprecated this function is badly specified and will not use shrinkers + appropriately. Consider using {!Gen.oneof} and then {!make} to build + a well behaved arbitrary instance. *) -type _ fun_repr -(** Internal data for functions. A ['f fun_] is a function - of type ['f], fundamentally. *) +val always : ?print:'a Print.t -> 'a -> 'a arbitrary +(** Always return the same element. *) -(** A function packed with the data required to print/shrink it. See {!Fn} - to see how to apply, print, etc. such a function. +val frequency : ?print:'a Print.t -> ?small:('a -> int) -> + ?shrink:'a Shrink.t -> ?collect:('a -> string) -> + (int * 'a arbitrary) list -> 'a arbitrary +(** Similar to {!oneof} but with frequencies. *) - One can also directly pattern match on it to obtain - the executable function. +val frequencyl : ?print:'a Print.t -> ?small:('a -> int) -> + (int * 'a) list -> 'a arbitrary +(** Same as {!oneofl}, but each element is paired with its frequency in + the probability distribution (the higher, the more likely). *) - For example: - {[ - QCheck.Test.make - QCheck.(pair (fun1 Observable.int bool) (small_list int)) +val frequencya : ?print:'a Print.t -> ?small:('a -> int) -> + (int * 'a) array -> 'a arbitrary +(** Same as {!frequencyl}, but with an array. *) + +val map : ?rev:('b -> 'a) -> ('a -> 'b) -> 'a arbitrary -> 'b arbitrary +(** [map f a] returns a new arbitrary instance that generates values using + [a#gen] and then transforms them through [f]. + @param rev if provided, maps values back to type ['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). +*) + +val map_same_type : ('a -> 'a) -> 'a arbitrary -> 'a arbitrary +(** 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) arbitrary +(** [map_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. + @param print optional printer for the [f]'s output. +*) + + +(** {1 Tests} + + 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]. + + The main features of this module are: + - {!Test.make} to build a test, + - {!Test.make_neg} to build a negative test that is expected not to satisfy the tested property, + - {!Test.check_exn} to run a single test with a simple runner. + + A test fails if the property does not hold for a given input. The {{!Test.fail_report} simple} form or the {{!Test.fail_reportf} rich} form) offer more elaborate forms to fail a test. + + For more serious testing, it is recommended to create a testsuite and use a full-fledged runner: + - {!QCheck_base_runner} is a QCheck-only runner (useful if you don't have or don't need another test framework) + - {!QCheck_alcotest} interfaces to the Alcotest framework + - {!QCheck_ounit} interfaces to the to OUnit framework +*) + + +(** {2 Test Results } *) + +module TestResult : sig + (** Module to represent the result of running a test *) + + type 'a counter_ex = 'a QCheck2.TestResult.counter_ex = { + instance: 'a; (** The counter-example(s) *) + + shrink_steps: int; (** How many shrinking steps for this counterex *) + + msg_l: string list; + (** messages. + @since 0.7 *) + } + + type 'a failed_state = 'a counter_ex list + + (** Result state. + changed in 0.10 (move to inline records, add Fail_other) *) + type 'a state = 'a QCheck2.TestResult.state = + | Success + | Failed of { + instances: 'a failed_state; (** Failed instance(s) *) + } + | Failed_other of {msg: string} + | Error of { + instance: 'a counter_ex; + exn: exn; + backtrace: string; + } (** Error, backtrace, and instance that triggered it *) + + (* result returned by running a test *) + type 'a t = 'a QCheck2.TestResult.t + + val get_count : _ t -> int + (** Get the count of a cell. + @since 0.5.3 *) + + val get_count_gen : _ t -> int + + val get_state : 'a t -> 'a state + + val collect : _ t -> (string,int) Hashtbl.t option + (** Obtain statistics + @since 0.6 *) + + val stats : 'a t -> ('a stat * (int,int) Hashtbl.t) list + (** Obtain statistics + @since 0.6 *) + + val warnings : _ t -> string list + (** Obtain list of warnings + @since 0.10 *) + + val is_success : _ t -> bool + (** Returns true iff the state if [Success] + @since 0.9 *) +end + +(** {2 Defining Tests } *) + +(** Module related to individual tests. + @since 0.18 most of it moved to {!QCheck2}, + and the type ['a cell] was made a private implementation detail. +*) +module Test : sig + type res = QCheck2.Test.res = + | Success + | Failure + | FalseAssumption + | Error of exn * string + type 'a event = 'a QCheck2.Test.event = + | Generating + | Collecting of 'a + | Testing of 'a + | Shrunk of int * 'a + | Shrinking of int * int * 'a + + type 'a cell = 'a QCheck2.Test.cell + type 'a handler = 'a QCheck2.Test.handler + type 'a step = 'a QCheck2.Test.step + type 'a callback = 'a QCheck2.Test.callback + + type t = QCheck2.Test.t + + val fail_report : string -> 'a + (** Fail the test with some additional message that will + be reported. + @since 0.7 *) + + val fail_reportf : ('a, Format.formatter, unit, 'b) format4 -> 'a + (** Format version of {!fail_report} + @since 0.7 *) + + val make_cell : + ?if_assumptions_fail:([`Fatal | `Warning] * float) -> + ?count:int -> ?long_factor:int -> ?negative:bool -> ?max_gen:int -> ?max_fail:int -> + ?small:('a -> int) -> ?retries:int -> ?name:string -> + 'a arbitrary -> ('a -> bool) -> 'a cell + (** [make_cell arb prop] builds a test that checks property [prop] on instances + of the generator [arb]. + @param name the name of the test. + @param count number of test cases to run, counting only + the test cases which satisfy preconditions. + @param retries number of times to retry the tested property while shrinking. + @param long_factor the factor by which to multiply count, max_gen and + max_fail when running a long test (default: 1). + @param negative whether the test is expected not to satisfy the tested property. + @param max_gen maximum number of times the generation function + is called in total to replace inputs that do not satisfy + preconditions (should be >= count). + @param max_fail maximum number of failures before we stop generating + inputs. This is useful if shrinking takes too much time. + @param small kept for compatibility reasons; if provided, replaces + the field [arbitrary.small]. + If there is no shrinking function but there is a [small] + function, only the smallest failures will be printed. + @param if_assumptions_fail the minimum + fraction of tests that must satisfy the precondition for a success + to be considered valid. + The fraction should be between 0. and 1. + A warning will be emitted otherwise if + the flag is [`Warning], the test will be a failure if the flag is [`Fatal]. + (since 0.10) + *) + + val get_law : 'a cell -> ('a -> bool) + (** @deprecated use {!QCheck2.Test.get_law} instead *) + val get_name : _ cell -> string + (** @deprecated use {!QCheck2.Test.get_name} instead *) + val set_name : _ cell -> string -> unit + (** @deprecated use {!QCheck2.Test.set_name} instead *) + + val get_count : _ cell -> int + (** Get the count of a cell. + @deprecated use {!QCheck2.Test.get_count} instead + @since 0.5.3 *) + + val get_long_factor : _ cell -> int + (** Get the long factor of a cell. + @deprecated use {!QCheck2.Test.get_long_factor} instead + @since 0.5.3 *) + + val make : + ?if_assumptions_fail:([`Fatal | `Warning] * float) -> + ?count:int -> ?long_factor:int -> ?max_gen:int -> ?max_fail:int -> + ?small:('a -> int) -> ?retries:int -> ?name:string -> 'a arbitrary -> + ('a -> bool) -> t + (** [make arb prop] builds a test that checks property [prop] on instances + of the generator [arb]. + See {!make_cell} for a description of the parameters. + *) + + val make_neg : + ?if_assumptions_fail:([`Fatal | `Warning] * float) -> + ?count:int -> ?long_factor:int -> ?max_gen:int -> ?max_fail:int -> + ?small:('a -> int) -> ?retries:int -> ?name:string -> 'a arbitrary -> + ('a -> bool) -> t + (** [make_neg arb prop] builds a test that checks property [prop] on instances + of the generator [arb]. + The test is considered negative, meaning that it is expected not to satisfy the tested property. + This information is recorded in an underlying test [cell] entry and interpreted suitably by test runners. + See {!make_cell} for a description of the parameters. + *) + + include module type of QCheck2.Test_exceptions + + val print_instance : 'a cell -> 'a -> string + val print_c_ex : 'a cell -> 'a TestResult.counter_ex -> string + val print_fail : 'a cell -> string -> 'a TestResult.counter_ex list -> string + val print_fail_other : string -> msg:string -> string + val print_error : ?st:string -> 'a cell -> string -> 'a TestResult.counter_ex * exn -> string + val print_test_fail : string -> string list -> string + val print_test_error : string -> string -> exn -> string -> string + + val check_cell : + ?long:bool -> ?call:'a callback -> + ?step:'a step -> ?handler:'a handler -> + ?rand:Random.State.t -> 'a cell -> 'a TestResult.t + (** See {!QCheck2.Test.check_cell}. *) + + val check_cell_exn : + ?long:bool -> ?call:'a callback -> + ?step:'a step -> ?handler:'a handler -> + ?rand:Random.State.t -> 'a cell -> unit + (** See {!QCheck2.Test.check_cell_exn}. *) + + val check_exn : ?long:bool -> ?rand:Random.State.t -> t -> unit + (** See {!QCheck2.Test.check_exn}. *) +end + +(** {2 Sub-tests} *) + +(** The infrastructure used to find counter-examples to properties can + also be used to find data satisfying a predicate, + {i within a property being tested}. + + See {:https://github.com/c-cube/qcheck/issues/31} +*) + +exception No_example_found of string + +val find_example : + ?name:string -> + ?count:int -> + f:('a -> bool) -> + 'a Gen.t -> + 'a Gen.t +(** [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. + {b NOTE} this should only be used from within a property in {!Test.make}. + @param count number of attempts. + @param name description of the example to find (used in the exception). + @param f the property that the example must satisfy. + @raise No_example_found if no example is found within [count] tries. + @since 0.6 +*) + +val find_example_gen : + ?rand:Random.State.t -> + ?name:string -> + ?count:int -> + f:('a -> bool) -> + 'a Gen.t -> + 'a +(** Toplevel version of {!find_example}. + [find_example_gen ~f arb ~n] is roughly the same as + [Gen.generate1 (find_example ~f arb |> gen)]. + @param rand the random state to use to generate inputs. + @raise No_example_found if no example was found within [count] tries. + @since 0.6 *) + + +(** {1 Generating Functions} + + The [QCheck] module supports generation of pure function values. + The implementation is inspired from {:https://blogs.janestreet.com/quickcheck-for-core/} + and {{:https://dl.acm.org/doi/abs/10.1145/2364506.2364516}Koen Claessen's "Shrinking and Showing Functions"}. + + Generated function arguments are of type {!Observable.t} and function results are of type + {{!section:arbitrary}[arbitrary]}. + + Underneath the hood, generated function values have a table-based representation. + They therefore need to be applied in a special way, e.g., with {!Fn.apply}. +*) + +(** {2 Observing arguments} *) + +module Observable : sig + (** 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. + + @since 0.6 + *) + + (** An observable for ['a], packing a printer and other things. *) + type -'a t + + val equal : 'a t -> 'a -> 'a -> bool + val hash : 'a t -> 'a -> int + val print : 'a t -> 'a Print.t + + val unit : unit t + val bool : bool t + val int : int t + val float : float t + val string : string t + val bytes : bytes t (** @since 0.20 *) + val char : char t + + val make : + ?eq:('a -> 'a -> bool) -> + ?hash:('a -> int) -> + 'a Print.t -> + 'a t + + val map : ('a -> 'b) -> 'b t -> 'a t + + val option : 'a t -> 'a option t + val list : 'a t -> 'a list t + val array : 'a t -> 'a array t + + val pair : 'a t -> 'b t -> ('a * 'b) t + val triple : 'a t -> 'b t -> 'c t -> ('a * 'b * 'c) t + val quad : 'a t -> 'b t -> 'c t -> 'd t -> ('a * 'b * 'c * 'd) t +end + +(** {2 Deprecated function generator combinators } *) + +val fun1_unsafe : 'a arbitrary -> 'b arbitrary -> ('a -> 'b) arbitrary +(** Generator of functions of arity 1. + The functions are always pure and total functions: + - when given the same argument (as decided by Stdlib.(=)), it returns the same value + - it never does side effects, like printing or never raise exceptions etc. + The functions generated are really printable. + + renamed from {!fun1} since 0.6 + + @deprecated use {!fun_} instead. + + @since 0.6 +*) + +val fun2_unsafe : 'a arbitrary -> 'b arbitrary -> 'c arbitrary -> ('a -> 'b -> 'c) arbitrary +(** Generator of functions of arity 2. The remark about [fun1] also apply + here. + renamed from {!fun2} since 0.6 + @deprecated use {!fun_} instead since 0.6 +*) + +type _ fun_repr +(** Internal data for functions. A ['f fun_] is a function + of type ['f], fundamentally. *) + +(** 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)) ]} *) type _ fun_ = | Fun : 'f fun_repr * 'f -> 'f fun_ -(** Utils on functions - @since 0.6 *) module Fn : sig + (** A utility module of helpers for printing, shrinking, and applying generated function values. + @since 0.6 *) + type 'a t = 'a fun_ val print : _ t Print.t @@ -1589,6 +1699,9 @@ module Fn : sig val apply : 'f t -> 'f end + +(** {2 Defining function generators } *) + val fun1 : 'a Observable.t -> 'b arbitrary -> ('a -> 'b) fun_ arbitrary (** [fun1 o ret] makes random functions that take an argument observable via [o] and map to random values generated from [ret]. @@ -1597,6 +1710,38 @@ val fun1 : 'a Observable.t -> 'b arbitrary -> ('a -> 'b) fun_ arbitrary (shrinking will be faster). @since 0.6 *) +val fun2 : + 'a Observable.t -> + 'b Observable.t -> + 'c arbitrary -> + ('a -> 'b -> 'c) fun_ arbitrary +(** @since 0.6 *) + +val fun3 : + 'a Observable.t -> + 'b Observable.t -> + 'c Observable.t -> + 'd arbitrary -> + ('a -> 'b -> 'c -> 'd) fun_ arbitrary +(** @since 0.6 *) + +val fun4 : + 'a Observable.t -> + 'b Observable.t -> + 'c Observable.t -> + 'd Observable.t -> + 'e arbitrary -> + ('a -> 'b -> 'c -> 'd -> 'e) fun_ arbitrary +(** @since 0.6 *) + + +(** {2 Tuples of observables } + + To circumvent the arity boundaries of {!fun1}, ..., {!fun4}, one can instead + define uncurried functions, instead accepting a tuple argument. A resulting + function then needs to be applied with {!fun_nary}. +*) + module Tuple : sig (** Heterogeneous tuple, used to pass any number of arguments to a function. *) @@ -1634,82 +1779,3 @@ val fun_nary : 'a Tuple.obs -> 'b arbitrary -> ('a Tuple.t -> 'b) fun_ arbitrary fun_nary Tuple.(O.int @-> O.float @-> O.string @-> o_nil) bool) ]} @since 0.6 *) - -val fun2 : - 'a Observable.t -> - 'b Observable.t -> - 'c arbitrary -> - ('a -> 'b -> 'c) fun_ arbitrary -(** @since 0.6 *) - -val fun3 : - 'a Observable.t -> - 'b Observable.t -> - 'c Observable.t -> - 'd arbitrary -> - ('a -> 'b -> 'c -> 'd) fun_ arbitrary -(** @since 0.6 *) - -val fun4 : - 'a Observable.t -> - 'b Observable.t -> - 'c Observable.t -> - 'd Observable.t -> - 'e arbitrary -> - ('a -> 'b -> 'c -> 'd -> 'e) fun_ arbitrary -(** @since 0.6 *) - -val oneofl : ?print:'a Print.t -> ?collect:('a -> string) -> - 'a list -> 'a arbitrary -(** Pick an element randomly in the list. *) - -val oneofa : ?print:'a Print.t -> ?collect:('a -> string) -> - 'a array -> 'a arbitrary -(** Pick an element randomly in the array. *) - -val oneof : 'a arbitrary list -> 'a arbitrary -(** Pick a generator among the list, randomly. - @deprecated this function is badly specified and will not use shrinkers - appropriately. Consider using {!Gen.oneof} and then {!make} to build - a well behaved arbitrary instance. -*) - -val always : ?print:'a Print.t -> 'a -> 'a arbitrary -(** Always return the same element. *) - -val frequency : ?print:'a Print.t -> ?small:('a -> int) -> - ?shrink:'a Shrink.t -> ?collect:('a -> string) -> - (int * 'a arbitrary) list -> 'a arbitrary -(** Similar to {!oneof} but with frequencies. *) - -val frequencyl : ?print:'a Print.t -> ?small:('a -> int) -> - (int * 'a) list -> 'a arbitrary -(** Same as {!oneofl}, but each element is paired with its frequency in - the probability distribution (the higher, the more likely). *) - -val frequencya : ?print:'a Print.t -> ?small:('a -> int) -> - (int * 'a) array -> 'a arbitrary -(** Same as {!frequencyl}, but with an array. *) - -val map : ?rev:('b -> 'a) -> ('a -> 'b) -> 'a arbitrary -> 'b arbitrary -(** [map f a] returns a new arbitrary instance that generates values using - [a#gen] and then transforms them through [f]. - @param rev if provided, maps values back to type ['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). -*) - -val map_same_type : ('a -> 'a) -> 'a arbitrary -> 'a arbitrary -(** 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) arbitrary -(** [map_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. - @param print optional printer for the [f]'s output. -*) From 051a8dde6c6944e6eef81042bd51f8ddd3bc6ba8 Mon Sep 17 00:00:00 2001 From: Jan Midtgaard Date: Tue, 10 Dec 2024 11:02:35 +0100 Subject: [PATCH 11/15] Remove unmatched paren in Tuple example --- src/core/QCheck.mli | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/QCheck.mli b/src/core/QCheck.mli index fc3d8b25..79e8cf29 100644 --- a/src/core/QCheck.mli +++ b/src/core/QCheck.mli @@ -1776,6 +1776,6 @@ val fun_nary : 'a Tuple.obs -> 'b arbitrary -> ('a Tuple.t -> 'b) fun_ arbitrary Example: {[ let module O = Observable in - fun_nary Tuple.(O.int @-> O.float @-> O.string @-> o_nil) bool) + fun_nary Tuple.(O.int @-> O.float @-> O.string @-> o_nil) bool ]} @since 0.6 *) From 2f5e605b2846977bf5e122950f490c90ded900d3 Mon Sep 17 00:00:00 2001 From: Jan Midtgaard Date: Tue, 10 Dec 2024 11:07:36 +0100 Subject: [PATCH 12/15] Address warnings in QCheck.mli --- src/core/QCheck.mli | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/core/QCheck.mli b/src/core/QCheck.mli index 79e8cf29..7942b8b0 100644 --- a/src/core/QCheck.mli +++ b/src/core/QCheck.mli @@ -634,7 +634,8 @@ module Print : sig (** Printer for values of type ['a]. *) - val unit : unit t (** @since 0.6 *) + val unit : unit t + (** @since 0.6 *) val int : int t (** Integer printer. *) @@ -644,7 +645,9 @@ module Print : sig val char : char t (** Character printer. *) - val bytes : bytes t (** Bytes printer. @since 0.20 *) + val bytes : bytes t + (** Bytes printer. + @since 0.20 *) val string : string t (** String printer. *) @@ -1417,8 +1420,8 @@ end (** {2 Defining Tests } *) (** Module related to individual tests. - @since 0.18 most of it moved to {!QCheck2}, - and the type ['a cell] was made a private implementation detail. + Since 0.18 most of it moved to {!QCheck2}, + and the type ['a cell] was made a private implementation detail. *) module Test : sig type res = QCheck2.Test.res = From 4b952125b929642137539beade6b18a3004d2110 Mon Sep 17 00:00:00 2001 From: Jan Midtgaard Date: Tue, 10 Dec 2024 12:03:03 +0100 Subject: [PATCH 13/15] Documentation pass over QCheck2.mli --- src/core/QCheck2.mli | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/src/core/QCheck2.mli b/src/core/QCheck2.mli index e686823e..095a8e4b 100644 --- a/src/core/QCheck2.mli +++ b/src/core/QCheck2.mli @@ -93,6 +93,21 @@ content will appear. *) @since 0.18 *) +(** {1 Generators, printers, and shrinkers in QCheck2 } + +The {!Gen} module offers combinators to build compositive generators for complex +data types. + +To print counter-examples, {!Test.make} accepts a [print] function, turning a +test-case into a [string] for printing on the console. The {!Print} module +offers combinators to build such printers. + +The {!Tree} module defines the lazy tree type underlying integrated shrinking. + +The {!Shrink} module contains utility functions for defining shrinkers. +*) + + (** A tree represents a generated value and its successive shrunk values. *) module Tree : sig (** Conceptually a pseudo-randomly generated value is packaged with its shrunk values. @@ -1256,6 +1271,19 @@ module Shrink : sig end +(** {1 Generating Functions} + + The [QCheck2] module supports generation of pure function values. + The implementation is inspired from {:https://blogs.janestreet.com/quickcheck-for-core/} + and {{:https://dl.acm.org/doi/abs/10.1145/2364506.2364516}Koen Claessen's "Shrinking and Showing Functions"}. + + Generated function arguments are of type {!Observable.t} and function results are of type + {!Gen.t}. + + Underneath the hood, generated function values have a table-based representation. + They therefore need to be applied in a special way, e.g., with {!Fn.apply}. +*) + (** An observable is a random function {i argument}. *) module Observable : sig (** @@ -1481,9 +1509,10 @@ val fun_nary : 'a Tuple.obs -> ?print:('b Print.t) -> 'b Gen.t -> ('a Tuple.t -> @since 0.6 *) -(** Utils on generated functions. - @since 0.6 *) module Fn : sig + (** A utility module of helpers for printing, shrinking, and applying generated function values. + @since 0.6 *) + val print : 'f fun_ Print.t (** [print f] prints the implementation of generated function [f]. @@ -1498,7 +1527,7 @@ module Fn : sig end -(** {2 Assumptions} *) +(** {1 Assumptions} *) val assume : bool -> unit (** [assume cond] checks the precondition [cond], and does nothing From 908173db6956e051a29ebd74b54a5c9c22beb428 Mon Sep 17 00:00:00 2001 From: Jan Midtgaard Date: Tue, 10 Dec 2024 12:07:02 +0100 Subject: [PATCH 14/15] Add a documentation frontpage, briefly describing QCheck and QCheck2 --- Makefile | 2 +- doc/dune | 3 +++ doc/index.mld | 31 +++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 doc/dune create mode 100644 doc/index.mld diff --git a/Makefile b/Makefile index aed037c1..a80c1aed 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ clean: @dune clean doc: - @dune build @doc + @dune build @doc doc/ example-test: @dune exec example/ounit/QCheck_test.exe diff --git a/doc/dune b/doc/dune new file mode 100644 index 00000000..d43522ad --- /dev/null +++ b/doc/dune @@ -0,0 +1,3 @@ +(documentation + (package qcheck-core) + (mld_files index)) diff --git a/doc/index.mld b/doc/index.mld new file mode 100644 index 00000000..5d9eea71 --- /dev/null +++ b/doc/index.mld @@ -0,0 +1,31 @@ +{0 qcheck-core} + +The [qcheck-core] opam package contains two libraries: + +- The [qcheck-core] library for defining property-based tests +- The [qcheck-core.runner] library for running property-based tests + +{1: The [qcheck-core] library} + +The [qcheck-core] library exposes two toplevel modules: + +- {!QCheck} is the initial property-based-testing module and +- {!QCheck2} is a newer property-based-testing module supporting integrated shrinking + +Of the two, {!QCheck} is the most battle-tested module. +{!QCheck2} on the other hand offers integrated shrinking, thus +removing the need for having to hand-write shrinkers. + +{!QCheck} tests can be ported to {!QCheck2} by following the +{{!QCheck2.migration_qcheck2}migration guide}. Please +file an issue if you encounter problems using either of the two +modules. + +{1: The [qcheck-core.runner] library} + +The entry point of the [qcheck-core.runner] library is the {!QCheck_base_runner} module. + +One can run a list of property-based tests by calling either + +- {!QCheck_base_runner.run_tests}, which accepts a number of optional arguments, or +- {!QCheck_base_runner.run_tests_main}, which can be controlled via command-line arguments From da09a2a02c9f9748510c9b6c46b3fae8d506625e Mon Sep 17 00:00:00 2001 From: Jan Midtgaard Date: Tue, 10 Dec 2024 12:21:16 +0100 Subject: [PATCH 15/15] Add a CHANGELOG entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb23e9cb..8d4d2e08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## NEXT RELEASE +- Clean-up `QCheck` and `QCheck2` documentation pages - Add `exponential` generator to `QCheck`, `QCheck.Gen`, and `QCheck2.Gen` - Add `Shrink.bool` and use it in `QCheck.bool` - Remove unread `fun_gen` field from `QCheck2`'s `fun_repr_tbl` type