Skip to content

Commit

Permalink
Rehaul the function generation documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
Julien Debon committed May 31, 2021
1 parent 23ba6f0 commit 6a32880
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 105 deletions.
7 changes: 2 additions & 5 deletions src/core/QCheck2.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1099,13 +1099,10 @@ type 'f fun_repr =
- Test developers will only use the "real" function inside their tests (and ignore the function representation).
- During shrinking/printing, QCheck will ignore the "real" function and only use its representation.
*)
type _ fun_ =
| Fun : 'f fun_repr * 'f -> 'f fun_
type 'f fun_ = Fun of 'f fun_repr * 'f

(** Reifying functions *)
module Fn = struct
type 'a t = 'a fun_

let apply (Fun (_repr, real_function)) = real_function

(** [function_of_repr repr] creates the "real" function (that will be used in tests)
Expand Down Expand Up @@ -1152,7 +1149,7 @@ module Fn = struct
let gen_rep (obs : 'a Observable.t) (arb : 'b arbitrary) : ('a -> 'b) fun_repr Gen.t =
Gen.liftA2 (fun default_value poly_tbl -> mk_repr poly_tbl arb default_value) arb.gen (Poly_tbl.create obs arb 8)

let gen (obs : 'a Observable.t) (arb : 'b arbitrary) : ('a -> 'b) t Gen.t =
let gen (obs : 'a Observable.t) (arb : 'b arbitrary) : ('a -> 'b) fun_ Gen.t =
Gen.map make_ (gen_rep obs arb)
end

Expand Down
242 changes: 142 additions & 100 deletions src/core/QCheck2.mli
Original file line number Diff line number Diff line change
Expand Up @@ -1549,6 +1549,148 @@ val option : 'a arbitrary -> 'a option arbitrary
Shrinks towards {!None} then towards shrinks of [arb].
*)

(** {2 Functions} *)

(** Utils on combining function arguments. *)
module Tuple : sig
(** Heterogeneous tuple, used to pass any number of arguments to
a function. *)
type 'a t =
| Nil : unit t
| Cons : 'a * 'b t -> ('a * 'b) t

val nil : unit t
(** [nil] is {!Nil}. *)

val cons : 'a -> 'b t -> ('a * 'b) t
(** [cons] is {!Cons}. *)

type 'a obs
(** How to observe a {!t}.
See {!module:Observable} for more information on what
"observe" means in the QCheck. *)

val o_nil : unit obs
(** [o_nil] is the {!obs} equivalent of {!nil}. *)

val o_cons : 'a Observable.t -> 'b obs -> ('a * 'b) obs
(** [o_cons] is the {!obs} equivalent of {!cons}. *)

val observable : 'a obs -> 'a t Observable.t
(** [observable obs] returns the underlying observable of [obs]. *)

(** Infix {!module:Tuple} operators for convenience. *)
module Infix : sig
val (@::) : 'a -> 'b t -> ('a * 'b) t
(** Alias for {!cons}. *)

val (@->) : 'a Observable.t -> 'b obs -> ('a * 'b) obs
(** Alias for {!o_cons}. *)
end

include module type of Infix
end

type 'f fun_repr
(** Used by QCheck to shrink and print generated functions of type ['f] in case
of test failure. You cannot and should not use it yourself. See {!fun_} for more information. *)

(** A function packed with the data required to print/shrink it.
The idiomatic way to use any [fun_] arbitrary is to directly pattern match
on it to obtain the executable function.
For example (note the [Fun (_, f)] part):
{[
QCheck2.(Test.make
pair (fun1 Observable.int bool) (small_list int))
(fun (Fun (_, f), l) -> l = (List.rev_map f l |> List.rev l))
]}
In this example [f] is a generated function of type [int -> bool].
The ignored part [_] of [Fun (_, f)] is useless to you, but is used by
QCheck during shrinking/printing in case of test failure.
See also {!Fn} for utils to print and apply such a function.
*)
type 'f fun_ = Fun of 'f fun_repr * 'f

val fun1 : 'a Observable.t -> 'b arbitrary -> ('a -> 'b) fun_ arbitrary
(** [fun1 obs arb] generates random functions that take an argument observable
via [obs] and map to random values generated with [arb].
To write functions with multiple arguments, it's better to use {!Tuple}
or {!Observable.pair} rather than applying {!fun_} several times
(shrinking will be faster).
@since 0.6 *)

val fun2 :
'a Observable.t ->
'b Observable.t ->
'c arbitrary ->
('a -> 'b -> 'c) fun_ arbitrary
(** Specialized version of {!fun_nary} for functions of 2 arguments, for convenience.
@since 0.6 *)

val fun3 :
'a Observable.t ->
'b Observable.t ->
'c Observable.t ->
'd arbitrary ->
('a -> 'b -> 'c -> 'd) fun_ arbitrary
(** Specialized version of {!fun_nary} for functions of 3 arguments, for convenience.
@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
(** Specialized version of {!fun_nary} for functions of 4 arguments, for convenience.
@since 0.6 *)

val fun_nary : 'a Tuple.obs -> 'b arbitrary -> ('a Tuple.t -> 'b) fun_ arbitrary
(** [fun_nary tuple_obs arb] generates random n-ary functions. Arguments are observed
using [tuple_obs] and return values are generated using [arb].
Example (the property is wrong as a random function may return [false], this is for
the sake of demonstrating the syntax):
{[
let module O = Observable in
Test.make
(fun_nary Tuple.(O.int @-> O.float @-> O.string @-> o_nil) bool)
(fun (Fun (_, f)) -> f Tuple.(42 @:: 17.98 @:: "foobar" @:: nil))
]}
Note that this particular example can be simplified using {!fun3} directly:
{[
let module O = Observable in
Test.make
(fun3 O.int O.float O.string bool)
(fun (Fun (_, f)) -> f 42 17.98 "foobar")
]}
@since 0.6 *)

(** Utils on generated functions.
@since 0.6 *)
module Fn : sig
val print : 'f fun_ Print.t
(** [print f] prints the implementation of generated function [f].
The implementation always contains a default case, represented as [_].
Note that printing a function {i before} it was called in the test may not print the full implementation.
*)

val apply : 'f fun_ -> 'f
(** [apply f] returns the underlying function to be used in tests. This is an alias for
deconstructing as documented in {!fun_}. *)
end

(** {2 Combining arbitraries} *)

val pair : 'a arbitrary -> 'b arbitrary -> ('a * 'b) arbitrary
Expand Down Expand Up @@ -1937,106 +2079,6 @@ val find_example_gen :
@raise No_example_found if no example was found within [count] tries.
@since 0.6 *)

type _ fun_repr
(** Used by QCheck to shrink and print generated functions in case
of test failure. You cannot and should not use it yourself. *)

(** 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:
{[
QCheck2.(Test.make
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
type 'a t = 'a fun_

val print : _ t Print.t

val apply : 'f t -> 'f
end

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].
To write functions with multiple arguments, it's better to use {!Tuple}
or {!Observable.pair} rather than applying {!fun_} several times
(shrinking will be faster).
@since 0.6 *)

module Tuple : sig
(** Heterogeneous tuple, used to pass any number of arguments to
a function. *)
type 'a t =
| Nil : unit t
| Cons : 'a * 'b t -> ('a * 'b) t

val nil : unit t
val cons : 'a -> 'b t -> ('a * 'b) t

(** How to observe a {!'a t} *)
type 'a obs

val o_nil : unit obs
val o_cons : 'a Observable.t -> 'b obs -> ('a * 'b) obs

module Infix : sig
val (@::) : 'a -> 'b t -> ('a * 'b) t
(** Alias to {!cons}. *)

val (@->) : 'a Observable.t -> 'b obs -> ('a * 'b) obs
(** Alias to {!B_cons}. *)
end

include module type of Infix

val observable : 'a obs -> 'a t Observable.t
end

val fun_nary : 'a Tuple.obs -> 'b arbitrary -> ('a Tuple.t -> 'b) fun_ arbitrary
(** [fun_nary] makes random n-ary functions.
Example:
{[
let module O = Observable in
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. *)
Expand Down

0 comments on commit 6a32880

Please sign in to comment.