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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
type 'a t = Random.State.t -> 'a Tree.t
let pure x _ = Tree.pure x
let map f gen st = Tree.map f (gen st)
let ( >|= ) gen f = map f gen
let ap fgen xgen st =
let st' = Random.State.split st in
Tree.ap (fgen st) (xgen st')
let bind gen f st =
let st' = Random.State.split st in
Tree.bind (gen st) (fun x -> f x (Random.State.copy st'))
let ( >>= ) = bind
let ( let+ ) gen f = map f gen
let ( and+ ) a b st =
let st' = Random.State.split st in
Tree.liftA2 (fun x y -> (x, y)) (a st) (b st')
let ( let* ) = bind
let make_primitive ~gen ~shrink st = Tree.make_primitive shrink (gen st)
let unit _ = Tree.pure ()
let bool st =
if Random.State.bool st then Tree.Tree (true, Seq.return (Tree.pure false))
else Tree.pure false
let max_random_int = (1 lsl 30) - 1
let int_pos_raw st =
if Sys.word_size = 32 then Random.State.bits st
else
let top2_mask = 0b11 in
let left = (Random.State.bits st land top2_mask) lsl 60 in
let middle = Random.State.bits st lsl 30 in
let right = Random.State.bits st in
left lor middle lor right
let int_pos st =
let x = int_pos_raw st in
Tree.make_primitive (fun n -> Shrink.int_towards 0 n) x
let int st =
if Random.State.bool st then Tree.map (fun n -> -n - 1) (int_pos st)
else int_pos st
let int_bound upper st =
if upper < 0 then invalid_arg "Gen.int_bound: upper < 0";
if upper <= max_random_int - 1 then Random.State.int st (upper + 1)
else if upper = max_int then int_pos_raw st
else int_pos_raw st mod (upper + 1)
let pick_origin_in_range ~low ~high ~goal =
if goal < low then low else if goal > high then high else goal
let resolve_origin ~loc ~low ~high ~origin =
if origin < low then invalid_arg (loc ^ ": origin < low")
else if origin > high then invalid_arg (loc ^ ": origin > high")
else origin
let int_range ?origin low high st =
if high < low then invalid_arg "Gen.int_range: high < low";
let n =
if low = high then low
else if low >= 0 || high < 0 then
low + int_bound (high - low) st
else
let f_low = float_of_int low in
let f_high = float_of_int high in
let ratio = -.f_low /. (1.0 +. f_high -. f_low) in
if Random.State.float st 1.0 <= ratio then
if low = min_int then -int_pos_raw st - 1
else -int_bound (pred (-low)) st - 1
else int_bound high st
in
let default_origin = pick_origin_in_range ~low ~high ~goal:0 in
let origin =
resolve_origin ~loc:"Gen.int_range" ~low ~high
~origin:(Option.value origin ~default:default_origin)
in
Tree.make_primitive (fun x -> Shrink.int_towards origin x) n
let nat st =
let p = Random.State.float st 1.0 in
let x =
if p < 0.5 then Random.State.int st 10
else if p < 0.75 then Random.State.int st 100
else if p < 0.95 then Random.State.int st 1_000
else Random.State.int st 10_000
in
Tree.make_primitive (fun n -> Shrink.int_towards 0 n) x
let small_int st =
if Random.State.bool st then nat st else Tree.map Int.neg (nat st)
let int32 st =
let low = Int32.of_int (Random.State.bits st) in
let high = Int32.of_int (Random.State.bits st land 0x3) in
let bits = Int32.logor low (Int32.shift_left high 30) in
Tree.make_primitive (fun n -> Shrink.int32_towards 0l n) bits
let pick_origin_int32 ~low ~high ~goal =
if goal < low then low else if goal > high then high else goal
let int32_range ?origin low high st =
if high < low then invalid_arg "Gen.int32_range: high < low";
let range = Int64.sub (Int64.of_int32 high) (Int64.of_int32 low) in
let range = Int64.add range 1L in
let n =
if Int64.compare range (Int64.of_int max_random_int) <= 0 then
Int32.add low (Int32.of_int (Random.State.int st (Int64.to_int range)))
else
let f_range = Int64.to_float range in
let offset = Int64.of_float (Random.State.float st f_range) in
Int32.add low (Int64.to_int32 offset)
in
let origin =
pick_origin_int32 ~low ~high ~goal:(Option.value origin ~default:0l)
in
Tree.make_primitive (fun x -> Shrink.int32_towards origin x) n
let int64 st =
let low = Int64.of_int (Random.State.bits st) in
let mid = Int64.shift_left (Int64.of_int (Random.State.bits st)) 30 in
let high =
Int64.shift_left (Int64.of_int (Random.State.bits st land 0xF)) 60
in
let bits = Int64.(logor high (logor mid low)) in
Tree.make_primitive (fun n -> Shrink.int64_towards 0L n) bits
let int64_nonneg_raw st =
let low = Int64.of_int (Random.State.bits st) in
let mid = Int64.shift_left (Int64.of_int (Random.State.bits st)) 30 in
let high =
Int64.shift_left (Int64.of_int (Random.State.bits st land 0x7)) 60
in
Int64.(logor high (logor mid low))
let int64_bound upper st =
if upper < 0L then invalid_arg "Gen.int64_bound: upper < 0";
if Int64.equal upper Int64.max_int then int64_nonneg_raw st
else
let bound = Int64.add upper 1L in
Int64.rem (int64_nonneg_raw st) bound
let pick_origin_int64 ~low ~high ~goal =
if goal < low then low else if goal > high then high else goal
let int64_range ?origin low high st =
if high < low then invalid_arg "Gen.int64_range: high < low";
let n =
if low >= 0L || high < 0L then
let offset = int64_bound (Int64.sub high low) st in
Int64.add low offset
else
let f_low = Int64.to_float low in
let f_high = Int64.to_float high in
let ratio = -.f_low /. (1.0 +. f_high -. f_low) in
if Random.State.float st 1.0 <= ratio then
if Int64.equal low Int64.min_int then
Int64.sub (Int64.neg (int64_nonneg_raw st)) 1L
else
let offset = int64_bound (Int64.pred (Int64.neg low)) st in
Int64.neg (Int64.succ offset)
else int64_bound high st
in
let origin =
pick_origin_int64 ~low ~high ~goal:(Option.value origin ~default:0L)
in
Tree.make_primitive (fun x -> Shrink.int64_towards origin x) n
let nativeint st = Tree.map Nativeint.of_int (int st)
let float st =
let bits =
let left = Int64.(shift_left (of_int (Random.State.bits st land 0xF)) 60) in
let middle = Int64.(shift_left (of_int (Random.State.bits st)) 30) in
let right = Int64.of_int (Random.State.bits st) in
Int64.(logor left (logor middle right))
in
let x = Int64.float_of_bits bits in
Tree.make_primitive (fun f -> Shrink.float_towards 0.0 f) x
let float_range ?origin low high st =
if high < low then invalid_arg "Gen.float_range: high < low";
if high -. low > max_float then
invalid_arg "Gen.float_range: high -. low > max_float";
let x = low +. Random.State.float st (high -. low) in
let default_origin = pick_origin_in_range ~low ~high ~goal:0.0 in
let origin =
resolve_origin ~loc:"Gen.float_range" ~low ~high
~origin:(Option.value origin ~default:default_origin)
in
Tree.make_primitive (fun f -> Shrink.float_towards origin f) x
let char_range ?origin low high =
let lo = Char.code low in
let hi = Char.code high in
let origin = Option.map Char.code origin in
map Char.chr (int_range ?origin lo hi)
let char st =
let c = Random.State.int st 256 in
let shrink i = Shrink.int_towards (Char.code 'a') i in
Tree.map Char.chr (Tree.make_primitive shrink c)
let option ?(ratio = 0.85) gen st =
let p = Random.State.float st 1.0 in
if p < 1.0 -. ratio then Tree.pure None else Tree.opt (gen st)
let result ?(ratio = 0.75) ok_gen err_gen st =
let p = Random.State.float st 1.0 in
if p < 1.0 -. ratio then Tree.map (fun e -> Error e) (err_gen st)
else Tree.map (fun o -> Ok o) (ok_gen st)
let either ?(ratio = 0.5) left_gen right_gen st =
let p = Random.State.float st 1.0 in
if p < ratio then Tree.map (fun x -> Either.Left x) (left_gen st)
else Tree.map (fun x -> Either.Right x) (right_gen st)
let list_size size_gen gen st =
let st' = Random.State.split st in
Tree.bind (size_gen st) (fun size ->
let st' = Random.State.copy st' in
let rec build n acc =
if n <= 0 then
List.fold_left
(fun acc elt_tree -> Tree.liftA2 List.cons elt_tree acc)
(Tree.pure []) acc
else build (n - 1) (gen st' :: acc)
in
build size [])
let list_ignore_size_tree size_gen gen st =
let st' = Random.State.split st in
let size = Tree.root (size_gen st) in
let st' = Random.State.copy st' in
let rec build n acc =
if n <= 0 then
let l = List.rev acc in
Tree.Tree (List.map Tree.root l, Tree.build_list_shrink_tree l)
else build (n - 1) (gen st' :: acc)
in
build size []
let list gen = list_ignore_size_tree nat gen
let array gen st = Tree.map Array.of_list (list gen st)
let pair a b =
let+ x = a and+ y = b in
(x, y)
let triple a b c =
let+ x, y = pair a b and+ z = c in
(x, y, z)
let quad a b c d =
let+ x, y, z = triple a b c and+ w = d in
(x, y, z, w)
let oneof gens st =
match gens with
| [] -> invalid_arg "Gen.oneof: empty list"
| _ ->
let i_tree =
Tree.make_primitive
(fun i -> Shrink.int_towards 0 i)
(Random.State.int st (List.length gens))
in
Tree.bind i_tree (fun i -> (List.nth gens i) st)
let oneofl xs st =
match xs with
| [] -> invalid_arg "Gen.oneofl: empty list"
| _ ->
let i_tree =
Tree.make_primitive
(fun i -> Shrink.int_towards 0 i)
(Random.State.int st (List.length xs))
in
Tree.map (List.nth xs) i_tree
let frequency weighted_gens st =
match weighted_gens with
| [] -> invalid_arg "Gen.frequency: empty list"
| _ ->
let total = List.fold_left (fun acc (w, _) -> acc + w) 0 weighted_gens in
if total < 1 then invalid_arg "Gen.frequency: total weight < 1";
let pick = Random.State.int st total in
let rec choose acc = function
| [] -> assert false
| (w, g) :: rest ->
if pick < acc + w then g st else choose (acc + w) rest
in
choose 0 weighted_gens
let string_size size_gen char_gen st =
let size_tree = size_gen st in
Tree.bind size_tree (fun size ->
let st' = Random.State.copy st in
let chars = List.init size (fun _ -> char_gen st') in
Tree.map
(fun char_list ->
let a = Array.of_list char_list in
String.init (Array.length a) (Array.get a))
(Tree.sequence_list chars))
let string_of char_gen = string_size nat char_gen
let string = string_of char
let bytes st = map Bytes.of_string string st
let sized f = bind nat f
let no_shrink gen st =
let (Tree.Tree (x, _)) = gen st in
Tree.pure x
let add_shrink_invariant p gen st = Tree.add_shrink_invariant p (gen st)
let delay f st = f () st
let fix f =
let rec gen st = f gen st in
gen
let find ?(count = 100) ~f gen st =
let rec loop n =
if n <= 0 then None
else
let (Tree.Tree (x, _)) = gen st in
if f x then Some x else loop (n - 1)
in
loop count