-
Notifications
You must be signed in to change notification settings - Fork 39
/
Copy pathQCheck2.mli
1778 lines (1320 loc) · 59.8 KB
/
QCheck2.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
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
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
(*
QCheck: Random testing for OCaml
copyright (c) 2013-2017, Guillaume Bury, Simon Cruanes, Vincent Hugot,
Jan Midtgaard, Julien Debon, Valentin Chaboche
all rights reserved.
*)
(* Keep the following title alone in its documentation block as it is specially treated by Odoc: it doesn't appear
in the Contents menu on the left. The next documentation block with all the actual
content will appear. *)
(** {1 QuickCheck-inspired property-based testing} *)
(** {1 Introduction}
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,
so that the property is tried and checked on a number of random instances.
This explains the organization of this module:
- {!Gen} is used to describe how to generate random values.
Auxiliary module {!Print} can be used along with {!Test.make}
to build one's own generator instances.
- {!Test} is used to describe a single test, that is, a property of
type ['a -> bool] combined with an ['a Gen.t] that is used to generate
the test cases for this property. Optional parameters
allow to specify the random generator state, number of instances to generate
and test, etc.
💡 If you are migrating from QCheck, check the {{!section:migration_qcheck2} migration guide} below.
{1 Examples}
- "{!List.rev} is involutive" (the test passes so [check_exn] returns [()]):
{[
let test =
QCheck2.(Test.make ~count:1000
~print:Print.(list int)
Gen.(list int)
(fun l -> List.rev (List.rev l) = l));;
QCheck2.Test.check_exn test;;
]}
- "All lists are sorted" (false property that will fail):
{ul
{- QCheck tests this property on random lists and finds a counter-example}
{- QCheck then looks for the smallest counter-example possible (here [[1; 0]])
to help you find the problem (called "shrinking")}
}
{[
let test = QCheck2.(
Test.make
~name:"All lists are sorted"
~count:10_000
~print:Print.(list int)
Gen.(list small_nat)
(fun l -> l = List.sort compare l));;
QCheck2.Test.check_exn test;;
Exception:
test `All lists are sorted` failed on ≥ 1 cases:
[1; 0] (after 5 shrink steps)
]}
- Generate 20 random trees using {! Gen.fix} :
{[
type tree = Leaf of int | Node of tree * tree
let leaf x = Leaf x
let node x y = Node (x,y)
let tree_gen = QCheck2.Gen.(sized @@ fix
(fun self n -> match n with
| 0 -> map leaf nat
| n ->
frequency
[1, map leaf nat;
2, map2 node (self (n/2)) (self (n/2))]
));;
QCheck2.Gen.generate ~n:20 tree_gen;;
]}
@since 0.18
*)
(** 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.
This coupling - called "integrated shrinking" - in a single type has a major benefit:
most generators get shrinking "for free" by composing from smaller generators, and shrinking
does not break invariants (e.g. shrinks of a positive number are always positive).
*)
type 'a t
(** A tree of random generated values, where the root contains the value used for the test,
and the sub-trees contain shrunk values (as trees, to be able to shrink several times a value)
used if the test fails. *)
val root : 'a t -> 'a
(** [root tree] returns the root value of the tree of generated values [t]. *)
val children : 'a t -> 'a t Seq.t
(** [children tree] returns the direct sub-trees of the tree of generated values [t]. *)
val pp : ?depth : int -> (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a t -> unit
(** [pp ?depth pp_a ppf tree] pretty-prints the tree of generated values [tree] using the
pretty-print formatter [ppf]. Values of type ['a] will be printed using the given
pretty-printer [pp_a].
As a tree [t] can be potentially huge when fully evaluated, you can control the maximum
depth the printer goes with [depth].
- [None] means "everything"
- [0] means "only the root"
- [1] means "the root and its direct shrinks"
- [2] means "the root, its direct shrinks, and the shrinks of its shrinks"
- etc.
*)
end
(** A generator is responsible for generating pseudo-random values and provide shrinks (smaller
values) when a test fails. *)
module Gen : sig
(** This module provides some of the most important features of QCheck:
- {{!section:primitive_generators} primitive generators}
- {{!section:composing_generators} generator compositions}
*)
type 'a t
(** A random generator for values of type ['a]. *)
type 'a sized = int -> 'a t
(** Random generator with a size bound. *)
(** {3:primitive_generators Primitive generators} *)
val unit : unit t
(** The unit generator.
Does not shrink.
*)
val bool : bool t
(** The boolean generator.
Shrinks towards [false].
*)
val int : int t
(** Generates integers uniformly.
Shrinks towards [0].
*)
val pint : ?origin : int -> int t
(** Generates non-strictly positive integers uniformly ([0] included).
Shrinks towards [origin] if specified, otherwise towards [0]. *)
val small_nat : int t
(** Small positive integers (< [100], [0] included).
Non-uniform: smaller numbers are more likely than bigger numbers.
Shrinks towards [0].
@since 0.5.1 *)
val nat : int t
(** Generates natural numbers (< [10_000]).
Non-uniform: smaller numbers are more likely than bigger numbers.
Shrinks towards [0].
*)
val big_nat : int t
(** Generates natural numbers, possibly large (< [1_000_000]).
Non-uniform: smaller numbers are more likely than bigger numbers.
Shrinks towards [0].
@since 0.10 *)
val neg_int : int t
(** Generates non-strictly negative integers ([0] included).
Non-uniform: smaller numbers (in absolute value) are more likely than bigger numbers.
Shrinks towards [0].
*)
val small_int : int t
(** Small UNSIGNED integers, for retrocompatibility.
Shrinks towards [0].
@deprecated use {!small_nat}. *)
val small_signed_int : int t
(** Small SIGNED integers, based on {!small_nat}.
Non-uniform: smaller numbers (in absolute value) are more likely than bigger numbers.
Shrinks towards [0].
@since 0.5.2 *)
val small_int_corners : unit -> int t
(** As {!small_int}, but each newly created generator starts with
a list of corner cases before falling back on random generation. *)
val int32 : int32 t
(** Generates uniform {!int32} values.
Shrinks towards [0l].
*)
val ui32 : int32 t
(** Generates {!int32} values.
Shrinks towards [0l].
@deprecated use {!val:int32} instead, the name is wrong, values {i are} signed.
*)
val int64 : int64 t
(** Generates uniform {!int64} values.
Shrinks towards [0L].
*)
val ui64 : int64 t
(** Generates {!int64} values.
Shrinks towards [0L].
@deprecated use {!val:int64} instead, the name is wrong, values {i are} signed.
*)
val float : float t
(** Generates floating point numbers.
Shrinks towards [0.].
*)
val pfloat : float t
(** Generates positive floating point numbers ([0.] included).
Shrinks towards [0.].
*)
val nfloat : float t
(** Generates negative floating point numbers. ([-0.] included).
Shrinks towards [-0.].
*)
val char : char t
(** Generates characters in the [0..255] range.
Shrinks towards ['a'].
*)
val printable : char t
(** Generates printable characters.
The exhaustive list of character codes is:
- [32] to [126], inclusive
- ['\n']
Shrinks towards ['a'].
*)
val numeral : char t
(** Generates numeral characters ['0'..'9'].
Shrinks towards ['0'].
*)
val string_size : ?gen:char t -> int t -> string t
(** Builds a string generator from a (non-negative) size generator.
Accepts an optional character generator (the default is {!char}).
Shrinks on the number of characters first, then on the characters.
*)
val string : string t
(** Builds a string generator. String size is generated by {!nat}.
The default character generator is {!char}.
See also {!string_of} and {!string_printable} for versions with
custom char generator.
Shrinks on the number of characters first, then on the characters.
*)
val string_of : char t -> string t
(** Builds a string generator using the given character generator.
Shrinks on the number of characters first, then on the characters.
@since 0.11 *)
val string_printable : string t
(** Builds a string generator using the {!printable} character generator.
Shrinks on the number of characters first, then on the characters.
@since 0.11 *)
val small_string : ?gen:char t -> string t
(** Builds a string generator, length is {!small_nat}.
Accepts an optional character generator (the default is {!char}).
Shrinks on the number of characters first, then on the characters.
*)
val pure : 'a -> 'a t
(** [pure a] creates a generator that always returns [a].
Does not shrink.
@since 0.8
*)
val return : 'a -> 'a t
(** Synonym for {!pure} *)
val make_primitive : gen : (Random.State.t -> 'a) -> shrink : ('a -> 'a Seq.t) -> 'a t
(** [make_primitive ~gen ~shrink] creates a generator from a function [gen] that creates
a random value (this function must only use the given {!Random.State.t} for randomness)
and a function [shrink] that, given a value [a], returns a lazy list of
"smaller" values (used when a test fails).
This lower-level function is meant to build generators for "primitive" types that can neither be
built with other primitive generators nor through composition, or to have more control on the
shrinking steps.
[shrink] must obey the following rules (for your own definition of "small"):
- [shrink a = Seq.empty] when [a] is the smallest possible value
- [shrink a] must return values strictly smaller than [a], ideally from smallest to largest (for
faster shrinking)
- [let rec loop a = match shrink a () with | Nil -> () | Cons (smaller_a, _) -> loop smaller_a]
must end for all values [a] of type ['a] (i.e. there must not be an infinite number of shrinking
steps).
⚠️ This is an unstable API as it partially exposes the implementation. In particular, the type of
[Random.State.t] may very well change in a future version, e.g. if QCheck switches to another
randomness library.
*)
val add_shrink_invariant : ('a -> bool) -> 'a t -> 'a t
(** [add_shrink_invariant f gen] returns a generator similar to [gen] except all shrinks satisfy [f].
This way it's easy to preserve invariants that are enforced by
generators, when shrinking values
@since 0.8
@deprecated is this function still useful? I feel like it is either useless (invariants
should already be part of the shrinking logic, not be added later) or a special,
incomplete case of {!Gen.t} being an Alternative (not implemented yet). For now we
keep it and wait for users feedback (hence deprecation to raise attention).
*)
(** {3 Ranges} *)
val int_bound : int -> int t
(** Uniform integer generator producing integers within [0..bound].
Shrinks towards [0].
@raise Invalid_argument if the argument is negative. *)
val int_range : ?origin:int -> int -> int -> int t
(** [int_range ?origin low high] is an uniform integer generator producing integers within [low..high] (inclusive).
Shrinks towards [origin] if specified, otherwise towards [0] (but always stays within the range).
Examples:
- [int_range ~origin:6 (-5) 15] will shrink towards [6]
- [int_range (-5) 15] will shrink towards [0]
- [int_range 8 20] will shrink towards [8] (closest to [0] within range)
- [int_range (-20) (-8)] will shrink towards [-8] (closest to [0] within range)
@raise Invalid_argument if any of the following holds:
- [low > high]
- [origin < low]
- [origin > high]
*)
val (--) : int -> int -> int t
(** [a -- b] is an alias for [int_range a b]. See {!int_range} for more information.
*)
val float_bound_inclusive : ?origin : float -> float -> float t
(** [float_bound_inclusive ?origin bound] returns a random floating-point number between [0.] and
[bound] (inclusive). If [bound] is negative, the result is negative or zero. If
[bound] is [0.], the result is [0.].
Shrinks towards [origin] if given, otherwise towards [0.].
@since 0.11 *)
val float_bound_exclusive : ?origin : float -> float -> float t
(** [float_bound_exclusive origin bound] returns a random floating-point number between [0.] and
[bound] (exclusive). If [bound] is negative, the result is negative or zero.
Shrinks towards [origin] if given, otherwise towards [0.].
@raise Invalid_argument if [bound] is [0.].
@since 0.11 *)
val float_range : ?origin : float -> float -> float -> float t
(** [float_range ?origin low high] generates floating-point numbers within [low] and [high] (inclusive).
Shrinks towards [origin] if specified, otherwise towards [0.] (but always stays within the range).
Examples:
- [float_range ~origin:6.2 (-5.8) 15.1] will shrink towards [6.2]
- [float_range (-5.8) 15.1] will shrink towards [0.]
- [float_range 8.5 20.1] will shrink towards [8.5] (closest to [0.] within range)
- [float_range (-20.1) (-8.5)] will shrink towards [-8.5] (closest to [0.] within range)
@raise Invalid_argument if any of the following holds:
- [low > high]
- [high -. low > max_float]
- [origin < low]
- [origin > high]
@since 0.11 *)
val (--.) : float -> float -> float t
(** [a --. b] is an alias for [float_range ~origin:a a b]. See {!float_range} for more information.
@since 0.11 *)
val char_range : ?origin:char -> char -> char -> char t
(** [char_range ?origin low high] generates chars between [low] and [high], inclusive.
Example: [char_range 'a' 'z'] for all lower case ASCII letters.
Shrinks towards [origin] if specified, otherwise towards [low].
@raise Invalid_argument if [low > high].
@since 0.13 *)
(** {3 Choosing elements} *)
val oneof : 'a t list -> 'a t
(** [oneof l] constructs a generator that selects among the given list of generators [l].
Shrinks towards the first generator of the list.
@raise Invalid_argument or Failure if [l] is empty
*)
val oneofl : 'a list -> 'a t
(** [oneofl l] constructs a generator that selects among the given list of values [l].
Shrinks towards the first element of the list.
@raise Invalid_argument or Failure if [l] is empty
*)
val oneofa : 'a array -> 'a t
(** [oneofa a] constructs a generator that selects among the given array of values [a].
Shrinks towards the first element of the array.
@raise Invalid_argument or Failure if [l] is empty
*)
val frequency : (int * 'a t) list -> 'a t
(** Constructs a generator that selects among a given list of generators.
Each of the given generators are chosen based on a positive integer weight.
Shrinks towards the first element of the list.
*)
val frequencyl : (int * 'a) list -> 'a t
(** Constructs a generator that selects among a given list of values.
Each of the given values are chosen based on a positive integer weight.
Shrinks towards the first element of the list.
*)
val frequencya : (int * 'a) array -> 'a t
(** Constructs a generator that selects among a given array of values.
Each of the array entries are chosen based on a positive integer weight.
Shrinks towards the first element of the array.
*)
(** {3 Shuffling elements} *)
val shuffle_a : 'a array -> 'a array t
(** Returns a copy of the array with its elements shuffled. *)
val shuffle_l : 'a list -> 'a list t
(** Creates a generator of shuffled lists. *)
val shuffle_w_l : (int * 'a) list -> 'a list t
(** Creates a generator of weighted shuffled lists. A given list is shuffled on each
generation according to the weights of its elements. An element with a larger weight
is more likely to be at the front of the list than an element with a smaller weight.
If we want to pick random elements from the (head of) list but need to prioritize
some elements over others, this generator can be useful.
Example: given a weighted list [[1, "one"; 5, "five"; 10, "ten"]], the generator is
more likely to generate [["ten"; "five"; "one"]] or [["five"; "ten"; "one"]] than
[["one"; "ten"; "five"]] because "ten" and "five" have larger weights than "one".
@since 0.11
*)
(** {3 Corner cases} *)
val graft_corners : 'a t -> 'a list -> unit -> 'a t
(** [graft_corners gen l ()] makes a new generator that enumerates
the corner cases in [l] and then behaves like [g].
Does not shrink if the test fails on a grafted value.
Shrinks towards [gen] otherwise.
@since 0.6 *)
val int_pos_corners : int list
(** Non-negative corner cases for int.
@since 0.6 *)
val int_corners : int list
(** All corner cases for int.
@since 0.6 *)
(** {3 Lists, arrays and options} *)
val list : 'a t -> 'a list t
(** Builds a list generator from an element generator. List size is generated by {!nat}.
Shrinks on the number of elements first, then on elements.
*)
val small_list : 'a t -> 'a list t
(** Generates lists of small size (see {!small_nat}).
Shrinks on the number of elements first, then on elements.
@since 0.5.3 *)
val list_size : int t -> 'a t -> 'a list t
(** Builds a list generator from a (non-negative) size generator and an element generator.
Shrinks on the number of elements first, then on elements.
*)
val list_repeat : int -> 'a t -> 'a list t
(** [list_repeat i g] builds a list generator from exactly [i] elements generated by [g].
Shrinks on elements only.
*)
val array : 'a t -> 'a array t
(** Builds an array generator from an element generator. Array size is generated by {!nat}.
Shrinks on the number of elements first, then on elements.
*)
val array_size : int t -> 'a t -> 'a array t
(** Builds an array generator from a (non-negative) size generator and an element generator.
Shrinks on the number of elements first, then on elements.
*)
val small_array : 'a t -> 'a array t
(** Generates arrays of small size (see {!small_nat}).
Shrinks on the number of elements first, then on elements.
@since 0.10 *)
val array_repeat : int -> 'a t -> 'a array t
(** [array_repeat i g] builds an array generator from exactly [i] elements generated by [g].
Shrinks on elements only.
*)
val opt : ?ratio:float -> 'a t -> 'a option t
(** [opt gen] is an [option] generator that uses [gen] when generating [Some] values.
Shrinks towards {!None} then towards shrinks of [gen].
@param ratio a float between [0.] and [1.] indicating the probability of a sample to be [Some _]
rather than [None] (value is [0.85]).
*)
(** {3 Combining generators} *)
val pair : 'a t -> 'b t -> ('a * 'b) t
(** [pair gen1 gen2] generates pairs.
Shrinks on [gen1] and then [gen2].
*)
val triple : 'a t -> 'b t -> 'c t -> ('a * 'b * 'c) t
(** [triple gen1 gen2 gen3] generates triples.
Shrinks on [gen1], then [gen2] and then [gen3].
*)
val quad : 'a t -> 'b t -> 'c t -> 'd t -> ('a * 'b * 'c * 'd) t
(** [quad gen1 gen2 gen3 gen4] generates quadruples.
Shrinks on [gen1], then [gen2], then [gen3] and then [gen4].
@since 0.5.1
*)
(** {3 Convert a structure of generator to a generator of structure} *)
val flatten_l : 'a t list -> 'a list t
(** Generate a list of elements from individual generators.
Shrinks on the elements of the list, in the list order.
@since 0.13 *)
val flatten_a : 'a t array -> 'a array t
(** Generate an array of elements from individual generators.
Shrinks on the elements of the array, in the array order.
@since 0.13 *)
val flatten_opt : 'a t option -> 'a option t
(** Generate an option from an optional generator.
Shrinks towards {!None} then shrinks on the value.
@since 0.13 *)
val flatten_res : ('a t, 'e) result -> ('a,'e) result t
(** Generate a result from [Ok gen], an error from [Error e].
Shrinks on [gen] if [Ok gen].
Does not shrink if [Error e].
@since 0.13 *)
val join : 'a t t -> 'a t
(** Collapses a generator of generators to a generator.
Shrinks on the generated generators.
@since 0.5 *)
(** {3 Influencing the size of generated values} *)
val sized : 'a sized -> 'a t
(** Creates a generator from a size-bounded generator by first
generating a size using {!nat} and passing the result to the size-bounded generator.
Shrinks on the size first, then on the generator.
*)
val sized_size : int t -> 'a sized -> 'a t
(** Creates a generator from a size-bounded generator by first
generating a size using the integer generator and passing the result
to the size-bounded generator.
Shrinks on the size first, then on the generator.
@since 0.5 *)
(** {3 Recursive data structures} *)
val fix : (('a -> 'b t) -> 'a -> 'b t) -> 'a -> 'b t
(** Parametrized fixpoint combinator for generating recursive values.
The fixpoint is parametrized over an generator state ['a], and the
fixpoint computation may change the value of this state in the recursive
calls.
In particular, this can be used for size-bounded generators (with ['a] as [int]).
The passed size-parameter should decrease to ensure termination. *)
(** Example:
{[
type tree = Leaf of int | Node of tree * tree
let leaf x = Leaf x
let node x y = Node (x,y)
let g = QCheck.Gen.(sized @@ fix
(fun self n -> match n with
| 0 -> map leaf nat
| n ->
frequency
[1, map leaf nat;
2, map2 node (self (n/2)) (self (n/2))]
))
]}
[fix f] shrinks on the generators returned by [f].
*)
val delay : (unit -> 'a t) -> 'a t
(** Delay execution of some code until the generator is actually called.
This can be used to manually implement recursion or control flow
in a generator.
@since 0.17 *)
(** {2:composing_generators Composing generators}
QCheck generators compose well: it means one can easily craft generators for new values
or types from existing generators.
Part of the following documentation is greatly inspired by Gabriel Scherer's excellent
{{:http://gasche.github.io/random-generator/doc/Generator.html } Generator} module documentation.
{3 Functor}
[Gen.t] is a functor (in the Haskell sense of "mappable"): it has a [map] function to transform a generator of ['a] into a generator of ['b],
given a simple function ['a -> 'b].
{[
let even_gen : int Gen.t = Gen.map (fun n -> n * 2) Gen.int
let odd_gen : int Gen.t = Gen.map (fun n -> n * 2 + 1) Gen.int
let lower_case_string_gen : string Gen.t = Gen.map String.lowercase Gen.string_printable
type foo = Foo of string * int
let foo_gen : foo Gen.t =
Gen.map (fun (s, n) -> Foo (s, n)) Gen.(pair string_printable int)
]}
{3 Applicative}
[Gen.t] is applicative: it has a [map2] function to apply a function of 2 (or more) arguments to 2 (or more) generators.
Another equivalent way to look at it is that it has an [ap] function to apply a generator of
functions to a generator of values. While at first sight this may look almost useless, it actually
permits a nice syntax (using the operator alias [<*>]) for functions of any number of arguments.
{[
(* Notice that this looks suspiciously like the [foo] example above:
this is no coincidence! [pair] is a special case of [map2] where
the function wraps arguments in a tuple. *)
type foo = Foo of string * int
let foo_gen : foo Gen.t =
Gen.map2 (fun s n -> Foo (s, n)) Gen.string_printable Gen.int
let string_prefixed_with_keyword_gen : string Gen.t =
Gen.map2 (fun prefix s -> prefix ^ s)
(Gen.oneofl ["foo"; "bar"; "baz"])
Gen.string_printable
]}
Applicatives are useful when you need several random values to build a new generator,
{b and the values are unrelated}. A good rule of thumb is: if the values could be generated
in parallel, then you can use an applicative function to combine those generators.
Note that while [map2] and [map3] are provided, you can use functions with more than 3
arguments (and that is where the [<*>] operator alias really shines):
{[
val complex_function : bool -> string -> int -> string -> int64 -> some_big_type
(* Verbose version, using map3 and ap *)
let big_type_gen : some_big_type Gen.t = Gen.(
ap (
ap (
map3 complex_function
bool
string_printable
int)
string_printable)
int64)
(* Sleeker syntax, using operators aliases for map and ap *)
let big_type_gen : some_big_type Gen.t = Gen.(
complex_function
<$> bool
<*> string_printable
<*> int
<*> string_printable
<*> int64)
]}
{3 Monad}
[Gen.t] is a monad: it has a [bind] function to return a {b generator} (not a value)
based on {b another generated value}.
As an example, imagine you want to create a generator of [(int, string) result] that is
an [Ok] 90% of the time and an [Error] 10% of the time. You can generate a number between
0 and 9 and return a generator of [int] (wrapped in an [Ok] using [map]) if the generated number is
lower than 9, otherwise return a generator of [string] (wrapped in an [Error] using [map]):
{[
let int_string_result : (int, string) result Gen.t = Gen.(
bind (int_range 0 9) (fun n ->
if n < 9
then map Result.ok int
else map Result.error string_printable))
(* Alternative syntax with operators *)
let int_string_result : (int, string) result Gen.t = Gen.(
int_range 0 9 >>= fun n ->
if n < 9
then int >|= Result.ok
else string_printable >|= Result.error)
(* Another allternative syntax with OCaml 4.08+ binding operators *)
let int_string_result : (int, string) result Gen.t = Gen.(
let* n = int_range 0 9 in
if n < 9
then int >|= Result.ok
else string_printable >|= Result.error)
]}
Note that this particular use case can be simplified by using [frequency]:
{[
let int_string_result : (int, string) result Gen.t = Gen.(
frequency [
(9, int >|= Result.ok);
(1, string_printable >|= Result.error)
])
]}
*)
val map : ('a -> 'b) -> 'a t -> 'b t
(** [map f gen] transforms a generator [gen] by applying [f] to each generated element.
Shrinks towards the shrinks of [gen] with [f] applied to them.
*)
val (>|=) : 'a t -> ('a -> 'b) -> 'b t
(** An infix synonym for {!map}. Note the order of arguments is reversed (usually more
convenient for composing). *)
val (<$>) : ('a -> 'b) -> 'a t -> 'b t
(** An infix synonym for {!map}
@since 0.13 *)
val map2 : ('a -> 'b -> 'c) -> 'a t -> 'b t -> 'c t
(** [map2 f gen1 gen2] transforms two generators [gen1] and [gen2] by applying [f] to each
pair of generated elements.
Shrinks on [gen1] and then [gen2].
*)
val map3 : ('a -> 'b -> 'c -> 'd) -> 'a t -> 'b t -> 'c t -> 'd t
(** [map3 f gen1 gen2 gen3] transforms three generators [gen1], [gen2], and [gen3] by applying [f]
to each triple of generated elements.
Shrinks on [gen1], then [gen2], and then [gen3].
*)
val ap : ('a -> 'b) t -> 'a t -> 'b t
(** [ap fgen gen] composes a function generator and an argument generator
into a result generator.
Shrinks on [fgen] and then [gen].
*)
val (<*>) : ('a -> 'b) t -> 'a t -> 'b t
(** Synonym for {!ap} *)
val bind : 'a t -> ('a -> 'b t) -> 'b t
(** [bind gen f] first generates a value of type ['a] with [gen] and then
passes it to [f] to generate a value of type ['b]. This is typically
useful when a generator depends on the value generated by another
generator.
Shrinks on [gen] and then on the resulting generator.
*)
val (>>=) : 'a t -> ('a -> 'b t) -> 'b t
(** Synonym for {!bind} *)
val ( let+ ) : 'a t -> ('a -> 'b) -> 'b t
(** {{: https://ocaml.org/manual/bindingops.html} Binding operator} alias for {!map}.
Example:
{[
let+ n = int_range 0 10 in
string_of_int n
(* is equivalent to *)
map (fun n -> string_of_int n) (int_range 0 10)
]}
*)
val ( and+ ) : 'a t -> 'b t -> ('a * 'b) t
(** {{: https://ocaml.org/manual/bindingops.html} Binding operator} alias for {!pair}.
Example:
{[
let+ n = int_range 0 10
and+ b = bool in
if b then string_of_int n else "Not a number"
(* is equivalent to *)
map
(fun (n, b) -> if b then string_of_int n else "Not a number")
(pair (int_range 0 10) bool)
]}
*)
val ( let* ) : 'a t -> ('a -> 'b t) -> 'b t
(** {{: https://ocaml.org/manual/bindingops.html} Binding operator} alias for {!bind}.
Example:
{[
let* n = int_range 0 9 in
if n < 9
then int >|= Result.ok
else string_printable >|= Result.error
(* is equivalent to *)
bind (int_range 0 9) (fun n ->
if n < 9
then map Result.ok int
else map Result.error string_printable)
]}
*)
val ( and* ) : 'a t -> 'b t -> ('a * 'b) t
(** {{: https://ocaml.org/manual/bindingops.html} Binding operator} alias for {!pair}.
Example:
{[
let* n = int_range 0 9
and* b = bool in
if n < 9 then int >|= Result.ok
else if b then pure (Error "Some specific error")
else string_printable >|= Result.error
(* is equivalent to *)
bind (pair (int_range 0 9) bool) (fun (n, b) ->
if n < 9 then map Result.ok int
else if b then pure (Error "Some specific error")
else map Result.error string_printable)
]}
*)
(** {2 Debug generators}
These functions should not be used in tests: they are provided
for convenience to debug/investigate what values and shrinks a
generator produces.
*)
val generate : ?rand:Random.State.t -> n:int -> 'a t -> 'a list
(** [generate ~n gen] generates [n] values using [gen] (shrinks are discarded). *)
val generate1 : ?rand:Random.State.t -> 'a t -> 'a
(** [generate1 gen] generates one instance of [gen] (shrinks are discarded). *)
val generate_tree : ?rand:Random.State.t -> 'a t -> 'a Tree.t
(** [generate_tree ?rand gen] generates a random value and its shrinks using [gen]. *)
end
(** Printing functions and helpers, used to print generated values on
test failures. *)
module Print : sig
type 'a t = 'a -> string
(** Printer for values of type ['a]. *)
val unit : unit t
(** [unit] is a printer of unit.
@since 0.6
*)
val int : int t
(** [int] is a printer of integer. *)
val bool : bool t
(** [bool] is a printer of boolean. *)