Skip to content

Commit 1eefb22

Browse files
authored
Merge pull request #212 from jmid/shrinking-retries
Add parameter for retrying a property while shrinking
2 parents 6ed3441 + 3af0592 commit 1eefb22

File tree

8 files changed

+77
-25
lines changed

8 files changed

+77
-25
lines changed

src/core/QCheck.ml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1680,13 +1680,13 @@ module Test = struct
16801680

16811681
let make_cell ?if_assumptions_fail
16821682
?count ?long_factor ?max_gen
1683-
?max_fail ?small:_removed_in_qcheck_2 ?name arb law
1683+
?max_fail ?small:_removed_in_qcheck_2 ?retries ?name arb law
16841684
=
16851685
let {gen; shrink; print; collect; stats; _} = arb in
1686-
QCheck2.Test.make_cell_from_QCheck1 ?if_assumptions_fail ?count ?long_factor ?max_gen ?max_fail ?name ~gen ?shrink ?print ?collect ~stats law
1686+
QCheck2.Test.make_cell_from_QCheck1 ?if_assumptions_fail ?count ?long_factor ?max_gen ?max_fail ?retries ?name ~gen ?shrink ?print ?collect ~stats law
16871687

1688-
let make ?if_assumptions_fail ?count ?long_factor ?max_gen ?max_fail ?small ?name arb law =
1689-
QCheck2.Test.Test (make_cell ?if_assumptions_fail ?count ?long_factor ?max_gen ?max_fail ?small ?name arb law)
1688+
let make ?if_assumptions_fail ?count ?long_factor ?max_gen ?max_fail ?small ?retries ?name arb law =
1689+
QCheck2.Test.Test (make_cell ?if_assumptions_fail ?count ?long_factor ?max_gen ?max_fail ?small ?retries ?name arb law)
16901690

16911691
let fail_report = QCheck2.Test.fail_report
16921692

src/core/QCheck.mli

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -988,13 +988,14 @@ module Test : sig
988988
val make_cell :
989989
?if_assumptions_fail:([`Fatal | `Warning] * float) ->
990990
?count:int -> ?long_factor:int -> ?max_gen:int -> ?max_fail:int ->
991-
?small:('a -> int) -> ?name:string -> 'a arbitrary -> ('a -> bool) ->
992-
'a cell
991+
?small:('a -> int) -> ?retries:int -> ?name:string ->
992+
'a arbitrary -> ('a -> bool) -> 'a cell
993993
(** [make_cell arb prop] builds a test that checks property [prop] on instances
994994
of the generator [arb].
995995
@param name the name of the test.
996996
@param count number of test cases to run, counting only
997997
the test cases which satisfy preconditions.
998+
@param retries number of times to retry the tested property while shrinking.
998999
@param long_factor the factor by which to multiply count, max_gen and
9991000
max_fail when running a long test (default: 1).
10001001
@param max_gen maximum number of times the generation function
@@ -1035,7 +1036,8 @@ module Test : sig
10351036
val make :
10361037
?if_assumptions_fail:([`Fatal | `Warning] * float) ->
10371038
?count:int -> ?long_factor:int -> ?max_gen:int -> ?max_fail:int ->
1038-
?small:('a -> int) -> ?name:string -> 'a arbitrary -> ('a -> bool) -> t
1039+
?small:('a -> int) -> ?retries:int -> ?name:string -> 'a arbitrary ->
1040+
('a -> bool) -> t
10391041
(** [make arb prop] builds a test that checks property [prop] on instances
10401042
of the generator [arb].
10411043
See {!make_cell} for a description of the parameters.

src/core/QCheck2.ml

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1361,6 +1361,7 @@ module Test = struct
13611361
long_factor : int; (* multiplicative factor for long test count *)
13621362
max_gen : int; (* max number of instances to generate (>= count) *)
13631363
max_fail : int; (* max number of failures *)
1364+
retries : int; (* max number of retries during shrinking *)
13641365
law : 'a -> bool; (* the law to check *)
13651366
gen : 'a Gen.t; (* how to generate/shrink instances *)
13661367
print : 'a Print.t option; (* how to print values *)
@@ -1409,7 +1410,7 @@ module Test = struct
14091410

14101411
let make_cell ?(if_assumptions_fail=default_if_assumptions_fail)
14111412
?(count) ?(long_factor=1) ?max_gen
1412-
?(max_fail=1) ?(name=fresh_name()) ?print ?collect ?(stats=[]) gen law
1413+
?(max_fail=1) ?(retries=1) ?(name=fresh_name()) ?print ?collect ?(stats=[]) gen law
14131414
=
14141415
let count = global_count count in
14151416
let max_gen = match max_gen with None -> count + 200 | Some x->x in
@@ -1421,6 +1422,7 @@ module Test = struct
14211422
stats;
14221423
max_gen;
14231424
max_fail;
1425+
retries;
14241426
name;
14251427
count;
14261428
long_factor;
@@ -1430,7 +1432,7 @@ module Test = struct
14301432

14311433
let make_cell_from_QCheck1 ?(if_assumptions_fail=default_if_assumptions_fail)
14321434
?(count) ?(long_factor=1) ?max_gen
1433-
?(max_fail=1) ?(name=fresh_name()) ~gen ?shrink ?print ?collect ~stats law
1435+
?(max_fail=1) ?(retries=1) ?(name=fresh_name()) ~gen ?shrink ?print ?collect ~stats law
14341436
=
14351437
let count = global_count count in
14361438
(* Make a "fake" QCheck2 arbitrary with no shrinking *)
@@ -1444,15 +1446,16 @@ module Test = struct
14441446
stats;
14451447
max_gen;
14461448
max_fail;
1449+
retries;
14471450
name;
14481451
count;
14491452
long_factor;
14501453
if_assumptions_fail;
14511454
qcheck1_shrink = shrink;
14521455
}
14531456

1454-
let make ?if_assumptions_fail ?count ?long_factor ?max_gen ?max_fail ?name ?print ?collect ?stats gen law =
1455-
Test (make_cell ?if_assumptions_fail ?count ?long_factor ?max_gen ?max_fail ?name ?print ?collect ?stats gen law)
1457+
let make ?if_assumptions_fail ?count ?long_factor ?max_gen ?max_fail ?retries ?name ?print ?collect ?stats gen law =
1458+
Test (make_cell ?if_assumptions_fail ?count ?long_factor ?max_gen ?max_fail ?retries ?name ?print ?collect ?stats gen law)
14561459

14571460
let test_get_count (Test cell) = get_count cell
14581461

@@ -1543,9 +1546,33 @@ module Test = struct
15431546
| Run_ok
15441547
| Run_fail of string list
15451548

1546-
let run_law law x =
1549+
(* run_law is a helper function for testing a property [law] on a
1550+
generated input [x].
1551+
1552+
When passed a ~retries number n>1, the tested property is checked
1553+
n times for each shrunk input candidate. The default value is 1,
1554+
thus causing no change in behaviour.
1555+
1556+
Retrying a property can be useful when testing non-deterministic
1557+
code with QCheck, e.g., for multicore execution. The idea is
1558+
described in
1559+
'Testing a Database for Race Conditions with QuickCheck'
1560+
Hughes and Bolinder, Erlang 2011, Sec.6:
1561+
1562+
"As we explained in section 4, we ensure that tests fail when
1563+
races are present simply by repeating each test a large number of
1564+
times, and by running on a dual core machine. We obtained the
1565+
minimal failing cases in the previous section by repeating each
1566+
test 100 times during shrinking: thus we stopped shrinking a test
1567+
case only when all of its candidate shrinkings passed 100 tests
1568+
in a row." *)
1569+
let run_law ~retries law x =
1570+
let rec loop i = match law x with
1571+
| false -> Run_fail []
1572+
| true ->
1573+
if i<=1 then Run_ok else loop (i-1) in
15471574
try
1548-
if law x then Run_ok else Run_fail []
1575+
loop retries
15491576
with User_fail msg -> Run_fail [msg]
15501577

15511578
(* QCheck1-compatibility code *)
@@ -1575,7 +1602,7 @@ module Test = struct
15751602
try
15761603
incr count;
15771604
st.handler st.test.name st.test (Shrinking (steps, !count, x));
1578-
begin match run_law st.test.law x with
1605+
begin match run_law ~retries:st.test.retries st.test.law x with
15791606
| Run_fail m when not is_err -> Some (Tree.pure x, Shrink_fail, m)
15801607
| _ -> None
15811608
end
@@ -1590,7 +1617,7 @@ module Test = struct
15901617
try
15911618
incr count;
15921619
st.handler st.test.name st.test (Shrinking (steps, !count, x));
1593-
begin match run_law st.test.law x with
1620+
begin match run_law ~retries:st.test.retries st.test.law x with
15941621
| Run_fail m when not is_err -> Some (x_tree, Shrink_fail, m)
15951622
| _ -> None
15961623
end
@@ -1668,7 +1695,7 @@ module Test = struct
16681695
let res =
16691696
try
16701697
state.handler state.test.name state.test (Testing input);
1671-
begin match run_law state.test.law input with
1698+
begin match run_law ~retries:1 state.test.law input with
16721699
| Run_ok ->
16731700
(* one test ok *)
16741701
decr_count state;

src/core/QCheck2.mli

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1585,8 +1585,8 @@ module Test : sig
15851585

15861586
val make_cell :
15871587
?if_assumptions_fail:([`Fatal | `Warning] * float) ->
1588-
?count:int -> ?long_factor:int -> ?max_gen:int -> ?max_fail:int -> ?name:string ->
1589-
?print:'a Print.t -> ?collect:('a -> string) -> ?stats:('a stat list) ->
1588+
?count:int -> ?long_factor:int -> ?max_gen:int -> ?max_fail:int -> ?retries:int ->
1589+
?name:string -> ?print:'a Print.t -> ?collect:('a -> string) -> ?stats:('a stat list) ->
15901590
'a Gen.t -> ('a -> bool) ->
15911591
'a cell
15921592
(** [make_cell gen prop] builds a test that checks property [prop] on instances
@@ -1601,6 +1601,7 @@ module Test : sig
16011601
preconditions (should be >= count).
16021602
@param max_fail maximum number of failures before we stop generating
16031603
inputs. This is useful if shrinking takes too much time.
1604+
@param retries number of times to retry the tested property while shrinking.
16041605
@param if_assumptions_fail the minimum
16051606
fraction of tests that must satisfy the precondition for a success
16061607
to be considered valid.
@@ -1616,7 +1617,7 @@ module Test : sig
16161617
val make_cell_from_QCheck1 :
16171618
?if_assumptions_fail:([`Fatal | `Warning] * float) ->
16181619
?count:int -> ?long_factor:int -> ?max_gen:int -> ?max_fail:int ->
1619-
?name:string -> gen:(Random.State.t -> 'a) -> ?shrink:('a -> ('a -> unit) -> unit) ->
1620+
?retries:int -> ?name:string -> gen:(Random.State.t -> 'a) -> ?shrink:('a -> ('a -> unit) -> unit) ->
16201621
?print:('a -> string) -> ?collect:('a -> string) -> stats:'a stat list -> ('a -> bool) ->
16211622
'a cell
16221623
(** ⚠️ Do not use, this is exposed for internal reasons only. ⚠️
@@ -1646,8 +1647,8 @@ module Test : sig
16461647

16471648
val make :
16481649
?if_assumptions_fail:([`Fatal | `Warning] * float) ->
1649-
?count:int -> ?long_factor:int -> ?max_gen:int -> ?max_fail:int -> ?name:string ->
1650-
?print:('a Print.t) -> ?collect:('a -> string) -> ?stats:('a stat list) ->
1650+
?count:int -> ?long_factor:int -> ?max_gen:int -> ?max_fail:int -> ?retries:int ->
1651+
?name:string -> ?print:('a Print.t) -> ?collect:('a -> string) -> ?stats:('a stat list) ->
16511652
'a Gen.t -> ('a -> bool) -> t
16521653
(** [make gen prop] builds a test that checks property [prop] on instances
16531654
of the generator [gen].

test/core/QCheck2_expect_test.ml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ module Overall = struct
6565
]
6666
(Gen.int_bound 120) (fun _ -> true)
6767

68+
let retries =
69+
Test.make ~name:"with shrinking retries" ~retries:10 ~print:Print.int
70+
Gen.small_nat (fun i -> Printf.printf "%i %!" i; i mod 3 <> 1)
71+
6872
let bad_assume_warn =
6973
Test.make ~name:"WARN_unlikely_precond" ~count:2_000 ~print:Print.int
7074
Gen.int
@@ -86,6 +90,7 @@ module Overall = struct
8690
error;
8791
collect;
8892
stats;
93+
retries;
8994
bad_assume_warn;
9095
bad_assume_fail;
9196
]

test/core/QCheck_expect_test.ml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ module Overall = struct
6767
])
6868
(fun _ -> true)
6969

70+
let retries =
71+
Test.make ~name:"with shrinking retries" ~retries:10
72+
small_nat (fun i -> Printf.printf "%i %!" i; i mod 3 <> 1)
73+
7074
let bad_assume_warn =
7175
Test.make ~name:"WARN_unlikely_precond" ~count:2_000
7276
int
@@ -88,6 +92,7 @@ module Overall = struct
8892
error;
8993
collect;
9094
stats;
95+
retries;
9196
bad_assume_warn;
9297
bad_assume_fail;
9398
]

test/core/qcheck2_output.txt.expected

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
random seed: 1234
2-
2724675603984413065
2+
50 7 0 0 0 0 0 0 0 0 0 0 3 3 3 3 3 3 3 3 3 3 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 2724675603984413065
33
0
44
1362337801992206532
55
0
@@ -221,6 +221,12 @@ stats num:
221221
110..115: ####################################################### 9
222222
116..121: ################## 3
223223

224+
--- Failure --------------------------------------------------------------------
225+
226+
Test with shrinking retries failed (0 shrink steps):
227+
228+
7
229+
224230
!!! Warning !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
225231

226232
Warning for test WARN_unlikely_precond:
@@ -982,7 +988,7 @@ stats dist:
982988
4150517416584649600.. 4611686018427387903: ################# 189
983989
================================================================================
984990
1 warning(s)
985-
failure (35 tests failed, 1 tests errored, ran 83 tests)
991+
failure (36 tests failed, 1 tests errored, ran 84 tests)
986992
random seed: 153870556
987993

988994
+++ Stats for int_dist_empty_bucket ++++++++++++++++++++++++++++++++++++++++++++++++++++++++

test/core/qcheck_output.txt.expected

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
random seed: 1234
2-
2724675603984413065
2+
50 7 4 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 2724675603984413065
33
1362337801992206533
44
681168900996103267
55
340584450498051634
@@ -156,6 +156,12 @@ stats num:
156156
110..115: ####################################################### 9
157157
116..121: ################## 3
158158

159+
--- Failure --------------------------------------------------------------------
160+
161+
Test with shrinking retries failed (1 shrink steps):
162+
163+
4
164+
159165
!!! Warning !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
160166

161167
Warning for test WARN_unlikely_precond:
@@ -937,7 +943,7 @@ stats dist:
937943
4150517416584649600.. 4611686018427387903: ################# 189
938944
================================================================================
939945
1 warning(s)
940-
failure (34 tests failed, 1 tests errored, ran 89 tests)
946+
failure (35 tests failed, 1 tests errored, ran 90 tests)
941947
random seed: 153870556
942948

943949
+++ Stats for int_dist_empty_bucket ++++++++++++++++++++++++++++++++++++++++++++++++++++++++

0 commit comments

Comments
 (0)