/
crowbar.mli
276 lines (215 loc) · 9.6 KB
/
crowbar.mli
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
(** {1:top Types } *)
type 'a gen
(** ['a gen] knows how to generate ['a] for use in Crowbar tests. *)
type ('k, 'res) gens =
| [] : ('res, 'res) gens
| (::) : 'a gen * ('k, 'res) gens -> ('a -> 'k, 'res) gens
(** multiple generators are passed to functions using a listlike syntax.
for example, [map [int; int] (fun a b -> a + b)] *)
type 'a printer = Format.formatter -> 'a -> unit
(** pretty-printers for items generated by Crowbar; useful for the user in
translating test failures into bugfixes. *)
(**/**)
(* re-export stdlib's list
We only want to override [] syntax in the argument to Map *)
type nonrec +'a list = 'a list = [] | (::) of 'a * 'a list
(**/**)
(** {1:generators Generators } *)
(** {2:simple_generators Simple Generators } *)
val int : int gen
(** [int] generates an integer ranging from min_int to max_int, inclusive.
If you need integers from a smaller domain, consider using {!range}. *)
val uint8 : int gen
(** [uint8] generates an unsigned byte, ranging from 0 to 255 inclusive. *)
val int8 : int gen
(** [int8] generates a signed byte, ranging from -128 to 127 inclusive. *)
val uint16 : int gen
(** [uint16] generates an unsigned 16-bit integer,
ranging from 0 to 65535 inclusive. *)
val int16 : int gen
(** [int16] generates a signed 16-bit integer,
ranging from -32768 to 32767 inclusive. *)
val int32 : Int32.t gen
(** [int32] generates a 32-bit signed integer. *)
val int64 : Int64.t gen
(** [int64] generates a 64-bit signed integer. *)
val float : float gen
(** [float] generates a double-precision floating-point number. *)
val char : char gen
(** [char] generates a char. *)
val uchar : Uchar.t gen
(** [uchar] generates a Unicode scalar value *)
val bytes : string gen
(** [bytes] generates a string of arbitrary length (including zero-length strings). *)
val bytes_fixed : int -> string gen
(** [bytes_fixed length] generates a string of the specified length. *)
val bool : bool gen
(** [bool] generates a yes or no answer. *)
val range : ?min:int -> int -> int gen
(** [range ?min n] is a generator for integers between [min] (inclusive)
and [min + n] (exclusive). Default [min] value is 0.
[range ?min n] will raise [Invalid_argument] for [n <= 0].
*)
(** {2:generator_functions Functions on Generators } *)
val map : ('f, 'a) gens -> 'f -> 'a gen
(** [map gens map_fn] provides a means for creating generators using other
generators' output. For example, one might generate a Char.t from a
{!uint8}:
{[
open Crowbar
let char_gen : Char.t gen = map [uint8] Char.chr
]}
*)
val unlazy : 'a gen Lazy.t -> 'a gen
(** [unlazy gen] forces the generator [gen]. It is useful when defining
generators for recursive data types:
{[
open Crowbar
type a = A of int | Self of a
let rec a_gen = lazy (
choose [
map [int] (fun i -> A i);
map [(unlazy a_gen)] (fun s -> Self s);
])
let lazy a_gen = a_gen
]}
*)
val fix : ('a gen -> 'a gen) -> 'a gen
(** [fix fn] applies the function [fn]. It is useful when defining generators
for recursive data types:
{[
open Crowbar
type a = A of int | Self of a
let rec a_gen = fix (fun a_gen ->
choose [
map [int] (fun i -> A i);
map [a_gen] (fun s -> Self s);
])
]}
*)
val const : 'a -> 'a gen
(** [const a] always generates [a]. *)
val choose : 'a gen list -> 'a gen
(** [choose gens] chooses a generator arbitrarily from [gens]. *)
val option : 'a gen -> 'a option gen
(** [option gen] generates either [None] or [Some x], where [x] is the item
generated by [gen]. *)
val pair : 'a gen -> 'b gen -> ('a * 'b) gen
(** [pair gena gen] generates (a, b)
where [a] is generated by [gena] and [b] by [genb]. *)
val result : 'a gen -> 'b gen -> ('a, 'b) result gen
(** [result gena genb] generates either [Ok va] or [Error vb],
where [va], [vb] are generated by [gena], [genb] respectively. *)
val list : 'a gen -> 'a list gen
(** [list gen] makes a generator for lists using [gen]. Lists may be empty; for
non-empty lists, use {!list1}. *)
val list1 : 'a gen -> 'a list gen
(** [list1 gen] makes non-empty list generators. For potentially empty lists,
use {!list}.*)
val array : 'a gen -> 'a array gen
(** [array gen] makes a generator for arrays using [gen]. Arrays may be empty; for
non-empty arrays, use {!array1}. *)
val array1 : 'a gen -> 'a array gen
(** [array1 gen] makes non-empty array generators. For potentially empty arrays,
use {!array}.*)
val shuffle : 'a list -> 'a list gen
(** [shuffle l] generates random permutations of [l]. *)
val concat_gen_list : string gen -> string gen list -> string gen
(** [concat_gen_list sep l] concatenates a list of string gen [l] inserting the
separator [sep] between each *)
val with_printer : 'a printer -> 'a gen -> 'a gen
(** [with_printer printer gen] generates the same values as [gen]. If [gen]
is used to create a failing test case and the test was reached by
calling [check_eq] without [pp] set, [printer] will be used to print the
failing test case. *)
val dynamic_bind : 'a gen -> ('a -> 'b gen) -> 'b gen
(** [dynamic_bind gen f] is a monadic bind, it allows to express the
generation of a value whose generator itself depends on
a previously generated value. This is in contrast with [map gen f],
where no further generation happens in [f] after [gen] has
generated an element.
An typical example where this sort of dependencies is required is
a serialization library exporting combinators letting you build
values of the form ['a serializer]. You may want to test this
library by first generating a pair of a serializer and generator
['a serializer * 'a gen] for arbitrary ['a], and then generating
values of type ['a] depending on the (generated) generator to test
the serializer. There is such an example in the
[examples/serializer/] directory of the Crowbar implementation.
Because the structure of a generator built with [dynamic_bind] is
opaque/dynamic (it depends on generated values), the Crowbar
library cannot analyze its statically
(without generating anything) -- the generator is opaque to the
library, hidden in a function. In particular, many optimizations or
or fuzzing techniques based on generator analysis are
impossible. As a client of the library, you should avoid
[dynamic_bind] whenever it is not strictly required to express
a given generator, so that you can take advantage of these features
(present or future ones). Use the least powerful/complex
combinators that suffice for your needs.
*)
(** {1:printing Printing } *)
(* Format.fprintf, renamed *)
val pp : Format.formatter -> ('a, Format.formatter, unit) format -> 'a
val pp_int : int printer
val pp_int32 : Int32.t printer
val pp_int64 : Int64.t printer
val pp_float : float printer
val pp_bool : bool printer
val pp_string : string printer
val pp_list : 'a printer -> 'a list printer
val pp_option : 'a printer -> 'a option printer
(** {1:testing Testing} *)
val add_test :
?name:string -> ('f, unit) gens -> 'f -> unit
(** [add_test name generators test_fn] adds [test_fn] to the list of eligible
tests to be run when the program is invoked. At runtime, random data will
be sent to [generators] to create the input necessary to run [test_fn]. Any
failures will be printed annotated with [name]. *)
(** {2:aborting Aborting Tests} *)
val guard : bool -> unit
(** [guard b] aborts a test if [b] is false. The test will not be recorded
or reported as a failure. *)
val bad_test : unit -> 'a
(** [bad_test ()] aborts a test. The test will not be recorded or reported
as a failure. *)
val nonetheless : 'a option -> 'a
(** [nonetheless o] aborts a test if [o] is None. The test will not be recorded
or reported as a failure. *)
(** {2:failing Failing} *)
val fail : string -> 'a
(** [fail message] generates a test failure and prints [message]. *)
val failf : ('a, Format.formatter, unit, _) format4 -> 'a
(** [failf format ...] generates a test failure and prints the message
specified by the format string [format] and the following arguments.
It is set up so that [%a] calls for an ['a printer] and an ['a] value. *)
(** {2:asserting Asserting Properties} *)
val check : bool -> unit
(** [check b] generates a test failure if [b] is false. No useful information
will be printed in this case. *)
val check_eq : ?pp:('a printer) -> ?cmp:('a -> 'a -> int) -> ?eq:('a -> 'a -> bool) ->
'a -> 'a -> unit
(** [check_eq pp cmp eq x y] evaluates whether x and y are equal, and if they
are not, raises a failure and prints an error message.
Equality is evaluated as follows:
{ol
{- use a provided [eq]}
{- if no [eq] is provided, use a provided [cmp]}
{- if neither [eq] nor [cmp] is provided, use Stdlib.compare}}
If [pp] is provided, use this to print [x] and [y] if they are not equal.
If [pp] is not provided, a best-effort printer will be generated from the
printers for primitive generators and any printers registered with
[with_printer] and used. *)
(** {1:syntax Syntax module } *)
module Syntax : sig
val ( let+ ) : 'a gen -> ('a -> 'b) -> 'b gen
(** [let+ x = gen in e] is equivalent to [map [ gen ] (fun x -> e)]. *)
val ( let* ) : 'a gen -> ('a -> 'b gen) -> 'b gen
(** Equivalent to {!dynamic_bind}.
[let* x = gen in e] is equivalent to [dynamic_bind gen (fun x -> e)]. *)
val ( and+ ) : 'a gen -> 'b gen -> ('a * 'b) gen
(** Equivalent to {!pair}.
[let+ x = gen_x and+ y = gen_y and+ z = gen_z in e]
is equivalent to
[ map [pair (pair gen_x gen_y) gen_z)] (fun ((x, y), z) -> e) ]. *)
end