Codebase list ocaml-qcheck / 34388d0
New upstream version 0.11 Andy Li 4 years ago
15 changed file(s) with 508 addition(s) and 150 deletion(s). Raw diff Collapse all Expand all
66 global:
77 - PINS="qcheck:. qcheck-core:. qcheck-ounit:. qcheck-alcotest:."
88 - DISTRO="ubuntu-16.04"
9 - PACKAGE="qcheck"
10 - DEPOPTS="ounit alcotest"
911 matrix:
10 - PACKAGE="qcheck" OCAML_VERSION="4.03.0" DEPOPTS="ounit alcotest"
11 - PACKAGE="qcheck" OCAML_VERSION="4.04.2" DEPOPTS="ounit alcotest"
12 - PACKAGE="qcheck" OCAML_VERSION="4.05.0" DEPOPTS="ounit alcotest"
13 - PACKAGE="qcheck" OCAML_VERSION="4.06.0" DEPOPTS="ounit alcotest"
14 - PACKAGE="qcheck" OCAML_VERSION="4.07.0" DEPOPTS="ounit alcotest"
12 - OCAML_VERSION="4.03"
13 - OCAML_VERSION="4.04"
14 - OCAML_VERSION="4.05"
15 - OCAML_VERSION="4.06"
16 - OCAML_VERSION="4.07"
17 - OCAML_VERSION="4.08"
18 - OCAML_VERSION="4.09"
00 # Changes
1
2 ## 0.11
3
4 - Add `QCheck.Gen.{string_of,string_readable}`
5 - fix `int_bound` bound inclusiveness problem
6 - change implementation of `int_bound` to generate values using `Random.State.int` for `bound < 2^30`
7 - add weighted shuffled lists generator
8 - add `float_range` to generate a floating-point number in the given range (inclusive)
9 - add `float_bound_inclusive` and `float_bound_exclusive` to generate floating-point numbers between 0 and a given bound
10
11 ## 0.10
12
13 - `Shrink`: decompose Shrink.list into Shrink.list_spine and Shrink.list_elems
14 - `Gen.fix` has a more general and useful type
15 - update README to include `Rely` section (qcheck now available for reason-native!)
16 - Fix stat printing
17 - speed-up list shrinker
18 - Better int shrinking
19 - core: modify proba distributions again, add `big_nat`
20 - feat: add `small_array`, modify distributions
21 - print number of warnings in runner's summary
22 - refactor: modify type of results to make them more accurate
23 - feat: warn/fail if too many tests passed only b/c precondition failed
124
225 ## 0.9
326
3434 release: update_next_tag
3535 @echo "release version $(VERSION)..."
3636 git tag -f $(VERSION) ; git push origin :$(VERSION) ; git push origin $(VERSION)
37 opam publish prepare https://github.com/c-cube/qcheck/archive/$(VERSION).tar.gz
37 opam publish https://github.com/c-cube/qcheck/archive/$(VERSION).tar.gz
3838 @echo "review the release, then type 'opam publish submit qcheck.$(VERSION)/'"
3939
4040
1111 https://github.com/vincent-hugot/iTeML[qtest], but is now
1212 standalone again!
1313 Note that @gasche's
14 http://gasche.github.io/random-generator/doc/Generator.html[generator library]
14 https://github.com/gasche/random-generator/[generator library]
1515 can be useful too, for generating random values.
1616
1717 toc::[]
2121 == Use
2222
2323 See the documentation. I also wrote
24 http://cedeela.fr/quickcheck-for-ocaml.html[a blog post] that explains
24 https://cedeela.fr/quickcheck-for-ocaml[a blog post] that explains
2525 how to use it and some design choices; however, be warned that the API
2626 changed in lots of small ways (in the right direction, I hope) so the code
2727 will not work any more.
185185 - a printer (optional), very useful for printing counterexamples
186186 - a *shrinker* (optional), very useful for trying to reduce big
187187 counterexamples to small counterexamples that are usually
188 more easy to understand.
188 more easy to understand.
189189
190190 The above shrinker strategy is to
191191
356356
357357 ----
358358
359 === Integration within Rely
360 https://reason-native.com/docs/rely/[Rely] is a Jest-inspire native reason testing framework.
361 @reason-native/qcheck-rely is available via NPM and provides matchers for the easy
362 use of qCheck within Rely.
363
364 [source, Reason]
365 ----
366 open TestFramework;
367 open QCheckRely;
368
369 let {describe} = extendDescribe(QCheckRely.Matchers.matchers);
370
371 describe("qcheck-rely", ({test}) => {
372 test("passing test", ({expect}) => {
373 let passing =
374 QCheck.Test.make(
375 ~count=1000,
376 ~name="list_rev_is_involutive",
377 QCheck.(list(small_int)),
378 l =>
379 List.rev(List.rev(l)) == l
380 );
381 expect.ext.qCheckTest(passing);
382 ();
383 });
384 test("failing test", ({expect}) => {
385 let failing =
386 QCheck.Test.make(
387 ~count=10, ~name="fail_sort_id", QCheck.(list(small_int)), l =>
388 l == List.sort(compare, l)
389 );
390
391 expect.ext.qCheckTest(failing);
392 ();
393 });
394 });
395
396 ----
397
359398 === Compatibility notes
360399
361400 Starting with 0.9, the library is split into several components:
5151 QCheck.(fun1 Observable.string bool)
5252 (fun (QCheck.Fun (_,p)) ->
5353 not (p "some random string") || p "some other string")
54
55 let bad_assume_warn =
56 QCheck.Test.make ~count:2_000
57 ~name:"WARN_unlikely_precond"
58 QCheck.int
59 (fun x ->
60 QCheck.assume (x mod 100 = 1);
61 true)
62
63 let bad_assume_fail =
64 QCheck.Test.make ~count:2_000 ~if_assumptions_fail:(`Fatal, 0.1)
65 ~name:"FAIL_unlikely_precond"
66 QCheck.int
67 (fun x ->
68 QCheck.assume (x mod 100 = 1);
69 true)
5470
5571 let int_gen = QCheck.small_nat (* int *)
5672
138154 find_ex;
139155 shrink_int;
140156 stats_negs;
157 bad_assume_warn;
158 bad_assume_fail;
141159 ] @ stats_tests)
142160
0 opam-version: "1.2"
0 opam-version: "2.0"
11 maintainer: "simon.cruanes.2007@m4x.org"
2 author: [ "Simon Cruanes <simon.cruanes.2007@m4x.orgr>" ]
2 author: [ "Simon Cruanes <simon.cruanes.2007@m4x.org>" ]
33 homepage: "https://github.com/c-cube/qcheck/"
4 synopsis: "Alcotest backend for qcheck"
45 doc: ["http://c-cube.github.io/qcheck/"]
5 version: "0.9"
6 version: "0.11"
67 tags: [
78 "test"
8 "property"
99 "quickcheck"
10 "qcheck"
11 "alcotest"
1012 ]
1113 build: [
1214 ["dune" "build" "-p" name "-j" jobs]
13 ]
14 build-doc: [
15 ["dune" "build" "@doc" "-p" name]
16 ]
17 build-test: [
18 ["dune" "runtest" "-p" name]
15 ["dune" "build" "@doc" "-p" name "-j" jobs] {with-doc}
16 ["dune" "runtest" "-p" name "-j" jobs] {with-test}
1917 ]
2018 depends: [
21 "dune" {build}
19 "dune"
2220 "base-bytes"
2321 "base-unix"
24 "qcheck-core" { >= "0.9" }
22 "qcheck-core" { = version }
2523 "alcotest"
26 "odoc" {doc}
24 "odoc" {with-doc}
25 "ocaml" {>= "4.03.0"}
2726 ]
28 available: [ ocaml-version >= "4.03.0" ]
29 dev-repo: "https://github.com/c-cube/qcheck.git"
27 dev-repo: "git+https://github.com/c-cube/qcheck.git"
3028 bug-reports: "https://github.com/c-cube/qcheck/issues"
0 opam-version: "1.2"
0 opam-version: "2.0"
11 maintainer: "simon.cruanes.2007@m4x.org"
2 author: [ "Simon Cruanes <simon.cruanes.2007@m4x.orgr>" ]
2 author: [ "Simon Cruanes <simon.cruanes.2007@m4x.org>" ]
33 homepage: "https://github.com/c-cube/qcheck/"
4 synopsis: "Core qcheck library"
45 doc: ["http://c-cube.github.io/qcheck/"]
5 version: "0.9"
6 version: "0.11"
67 tags: [
78 "test"
89 "property"
1011 ]
1112 build: [
1213 ["dune" "build" "-p" name "-j" jobs]
13 ]
14 build-doc: [
15 ["dune" "build" "@doc" "-p" name]
16 ]
17 build-test: [
18 ["dune" "runtest" "-p" name]
14 ["dune" "build" "@doc" "-p" name "-j" jobs] {with-doc}
15 ["dune" "runtest" "-p" name "-j" jobs] {with-test}
1916 ]
2017 depends: [
21 "dune" {build}
18 "dune"
2219 "base-bytes"
2320 "base-unix"
24 "odoc" {doc}
21 "odoc" {with-doc}
22 "ocaml" {>= "4.03.0"}
2523 ]
26 available: [ ocaml-version >= "4.03.0" ]
27 dev-repo: "https://github.com/c-cube/qcheck.git"
24 dev-repo: "git+https://github.com/c-cube/qcheck.git"
2825 bug-reports: "https://github.com/c-cube/qcheck/issues"
2926 conflicts: [
3027 "ounit" { < "2.0" }
0 opam-version: "1.2"
0 opam-version: "2.0"
11 maintainer: "simon.cruanes.2007@m4x.org"
2 author: [ "Simon Cruanes <simon.cruanes.2007@m4x.orgr>" ]
2 author: [ "Simon Cruanes <simon.cruanes.2007@m4x.org>" ]
33 homepage: "https://github.com/c-cube/qcheck/"
44 doc: ["http://c-cube.github.io/qcheck/"]
5 version: "0.9"
5 synopsis: "OUnit backend for qcheck"
6 version: "0.11"
67 tags: [
7 "test"
8 "property"
8 "qcheck"
99 "quickcheck"
10 "ounit"
1011 ]
1112 build: [
1213 ["dune" "build" "-p" name "-j" jobs]
13 ]
14 build-doc: [
15 ["dune" "build" "@doc" "-p" name]
16 ]
17 build-test: [
18 ["dune" "runtest" "-p" name]
14 ["dune" "build" "@doc" "-p" name "-j" jobs] {with-doc}
15 ["dune" "runtest" "-p" name "-j" jobs] {with-test}
1916 ]
2017 depends: [
21 "dune" {build}
18 "dune"
2219 "base-bytes"
2320 "base-unix"
24 "qcheck-core" { >= "0.9" }
21 "qcheck-core" { = version }
2522 "ounit" {>= "2.0"}
26 "odoc" {doc}
23 "odoc" {with-doc}
24 "ocaml" {>= "4.03.0"}
2725 ]
28 available: [ ocaml-version >= "4.03.0" ]
29 dev-repo: "https://github.com/c-cube/qcheck.git"
26 dev-repo: "git+https://github.com/c-cube/qcheck.git"
3027 bug-reports: "https://github.com/c-cube/qcheck/issues"
0 opam-version: "1.2"
0 opam-version: "2.0"
11 maintainer: "simon.cruanes.2007@m4x.org"
2 author: [ "Simon Cruanes <simon.cruanes.2007@m4x.orgr>" ]
2 author: [ "Simon Cruanes <simon.cruanes.2007@m4x.org>" ]
3 synopsis: "Compatibility package for qcheck"
34 homepage: "https://github.com/c-cube/qcheck/"
45 doc: ["http://c-cube.github.io/qcheck/"]
5 version: "0.9"
6 version: "0.11"
67 tags: [
78 "test"
89 "property"
1011 ]
1112 build: [
1213 ["dune" "build" "-p" name "-j" jobs]
13 ]
14 build-doc: [
15 ["dune" "build" "@doc" "-p" name]
16 ]
17 build-test: [
18 ["dune" "runtest" "-p" name]
14 ["dune" "build" "@doc" "-p" name "-j" jobs] {with-doc}
15 ["dune" "runtest" "-p" name "-j" jobs] {with-test}
1916 ]
2017 depends: [
21 "dune" {build}
18 "dune"
2219 "base-bytes"
2320 "base-unix"
24 "qcheck-core" { >= "0.9" }
25 "qcheck-ounit" { >= "0.9" }
26 "odoc" {doc}
21 "qcheck-core" { = version }
22 "qcheck-ounit" { = version }
23 "odoc" {with-doc}
24 "ocaml" {>= "4.03.0"}
2725 ]
28 available: [ ocaml-version >= "4.03.0" ]
29 dev-repo: "https://github.com/c-cube/qcheck.git"
26 dev-repo: "git+https://github.com/c-cube/qcheck.git"
3027 bug-reports: "https://github.com/c-cube/qcheck/issues"
3128 conflicts: [
3229 "ounit" { < "2.0" }
33 We use environment variables for controlling QCheck here, since alcotest
44 doesn't seem to provide a lot of flexibility.
55
6 [QCHECK_VERBOSE] if "1" or "true", will make tests verbose
7 [QCHECK_SEED] if an integer, will fix the seed
8 [QCHECK_LONG] is present, will trigger long tests
6 - [QCHECK_VERBOSE] if "1" or "true", will make tests verbose
7 - [QCHECK_SEED] if an integer, will fix the seed
8 - [QCHECK_LONG] is present, will trigger long tests
99
1010 @since 0.9
1111 *)
9393
9494 let frequency l st = frequencyl l st st
9595
96 let small_nat st =
97 let p = RS.float st 1. in
98 if p < 0.75 then RS.int st 10 else RS.int st 100
99
96100 (* natural number generator *)
97101 let nat st =
98102 let p = RS.float st 1. in
101105 else if p < 0.95 then RS.int st 1_000
102106 else RS.int st 10_000
103107
104 let small_nat = nat
108 let big_nat st =
109 let p = RS.float st 1. in
110 if p < 0.75 then nat st
111 else RS.int st 1_000_000
105112
106113 let unit _st = ()
107114
113120
114121 let pfloat st = abs_float (float st)
115122 let nfloat st = -.(pfloat st)
123
124 let float_bound_inclusive bound st = RS.float st bound
125
126 let float_bound_exclusive bound st =
127 match bound with
128 | 0. -> raise (Invalid_argument "Gen.float_bound_exclusive")
129 | b_pos when bound > 0. -> RS.float st (b_pos -. epsilon_float)
130 | b_neg -> RS.float st (b_neg +. epsilon_float)
131
132 let float_range low high =
133 if high < low || high -. low > max_float then invalid_arg "Gen.float_range";
134 fun st -> low +. (float_bound_inclusive (high -. low) st)
135
136 let (--.) = float_range
116137
117138 let neg_int st = -(nat st)
118139
134155 let int st = if RS.bool st then - (pint st) - 1 else pint st
135156 let int_bound n =
136157 if n < 0 then invalid_arg "Gen.int_bound";
137 fun st ->
138 let r = pint st in
139 r mod (n+1)
158 if n <= (1 lsl 30) - 2
159 then fun st -> Random.State.int st (n + 1)
160 else fun st -> let r = pint st in r mod (n + 1)
140161 let int_range a b =
141162 if b < a then invalid_arg "Gen.int_range";
142163 fun st -> a + (int_bound (b-a) st)
187208 shuffle_a a st;
188209 Array.to_list a
189210
211 let shuffle_w_l l st =
212 let sample (w, v) =
213 let fl_w = float_of_int w in
214 (float_bound_inclusive 1. st ** (1. /. fl_w), v)
215 in
216 let samples = List.rev_map sample l in
217 List.sort (fun (w1, _) (w2, _) -> compare w1 w2) samples |> List.rev_map snd
218
190219 let pair g1 g2 st = (g1 st, g2 st)
191220
192221 let triple g1 g2 g3 st = (g1 st, g2 st, g3 st)
213242 Bytes.set s i (gen st)
214243 done;
215244 Bytes.unsafe_to_string s
216 let string ?gen st = string_size ?gen small_nat st
217 let small_string ?gen st = string_size ?gen (0--10) st
218 let small_list gen = list_size (0--10) gen
245 let string ?gen st = string_size ?gen nat st
246 let string_of gen = string_size ~gen nat
247 let string_readable = string_size ~gen:char nat
248 let small_string ?gen st = string_size ?gen small_nat st
249 let small_list gen = list_size small_nat gen
250 let small_array gen = array_size small_nat gen
219251
220252 let join g st = (g st) st
221253
324356 type 'a t = 'a -> 'a Iter.t
325357
326358 let nil _ = Iter.empty
359
327360 let unit = nil
328361
329362 (* balanced shrinker for integers (non-exhaustive) *)
330363 let int x yield =
331364 let y = ref x in
332365 (* try some divisors *)
333 while !y < -2 || !y >2 do y := !y / 2; yield !y; done; (* fast path *)
366 while !y < -2 || !y >2 do y := !y / 2; yield (x - !y); done; (* fast path *)
334367 if x>0 then yield (x-1);
335368 if x<0 then yield (x+1);
336369 ()
337370
338371 (* aggressive shrinker for integers,
339 get closer to 0, by dichotomy or just enumerating smaller values *)
372 get from 0 to x, by dichotomy or just enumerating smaller values *)
340373 let int_aggressive x yield =
341374 let y = ref x in
342 while !y < -2 || !y >2 do y := !y / 2; yield !y; done; (* fast path *)
375 while !y < -2 || !y >2 do y := !y / 2; yield (x - !y); done; (* fast path *)
343376 if x>0 then for i=x-1 downto 0 do yield i done;
344377 if x<0 then for i=x+1 to 0 do yield i done
345378
385418 )
386419 done
387420
421 let list_spine l yield =
422 let n = List.length l in
423 let chunk_size = ref (n/2) in
424
425 (* push the [n] first elements of [l] into [q], return the rest of the list *)
426 let rec fill_queue n l q = match n,l with
427 | 0, _ -> l
428 | _, x::xs ->
429 Queue.push x q;
430 fill_queue (n-1) xs q
431 | _, _ -> assert false
432 in
433
434 (* remove elements from the list, by chunks of size [chunk_size] (bigger
435 chunks first) *)
436 while !chunk_size > 0 do
437 let q = Queue.create () in
438 let l' = fill_queue !chunk_size l q in
439 (* remove [chunk_size] elements in queue *)
440 let rec pos_loop rev_prefix suffix =
441 yield (List.rev_append rev_prefix suffix);
442 match suffix with
443 | [] -> ()
444 | x::xs ->
445 Queue.push x q;
446 let y = Queue.pop q in
447 (pos_loop [@tailcall]) (y::rev_prefix) xs
448 in
449 pos_loop [] l';
450 chunk_size := !chunk_size / 2;
451 done
452
453 let list_elems shrink l yield =
454 (* try to shrink each element of the list *)
455 let rec elem_loop rev_prefix suffix = match suffix with
456 | [] -> ()
457 | x::xs ->
458 shrink x (fun x' -> yield (List.rev_append rev_prefix (x'::xs)));
459 elem_loop (x::rev_prefix) xs
460 in
461 elem_loop [] l
462
388463 let list ?shrink l yield =
389 array ?shrink (Array.of_list l)
390 (fun a -> yield (Array.to_list a))
464 list_spine l yield;
465 match shrink with
466 | None -> ()
467 | Some shrink -> list_elems shrink l yield
391468
392469 let pair a b (x,y) yield =
393470 a x (fun x' -> yield (x',y));
560637 let pos_float = make_scalar ~print:string_of_float Gen.pfloat
561638 let neg_float = make_scalar ~print:string_of_float Gen.nfloat
562639
640 let float_bound_inclusive bound =
641 make_scalar ~print:string_of_float (Gen.float_bound_inclusive bound)
642
643 let float_bound_exclusive bound =
644 make_scalar ~print:string_of_float (Gen.float_bound_exclusive bound)
645
646 let float_range low high = make_scalar ~print:string_of_float (Gen.float_range low high)
647
563648 let int = make_int Gen.int
564649 let int_bound n = make_int (Gen.int_bound n)
565650 let int_range a b = make_int (Gen.int_range a b)
587672
588673 let string = string_gen Gen.char
589674 let string_of_size size = string_gen_of_size size Gen.char
590 let small_string = string_gen_of_size Gen.(0--10) Gen.char
675 let small_string = string_gen_of_size Gen.small_nat Gen.char
591676
592677 let printable_string = string_gen Gen.printable
593678 let printable_string_of_size size = string_gen_of_size size Gen.printable
594 let small_printable_string = string_gen_of_size Gen.(0--10) Gen.printable
679 let small_printable_string = string_gen_of_size Gen.small_nat Gen.printable
595680
596681 let numeral_string = string_gen Gen.numeral
597682 let numeral_string_of_size size = string_gen_of_size size Gen.numeral
10411126
10421127 type 'a failed_state = 'a counter_ex list
10431128
1129 (** Result state.
1130 changed in 0.10 (move to inline records) *)
10441131 type 'a state =
10451132 | Success
1046 | Failed of 'a failed_state (** Failed instances *)
1047 | Error of 'a counter_ex * exn * string (** Error, backtrace, and instance
1048 that triggered it *)
1133 | Failed of {
1134 instances: 'a failed_state; (** Failed instance(s) *)
1135 }
1136 | Failed_other of {msg: string}
1137 | Error of {
1138 instance: 'a counter_ex;
1139 exn: exn;
1140 backtrace: string;
1141 } (** Error, backtrace, and instance that triggered it *)
1142
10491143
10501144 (* result returned by running a test *)
10511145 type 'a t = {
10541148 mutable count_gen: int; (* number of generated cases *)
10551149 collect_tbl: (string, int) Hashtbl.t lazy_t;
10561150 stats_tbl: ('a stat * (int, int) Hashtbl.t) list;
1151 mutable warnings: string list;
10571152 mutable instances: 'a list;
10581153 }
10591154
10611156 let fail ~msg_l ~small ~steps:shrink_steps res instance =
10621157 let c_ex = {instance; shrink_steps; msg_l; } in
10631158 match res.state with
1064 | Success -> res.state <- Failed [ c_ex ]
1065 | Error (x, e, bt) ->
1066 res.state <- Error (x,e,bt); (* same *)
1067 | Failed [] -> assert false
1068 | Failed (c_ex' :: _ as l) ->
1159 | Success -> res.state <- Failed {instances=[ c_ex ]}
1160 | Error _
1161 | Failed_other _ -> ()
1162 | Failed {instances=[]} -> assert false
1163 | Failed {instances=((c_ex'::_) as l)} ->
10691164 match small with
10701165 | Some small ->
10711166 (* all counter-examples in [l] have same size according to [small],
10721167 so we just compare to the first one, and we enforce
10731168 the invariant *)
10741169 begin match Pervasives.compare (small instance) (small c_ex'.instance) with
1075 | 0 -> res.state <- Failed (c_ex :: l) (* same size: add [c_ex] to [l] *)
1076 | n when n<0 -> res.state <- Failed [c_ex] (* drop [l] *)
1170 | 0 -> res.state <- Failed {instances=c_ex :: l} (* same size: add [c_ex] to [l] *)
1171 | n when n<0 -> res.state <- Failed {instances=[c_ex]} (* drop [l] *)
10771172 | _ -> () (* drop [c_ex], not small enough *)
10781173 end
10791174 | _ ->
10801175 (* no [small] function, keep all counter-examples *)
10811176 res.state <-
1082 Failed (c_ex :: l)
1083
1084 let error ~msg_l ~steps res instance e bt =
1085 res.state <- Error ({instance; shrink_steps=steps; msg_l; }, e, bt)
1177 Failed {instances=c_ex :: l}
1178
1179 let error ~msg_l ~steps res instance exn backtrace =
1180 res.state <- Error {instance={instance; shrink_steps=steps; msg_l; }; exn; backtrace}
10861181
10871182 let collect r =
10881183 if Lazy.is_val r.collect_tbl then Some (Lazy.force r.collect_tbl) else None
10891184
10901185 let stats r = r.stats_tbl
1186 let warnings r = r.warnings
10911187
10921188 let is_success r = match r.state with
10931189 | Success -> true
1094 | Failed _ | Error _ -> false
1190 | Failed _ | Error _ | Failed_other _ -> false
10951191 end
10961192
10971193 module Test = struct
11021198 max_fail : int; (* max number of failures *)
11031199 law : 'a -> bool; (* the law to check *)
11041200 arb : 'a arbitrary; (* how to generate/print/shrink instances *)
1201 if_assumptions_fail: [`Fatal | `Warning] * float;
11051202 mutable name : string; (* name of the law *)
11061203 }
11071204
11211218 let r = ref 0 in
11221219 (fun () -> incr r; Printf.sprintf "anon_test_%d" !r)
11231220
1124 let make_cell ?(count=default_count) ?(long_factor=1) ?max_gen
1221 let default_if_assumptions_fail = `Warning, 0.05
1222
1223 let make_cell ?(if_assumptions_fail=default_if_assumptions_fail)
1224 ?(count=default_count) ?(long_factor=1) ?max_gen
11251225 ?(max_fail=1) ?small ?(name=fresh_name()) arb law
11261226 =
11271227 let max_gen = match max_gen with None -> count + 200 | Some x->x in
11341234 name;
11351235 count;
11361236 long_factor;
1237 if_assumptions_fail;
11371238 }
11381239
1139 let make ?count ?long_factor ?max_gen ?max_fail ?small ?name arb law =
1140 Test (make_cell ?count ?long_factor ?max_gen ?max_fail ?small ?name arb law)
1240 let make ?if_assumptions_fail ?count ?long_factor ?max_gen ?max_fail ?small ?name arb law =
1241 Test (make_cell ?if_assumptions_fail ?count ?long_factor ?max_gen ?max_fail ?small ?name arb law)
11411242
11421243 (** {6 Running the test} *)
11431244
11741275 handler : 'a handler;
11751276 rand: Random.State.t;
11761277 mutable res: 'a TestResult.t;
1177 mutable cur_count: int; (** number of iterations to do *)
1278 mutable cur_count: int; (** number of iterations remaining to do *)
11781279 mutable cur_max_gen: int; (** maximum number of generations allowed *)
11791280 mutable cur_max_fail: int; (** maximum number of counter-examples allowed *)
11801281 }
13381439
13391440 let callback_nil_ : _ callback = fun _ _ _ -> ()
13401441
1442 (* check that there are sufficiently many tests which passed, to avoid
1443 the case where they all passed by failed precondition *)
1444 let check_if_assumptions target_count cell res : unit =
1445 let percentage_of_count = float_of_int res.R.count /. float_of_int target_count in
1446 let assm_flag, assm_frac = cell.if_assumptions_fail in
1447 if R.is_success res && percentage_of_count < assm_frac then (
1448 let msg =
1449 format_of_string "%s: \
1450 only %.1f%% tests (of %d) passed precondition for %S\n\n\
1451 NOTE: it is likely that the precondition is too strong, or that \
1452 the generator is buggy.\n%!"
1453 in
1454 match assm_flag with
1455 | `Warning ->
1456 let msg = Printf.sprintf
1457 msg "WARNING"
1458 (percentage_of_count *. 100.) cell.count cell.name in
1459 res.R.warnings <- msg :: res.R.warnings
1460 | `Fatal ->
1461 (* turn it into an error *)
1462 let msg = Printf.sprintf
1463 msg "ERROR"
1464 (percentage_of_count *. 100.) cell.count cell.name in
1465 res.R.state <- R.Failed_other {msg}
1466 )
1467
13411468 (* main checking function *)
13421469 let check_cell ?(long=false) ?(call=callback_nil_)
13431470 ?(step=step_nil_) ?(handler=handler_nil_)
13441471 ?(rand=Random.State.make [| 0 |]) cell =
13451472 let factor = if long then cell.long_factor else 1 in
1473 let target_count = factor*cell.count in
13461474 let state = {
13471475 test=cell; rand;
13481476 step; handler;
1349 cur_count=factor*cell.count;
1477 cur_count=target_count;
13501478 cur_max_gen=factor*cell.max_gen;
13511479 cur_max_fail=factor*cell.max_fail;
13521480 res = {R.
13531481 state=R.Success; count=0; count_gen=0;
13541482 collect_tbl=lazy (Hashtbl.create 10);
1355 instances=[];
1483 instances=[]; warnings=[];
13561484 stats_tbl= List.map (fun stat -> stat, Hashtbl.create 10) cell.arb.stats;
13571485 };
13581486 } in
13591487 let res = check_state state in
1488 check_if_assumptions target_count cell res;
13601489 call cell.name cell res;
13611490 res
13621491
14531582 let hist_size, bucket_size =
14541583 let sample_width = Int64.(sub (of_int max_idx) (of_int min_idx)) in
14551584 if sample_width > Int64.of_int stat_max_lines
1456 then 1+stat_max_lines,
1585 then stat_max_lines,
14571586 int_of_float (ceil (Int64.to_float sample_width /. float_of_int stat_max_lines))
1458 else 1+max_idx-min_idx, 1
1587 else max_idx-min_idx, 1
14591588 in
1589 let hist_size = if min_idx + bucket_size * hist_size <= max_idx then 1+hist_size else hist_size in
14601590 (* accumulate bucket counts *)
14611591 let max_val = ref 0 in (* max value after grouping by buckets *)
14621592 let bucket_count = Array.init hist_size (fun _ -> 0) in
14721602 Printf.bprintf out
14731603 " num: %d, avg: %.2f, stddev: %.2f, median %d, min %d, max %d\n"
14741604 !num !avg stddev !median min_idx max_idx;
1605 let indwidth =
1606 max (String.length (Printf.sprintf "%d" min_idx))
1607 (max (String.length (Printf.sprintf "%d" max_idx))
1608 (String.length (Printf.sprintf "%d" (min_idx + bucket_size * hist_size)))) in
1609 let labwidth = if bucket_size=1 then indwidth else 2+2*indwidth in
14751610 for i = 0 to hist_size - 1 do
14761611 let i' = min_idx + i * bucket_size in
14771612 let blabel =
14781613 if bucket_size=1
1479 then Printf.sprintf "%d" i'
1480 else Printf.sprintf "%d..%d" i' (i'+bucket_size-1) in
1614 then Printf.sprintf "%*d" indwidth i'
1615 else
1616 let bucket_bound = i'+bucket_size-1 in
1617 Printf.sprintf "%*d..%*d" indwidth i' indwidth (if bucket_bound < i' then max_int else bucket_bound) in
14811618 let bcount = bucket_count.(i) in
14821619 (* NOTE: keep in sync *)
14831620 let bar_len = bcount * 55 / !max_val in
1484 Printf.bprintf out " %15s: %-56s %10d\n" blabel (String.make bar_len '#') bcount
1621 Printf.bprintf out " %*s: %-56s %10d\n" labwidth blabel (String.make bar_len '#') bcount
14851622 done;
14861623 Buffer.contents out
14871624
14941631 let print_fail arb name l =
14951632 print_test_fail name (List.map (print_c_ex arb) l)
14961633
1634 let print_fail_other name ~msg =
1635 print_test_fail name [msg]
1636
14971637 let print_error ?(st="") arb name (i,e) =
14981638 print_test_error name (print_c_ex arb i) e st
14991639
15001640 let check_result cell res = match res.R.state with
15011641 | R.Success -> ()
1502 | R.Error (i,e, bt) ->
1503 raise (Test_error (cell.name, print_c_ex cell.arb i, e, bt))
1504 | R.Failed l ->
1505 let l = List.map (print_c_ex cell.arb) l in
1506 raise (Test_fail (cell.name, l))
1642 | R.Error {instance; exn; backtrace} ->
1643 raise (Test_error (cell.name, print_c_ex cell.arb instance, exn, backtrace))
1644 | R.Failed {instances=l} ->
1645 let l = List.map (print_c_ex cell.arb) l in
1646 raise (Test_fail (cell.name, l))
1647 | R.Failed_other {msg} ->
1648 raise (Test_fail (cell.name, [msg]))
15071649
15081650 let check_cell_exn ?long ?call ?step ?rand cell =
15091651 let res = check_cell ?long ?call ?step ?rand cell in
15231665 let res = Test.check_cell ~rand:st cell in
15241666 begin match res.TestResult.state with
15251667 | TestResult.Success -> raise (No_example_found name)
1526 | TestResult.Error (_, _, _) -> raise (No_example_found name)
1527 | TestResult.Failed [] -> assert false
1528 | TestResult.Failed (failed::_) ->
1668 | TestResult.Error _ -> raise (No_example_found name)
1669 | TestResult.Failed {instances=[]} -> assert false
1670 | TestResult.Failed {instances=failed::_} ->
15291671 (* found counter-example! *)
15301672 failed.TestResult.instance
1673 | TestResult.Failed_other {msg=_} ->
1674 raise (No_example_found name)
1675
15311676 end
15321677 in
15331678 gen
189189 val shuffle_l : 'a list -> 'a list t
190190 (** Creates a generator of shuffled lists. *)
191191
192 val unit: unit t (** The unit generator. *)
193
194 val bool: bool t (** The boolean generator. *)
195
196 val float: float t (** Generates floating point numbers. *)
192 val shuffle_w_l : (int * 'a) list -> 'a list t
193 (** Creates a generator of weighted shuffled lists. A given list is shuffled on each
194 generation according to the weights of its elements. An element with a larger weight
195 is more likely to be at the front of the list than an element with a smaller weight.
196 If we want to pick random elements from the (head of) list but need to prioritize
197 some elements over others, this generator can be useful.
198
199 Example: given a weighted list [[1, "one"; 5, "five"; 10, "ten"]], the generator is
200 more likely to generate [["ten"; "five"; "one"]] or [["five"; "ten"; "one"]] than
201 [["one"; "ten"; "five"]] because "ten" and "five" have larger weights than "one".
202
203 @since 0.11
204 *)
205
206 val unit : unit t (** The unit generator. *)
207
208 val bool : bool t (** The boolean generator. *)
209
210 val float : float t (** Generates floating point numbers. *)
197211
198212 val pfloat : float t (** Generates positive floating point numbers (0. included). *)
199213
200214 val nfloat : float t (** Generates negative floating point numbers. (-0. included) *)
201215
216 val float_bound_inclusive : float -> float t
217 (** [float_bound_inclusive bound] returns a random floating-point number between 0 and
218 [bound] (inclusive). If [bound] is negative, the result is negative or zero. If
219 [bound] is 0, the result is 0.
220 @since 0.11 *)
221
222 val float_bound_exclusive : float -> float t
223 (** [float_bound_exclusive bound] returns a random floating-point number between 0 and
224 [bound] (exclusive). If [bound] is negative, the result is negative or zero.
225 @raise Invalid_argument if [bound] is zero.
226 @since 0.11 *)
227
228 val float_range : float -> float -> float t
229 (** [float_range low high] generates floating-point numbers within [low] and [high] (inclusive)
230 @raise Invalid_argument if [high < low] or if the range is larger than [max_float].
231 @since 0.11 *)
232
233 val (--.) : float -> float -> float t
234 (** Synonym for [float_range]
235 @since 0.11 *)
236
202237 val nat : int t (** Generates small natural numbers. *)
203238
239 val big_nat : int t (** Generates natural numbers, possibly large. @since 0.10 *)
240
204241 val neg_int : int t (** Generates non-strictly negative integers (0 included). *)
205242
206243 val pint : int t (** Generates non-strictly positive integers uniformly (0 included). *)
207244
208245 val int : int t (** Generates integers uniformly. *)
209246
210 val small_nat : int t (** Synonym to {!nat}. @since 0.5.1 *)
247 val small_nat : int t (** Small integers (< 100) @since 0.5.1 *)
211248
212249 val small_int : int t
213250 (** Small UNSIGNED integers, for retrocompatibility.
214251 @deprecated use {!small_nat}. *)
215252
216253 val small_signed_int : int t
217 (** Small SIGNED integers.
254 (** Small SIGNED integers, based on {!small_nat}.
218255 @since 0.5.2 *)
219256
220257 val int_bound : int -> int t
221258 (** Uniform integer generator producing integers within [0... bound].
259 For [bound < 2^{30} - 1] uses [Random.State.int] for integer generation.
222260 @raise Invalid_argument if the argument is negative. *)
223261
224262 val int_range : int -> int -> int t
282320
283321 val string : ?gen:char t -> string t
284322 (** Builds a string generator. String size is generated by {!nat}.
323 Accepts an optional character generator (the default is {!char}).
324 See also {!string_of} and {!string_readable} for versions without
325 optional parameters. *)
326
327 val string_of : char t -> string t
328 (** Builds a string generator using the given character generator.
329 @since 0.11 *)
330
331 val string_readable : string t
332 (** Builds a string generator using the {!char} character generator.
333 @since 0.11 *)
334
335 val small_string : ?gen:char t -> string t
336 (** Builds a string generator, length is {!small_nat}
285337 Accepts an optional character generator (the default is {!char}). *)
286338
287 val small_string : ?gen:char t -> string t
288 (** Builds a string generator. String size is in the range [0--10].
289 Accepts an optional character generator (the default is {!char}). *)
290
291339 val small_list : 'a t -> 'a list t
292 (** Generates lists of small size (range [0 -- 10]).
340 (** Generates lists of small size (see {!small_nat}).
293341 @since 0.5.3 *)
342
343 val small_array : 'a t -> 'a array t
344 (** Generates arrays of small size (see {!small_nat}).
345 @since 0.10 *)
294346
295347 val join : 'a t t -> 'a t
296348 (** Collapses a generator of generators to simply a generator.
306358 to the size-bounded generator.
307359 @since 0.5 *)
308360
309 val fix : ('a sized -> 'a sized) -> 'a sized
310 (** Fixpoint combinator for generating recursive, size-bounded data types.
361 val fix : (('a -> 'b t) -> ('a -> 'b t)) -> 'a -> 'b t
362 (** Parametrized fixpoint combinator for generating recursive values.
363
364 The fixpoint is parametrized over an arbitrary state ('a), and the
365 fixpoint computation may change the value of this state in the recursive
366 calls.
367
368 In particular, this can be used for size-bounded generators ('a is int).
311369 The passed size-parameter should decrease to ensure termination. *)
312370
313371 (** Example:
449507 @since 0.7 *)
450508
451509 val list : ?shrink:'a t -> 'a list t
452 (** Try to shrink lists by removing elements one by one.
510 (** Try to shrink lists by removing one or more elements.
453511 @param shrink if provided, will be used to also try to reduce
454512 the elements of the list themselves (e.g. in an [int list]
455513 one can try to decrease the integers). *)
514
515 val list_spine : 'a list t
516 (** Try to shrink lists by removing one or more elements.
517 @since 0.10 *)
518
519 val list_elems : 'a t -> 'a list t
520 (** Shrinks the elements of a list, without changing the list size.
521 @since 0.10 *)
456522
457523 val array : ?shrink:'a t -> 'a array t
458524 (** Shrink an array.
604670
605671 type 'a failed_state = 'a counter_ex list
606672
673 (** Result state.
674 changed in 0.10 (move to inline records, add Fail_other) *)
607675 type 'a state =
608676 | Success
609 | Failed of 'a failed_state (** Failed instances *)
610 | Error of 'a counter_ex * exn * string (** Error, backtrace, and instance
611 that triggered it *)
677 | Failed of {
678 instances: 'a failed_state; (** Failed instance(s) *)
679 }
680 | Failed_other of {msg: string}
681 | Error of {
682 instance: 'a counter_ex;
683 exn: exn;
684 backtrace: string;
685 } (** Error, backtrace, and instance that triggered it *)
612686
613687 (* result returned by running a test *)
614 type 'a t = {
688 type 'a t = private {
615689 mutable state : 'a state;
616690 mutable count: int; (* Number of tests *)
617691 mutable count_gen: int; (* Number of generated cases *)
618692 collect_tbl: (string, int) Hashtbl.t lazy_t;
619693 stats_tbl: ('a stat * (int, int) Hashtbl.t) list; (** @since 0.6 *)
694 mutable warnings: string list;
620695 mutable instances: 'a list;
621696 (** List of instances used for this test, in no particular order.
622697 @since 0.9 *)
630705 (** Obtain statistics
631706 @since 0.6 *)
632707
708 val warnings : _ t -> string list
709 (** Obtain list of warnings
710 @since 0.10 *)
711
633712 val is_success : _ t -> bool
634713 (** Returns true iff the state if [Success]
635714 @since 0.9 *)
649728 @since 0.7 *)
650729
651730 val make_cell :
731 ?if_assumptions_fail:([`Fatal | `Warning] * float) ->
652732 ?count:int -> ?long_factor:int -> ?max_gen:int -> ?max_fail:int ->
653733 ?small:('a -> int) -> ?name:string -> 'a arbitrary -> ('a -> bool) ->
654734 'a cell
668748 the field [arbitrary.small].
669749 If there is no shrinking function but there is a [small]
670750 function, only the smallest failures will be printed.
751 @param if_assumptions_fail the minimum
752 fraction of tests that must satisfy the precondition for a success
753 to be considered valid.
754 The fraction should be between 0. and 1.
755 A warning will be emitted otherwise if
756 the flag is [`Warning], the test will be a failure if the flag is [`Fatal].
757 (since 0.10)
671758 *)
672759
673760 val get_arbitrary : 'a cell -> 'a arbitrary
688775 put tests on different types in the same list of tests. *)
689776
690777 val make :
778 ?if_assumptions_fail:([`Fatal | `Warning] * float) ->
691779 ?count:int -> ?long_factor:int -> ?max_gen:int -> ?max_fail:int ->
692780 ?small:('a -> int) -> ?name:string -> 'a arbitrary -> ('a -> bool) -> t
693781 (** [make arb prop] builds a test that checks property [prop] on instances
711799 val print_instance : 'a arbitrary -> 'a -> string
712800 val print_c_ex : 'a arbitrary -> 'a TestResult.counter_ex -> string
713801 val print_fail : 'a arbitrary -> string -> 'a TestResult.counter_ex list -> string
802 val print_fail_other : string -> msg:string -> string
714803 val print_error : ?st:string -> 'a arbitrary -> string -> 'a TestResult.counter_ex * exn -> string
715804 val print_test_fail : string -> string list -> string
716805 val print_test_error : string -> string -> exn -> string -> string
848937 val neg_float : float arbitrary
849938 (** Negative float generator (no nan and no infinities). *)
850939
940 val float_bound_inclusive : float -> float arbitrary
941 (** [float_bound_inclusive n] is uniform between [0] and [n] included. If [bound] is
942 negative, the result is negative or zero. If [bound] is 0, the result is 0.
943 @since 0.11 *)
944
945 val float_bound_exclusive : float -> float arbitrary
946 (** [float_bound_exclusive n] is uniform between [0] included and [n] excluded.
947 If [bound] is negative, the result is negative or zero.
948 @raise Invalid_argument if [bound] is zero.
949 @since 0.11 *)
950
951 val float_range : float -> float -> float arbitrary
952 (** [float_range low high] is uniform between [low] included and [high] included.
953 @raise Invalid_argument if [low > high] or if the range is larger than [max_float].
954 @since 0.11 *)
955
851956 val int : int arbitrary
852957 (** Int generator. Uniformly distributed. *)
853958
9141019 and distribution of characters of [char]. *)
9151020
9161021 val small_string : string arbitrary
917 (** Same as {!string} but with a small length (that is, [0--10]). *)
1022 (** Same as {!string} but with a small length (ie {!Gen.small_nat} ). *)
9181023
9191024 val small_list : 'a arbitrary -> 'a list arbitrary
920 (** Generates lists of small size (range [0 -- 10]).
1025 (** Generates lists of small size (see {!Gen.small_nat}).
9211026 @since 0.5.3 *)
9221027
9231028 val string_of_size : int Gen.t -> string arbitrary
66 (modules QCheck_runner)
77 (synopsis "compatibility library for qcheck")
88 (libraries qcheck-core qcheck-core.runner qcheck-ounit))
9
10 (documentation
11 (package qcheck)
12 (mld_files index))
0
1 {1 QCheck compatibility package}
2
3
4 This library is there to ensure compatibility with QCheck 0.8 and earlier.
5
6 It has a unique module {!QCheck_runner} that merges the custom runners
7 from [qcheck-core.runner] ({!QCheck_base_runner})
8 and the OUnit runners from [qcheck-ounit] ({!QCheck_ounit})
9 into a single module.
10
11 By depending on [qcheck-core], this library also brings {!QCheck} in scope,
12 so it can be used transparently.
129129 (* even if [not verbose], print errors *)
130130 match result.R.state with
131131 | R.Success -> ()
132 | R.Failed l ->
132 | R.Failed {instances=l} ->
133133 print.fail "%s%s\n" Color.reset_line (T.print_fail arb name l);
134 | R.Error (i,e,st) ->
135 print.err "%s%s\n" Color.reset_line (T.print_error ~st arb name (i,e));
134 | R.Failed_other {msg} ->
135 print.fail "%s%s\n" Color.reset_line (T.print_fail_other name ~msg);
136 | R.Error {instance; exn; backtrace} ->
137 print.err "%s%s\n" Color.reset_line
138 (T.print_error ~st:backtrace arb name (instance,exn));
136139 )
137140
138141 let print_std = { info = Printf.printf; fail = Printf.printf; err = Printf.printf }
268271 (Color.pp_str_c ~colors `Blue) "Collect"
269272 (String.make 68 '+') (QCheck.Test.get_name cell) (QCheck.Test.print_collect tbl)
270273 end;
274 List.iter (fun msg ->
275 Printf.fprintf out
276 "\n!!! %a %s\n\nWarning for test %s:\n\n%s%!"
277 (Color.pp_str_c ~colors `Yellow) "Warning" (String.make 68 '!')
278 (QCheck.Test.get_name cell) msg)
279 (QCheck.TestResult.warnings r);
271280 List.iter
272281 (fun st ->
273282 Printf.fprintf out
283292 (QCheck.Test.get_name cell) c_ex.QCheck.TestResult.shrink_steps
284293 (print_inst (QCheck.Test.get_arbitrary cell) c_ex.QCheck.TestResult.instance);
285294 print_messages ~colors out cell c_ex.QCheck.TestResult.msg_l
295
296 let print_fail_other ~colors out cell msg =
297 Printf.fprintf out "\n--- %a %s\n\n" (Color.pp_str_c ~colors `Red) "Failure" (String.make 68 '-');
298 Printf.fprintf out "Test %s failed:\n\n%s\n%!" (QCheck.Test.get_name cell) msg
286299
287300 let print_error ~colors out cell c_ex exn bt =
288301 Printf.fprintf out "\n=== %a %s\n\n" (Color.pp_str_c ~colors `Red) "Error" (String.make 70 '=');
325338 Res (cell, r)
326339 in
327340 let res = List.map aux_map l in
328 let aux_fold (total, fail, error) (Res (cell, r)) =
341 let aux_fold (total, fail, error, warns) (Res (cell, r)) =
342 let warns = warns + List.length r.R.warnings in
329343 let acc = match r.R.state with
330344 | R.Success ->
331345 print_success ~colors out cell r;
332 (total + 1, fail, error)
333 | R.Failed l ->
346 (total + 1, fail, error, warns)
347 | R.Failed {instances=l} ->
334348 List.iter (print_fail ~colors out cell) l;
335 (total + 1, fail + 1, error)
336 | R.Error (c_ex, exn, bt) ->
349 (total + 1, fail + 1, error, warns)
350 | R.Failed_other {msg} ->
351 print_fail_other ~colors out cell msg;
352 (total + 1, fail + 1, error, warns)
353 | R.Error {instance=c_ex; exn; backtrace=bt} ->
337354 print_error ~colors out cell c_ex exn bt;
338 (total + 1, fail, error + 1)
355 (total + 1, fail, error + 1, warns)
339356 in
340357 acc
341358 in
342 let total, fail, error = List.fold_left aux_fold (0, 0, 0) res in
359 let total, fail, error, warns = List.fold_left aux_fold (0, 0, 0,0) res in
343360 Printf.fprintf out "%s\n" (String.make 80 '=');
361 if warns > 0 then Printf.fprintf out "%d warning(s)\n" warns;
344362 if fail = 0 && error = 0 then (
345363 Printf.fprintf out "%a (ran %d tests)\n%!"
346364 (pp_color `Green) "success" total;