Skip to content

Commit 46fcbfb

Browse files
authored
Merge pull request #271 from alopezz/honor-polarity-in-check_exn
Make `Test.check_exn` honor test polarity
2 parents e294b14 + 4ac10da commit 46fcbfb

11 files changed

+84
-46
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## NEXT RELEASE
44

5+
- make `Test.check_result`, `Test.check_cell_exn`, and
6+
`Test.check_exn` honor test polarity by raising
7+
`Test_unexpected_success` when a negative test (expected to have a
8+
counter example), unexpectedly succeeds.
59
- ...
610

711
## 0.20

example/alcotest/output.txt.expected.32

+1-2
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ on `0 (after 31 shrink steps)`
2929
│ [FAIL] suite 4 neg test unexpected success. │
3030
└──────────────────────────────────────────────────────────────────────────────┘
3131
negative test 'neg test unexpected success' succeeded unexpectedly
32-
ASSERT negative test 'neg test unexpected success' succeeded unexpectedly
33-
FAIL negative test 'neg test unexpected success' succeeded unexpectedly
32+
[exception] negative test `neg test unexpected success` succeeded unexpectedly
3433
──────────────────────────────────────────────────────────────────────────────
3534
┌──────────────────────────────────────────────────────────────────────────────┐
3635
│ [FAIL] suite 5 neg fail with error. │

example/alcotest/output.txt.expected.64

+1-2
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ on `0 (after 63 shrink steps)`
2929
│ [FAIL] suite 4 neg test unexpected success. │
3030
└──────────────────────────────────────────────────────────────────────────────┘
3131
negative test 'neg test unexpected success' succeeded unexpectedly
32-
ASSERT negative test 'neg test unexpected success' succeeded unexpectedly
33-
FAIL negative test 'neg test unexpected success' succeeded unexpectedly
32+
[exception] negative test `neg test unexpected success` succeeded unexpectedly
3433
──────────────────────────────────────────────────────────────────────────────
3534
┌──────────────────────────────────────────────────────────────────────────────┐
3635
│ [FAIL] suite 5 neg fail with error. │

example/alcotest/output.txt.expected.ocaml5

+1-2
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ on `0 (after 62 shrink steps)`
2929
│ [FAIL] suite 4 neg test unexpected success. │
3030
└──────────────────────────────────────────────────────────────────────────────┘
3131
negative test 'neg test unexpected success' succeeded unexpectedly
32-
ASSERT negative test 'neg test unexpected success' succeeded unexpectedly
33-
FAIL negative test 'neg test unexpected success' succeeded unexpectedly
32+
[exception] negative test `neg test unexpected success` succeeded unexpectedly
3433
──────────────────────────────────────────────────────────────────────────────
3534
┌──────────────────────────────────────────────────────────────────────────────┐
3635
│ [FAIL] suite 5 neg fail with error. │

src/alcotest/QCheck_alcotest.ml

+1-9
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,6 @@ let to_alcotest
5454
let name = T.get_name cell in
5555
let run () =
5656
let call = Raw.callback ~colors ~verbose ~print_res:true ~print in
57-
if T.get_positive cell
58-
then
59-
T.check_cell_exn ~long ~call ~handler ~rand cell
60-
else
61-
try
62-
T.check_cell_exn ~long ~call ~handler ~rand cell;
63-
Alcotest.failf "negative test '%s' succeeded unexpectedly" name
64-
with
65-
T.Test_fail (_name,_l) -> ()
57+
T.check_cell_exn ~long ~call ~handler ~rand cell
6658
in
6759
((name, `Slow, run) : unit Alcotest.test_case)

src/core/QCheck.mli

+3
Original file line numberDiff line numberDiff line change
@@ -1137,13 +1137,16 @@ module Test : sig
11371137
?long:bool -> ?call:'a callback ->
11381138
?step:'a step -> ?handler:'a handler ->
11391139
?rand:Random.State.t -> 'a cell -> 'a TestResult.t
1140+
(** See {!QCheck2.Test.check_cell}. *)
11401141

11411142
val check_cell_exn :
11421143
?long:bool -> ?call:'a callback ->
11431144
?step:'a step -> ?handler:'a handler ->
11441145
?rand:Random.State.t -> 'a cell -> unit
1146+
(** See {!QCheck2.Test.check_cell_exn}. *)
11451147

11461148
val check_exn : ?long:bool -> ?rand:Random.State.t -> t -> unit
1149+
(** See {!QCheck2.Test.check_exn}. *)
11471150
end
11481151

11491152
(** {2 Sub-tests} *)

src/core/QCheck2.ml

+12-5
Original file line numberDiff line numberDiff line change
@@ -1406,6 +1406,7 @@ module Test_exceptions = struct
14061406

14071407
exception Test_fail of string * string list
14081408
exception Test_error of string * string * exn * string
1409+
exception Test_unexpected_success of string
14091410
end
14101411

14111412
module Test = struct
@@ -1882,6 +1883,8 @@ module Test = struct
18821883

18831884
let print_test_fail name l = asprintf "@[%a@]@?" (pp_print_test_fail name) l
18841885

1886+
let print_unexpected_success name = Format.sprintf "@[negative test `%s`@ succeeded unexpectedly@]" name
1887+
18851888
let print_test_error name i e stack =
18861889
Format.sprintf "@[test `%s`@ raised exception `%s`@ on `%s`@,%s@]"
18871890
name (Printexc.to_string e) i stack
@@ -1982,6 +1985,7 @@ module Test = struct
19821985
(function
19831986
| Test_fail (name,l) -> Some (print_test_fail name l)
19841987
| Test_error (name,i,e,st) -> Some (print_test_error name i e st)
1988+
| Test_unexpected_success name -> Some (print_unexpected_success name)
19851989
| User_fail s -> Some ("qcheck: user fail:\n" ^ s)
19861990
| _ -> None)
19871991

@@ -1998,14 +2002,17 @@ module Test = struct
19982002
let print_error ?(st="") arb name (i,e) =
19992003
print_test_error name (print_c_ex arb i) e st
20002004

2001-
let check_result cell res = match res.R.state with
2002-
| R.Success -> ()
2003-
| R.Error {instance; exn; backtrace} ->
2005+
let check_result cell res = match res.R.state, cell.positive with
2006+
| R.Success, true -> ()
2007+
| R.Success, false ->
2008+
raise (Test_unexpected_success cell.name)
2009+
| R.Error {instance; exn; backtrace}, _ ->
20042010
raise (Test_error (cell.name, print_c_ex cell instance, exn, backtrace))
2005-
| R.Failed {instances=l} ->
2011+
| R.Failed {instances=l}, true ->
20062012
let l = List.map (print_c_ex cell) l in
20072013
raise (Test_fail (cell.name, l))
2008-
| R.Failed_other {msg} ->
2014+
| R.Failed _, false -> ()
2015+
| R.Failed_other {msg}, _ ->
20092016
raise (Test_fail (cell.name, [msg]))
20102017

20112018
let check_cell_exn ?long ?call ?step ?handler ?rand cell =

src/core/QCheck2.mli

+21-4
Original file line numberDiff line numberDiff line change
@@ -1649,6 +1649,11 @@ module Test_exceptions : sig
16491649
[Test_error (name, i, e, st)]
16501650
means [name] failed on [i] with exception [e], and [st] is the
16511651
stacktrace (if enabled) or an empty string. *)
1652+
1653+
exception Test_unexpected_success of string
1654+
(** Exception raised when a negative test failed.
1655+
[Test_unexpected_success name] means test [name] failed to find an
1656+
expected counter example. *)
16521657
end
16531658

16541659
(** A test is a pair of a generator and a property that all generated values must satisfy. *)
@@ -1801,9 +1806,11 @@ module Test : sig
18011806
@since 0.6 *)
18021807

18031808
val check_result : 'a cell -> 'a TestResult.t -> unit
1804-
(** [check_result cell res] checks that [res] is [Ok _], and returns unit.
1809+
(** For a positive test [check_result cell res] checks that [res] is [Ok _], and returns unit.
1810+
For a negative test [check_result cell res] checks that [res] is [Failed _], and returns unit.
18051811
Otherwise, it raises some exception.
1806-
@raise Test_fail if [res = Failed _]
1812+
@raise Test_fail if the test is positive and [res = Failed _]
1813+
@raise Test_unexpected_success if the test is negative and [res = Ok _]
18071814
@raise Test_error if [res = Error _] *)
18081815

18091816
type res =
@@ -1840,6 +1847,10 @@ module Test : sig
18401847
predicate [law] is called on them and if it returns [false] or raises an
18411848
exception then we have a counter-example for the [law].
18421849
1850+
Note: [check_cell] ignores a test's polarity, acting as
1851+
described above regardless of whether the tested cell is a
1852+
positive or negative test.
1853+
18431854
@param long if [true] then multiply the number of instances to generate
18441855
by the cell's long_factor.
18451856
@param call function called on each test case, with the result.
@@ -1852,13 +1863,19 @@ module Test : sig
18521863
?step:'a step -> ?handler:'a handler ->
18531864
?rand:Random.State.t -> 'a cell -> unit
18541865
(** Same as {!check_cell} but calls {!check_result} on the result.
1855-
@raise Test_fail if [res = Failed _]
1866+
[check_cell test] honors test polarity and thus expects positive tests to succeed
1867+
without finding a counterexample and expects negative tests to fail by finding one.
1868+
@raise Test_fail if the test is positive and [res = Failed _]
1869+
@raise Test_unexpected_success if the test is negative and [res = Success _]
18561870
@raise Test_error if [res = Error _] *)
18571871

18581872
val check_exn : ?long:bool -> ?rand:Random.State.t -> t -> unit
18591873
(** Checks the property against some test cases, and calls {!check_result},
18601874
which might raise an exception in case of failure.
1861-
@raise Test_fail if [res = Failed _]
1875+
[check_exn test] honors test polarity and thus expects positive tests to succeed
1876+
without finding a counterexample and expects negative tests to fail by finding one.
1877+
@raise Test_fail if the test is positive and [res = Failed _]
1878+
@raise Test_unexpected_success if the test is negative and [res = Success _]
18621879
@raise Test_error if [res = Error _] *)
18631880
end
18641881

src/ounit/QCheck_ounit.ml

+8-22
Original file line numberDiff line numberDiff line change
@@ -72,16 +72,8 @@ let to_ounit2_test ?(rand =default_rand()) (QCheck2.Test.Test cell) =
7272
fail = (fun fmt -> Printf.ksprintf assert_failure fmt);
7373
err = (fun fmt -> logf ctxt `Error fmt);
7474
} in
75-
if QCheck2.Test.get_positive cell
76-
then
77-
T.check_cell_exn cell
78-
~long ~rand ~call:(Raw.callback ~colors:false ~verbose ~print_res:true ~print)
79-
else
80-
try
81-
T.check_cell_exn cell
82-
~long ~rand ~call:(Raw.callback ~colors:false ~verbose ~print_res:true ~print);
83-
()
84-
with T.Test_fail (_,_) -> ())
75+
T.check_cell_exn cell
76+
~long ~rand ~call:(Raw.callback ~colors:false ~verbose ~print_res:true ~print))
8577

8678
let to_ounit2_test_list ?rand lst =
8779
List.rev (List.rev_map (to_ounit2_test ?rand) lst)
@@ -93,18 +85,12 @@ let to_ounit_test_cell ?(verbose=verbose()) ?(long=long_tests())
9385
let module T = QCheck2.Test in
9486
let name = T.get_name cell in
9587
let run () =
96-
97-
let res =
98-
try
99-
T.check_cell_exn cell ~long ~rand
100-
~call:(Raw.callback ~colors:false ~verbose ~print_res:verbose ~print:Raw.print_std);
101-
true
102-
with T.Test_fail _ ->
103-
false
104-
in
105-
if QCheck2.Test.get_positive cell
106-
then res
107-
else not res
88+
try
89+
T.check_cell_exn cell ~long ~rand
90+
~call:(Raw.callback ~colors:false ~verbose ~print_res:verbose ~print:Raw.print_std);
91+
true
92+
with T.Test_fail _ ->
93+
false
10894
in
10995
name >:: (fun () -> assert_bool name (run ()))
11096

test/core/QCheck2_unit_tests.ml

+16
Original file line numberDiff line numberDiff line change
@@ -313,13 +313,29 @@ module Check_exn = struct
313313
then
314314
Alcotest.failf "%s: counter-example prefix. Received \"%s\"" name c_ex_str
315315

316+
let test_negative_trivial () =
317+
let run_test () = check_exn QCheck2.(Test.make_neg Gen.int (fun _ -> false)) in
318+
Alcotest.(check unit) "Success-negative-trivial" () @@ run_test ()
319+
320+
let test_negative_test_unexpected_success () =
321+
let name = "negative-trivial-test" in
322+
let run_test () = check_exn QCheck2.(Test.make_neg ~name Gen.int (fun _ -> true)) in
323+
try
324+
run_test ();
325+
Alcotest.failf "Negative test didn't raise expected exception."
326+
with
327+
Test.Test_unexpected_success n ->
328+
Alcotest.(check string) (Printf.sprintf "%s: name" name) n name
329+
316330
let tests =
317331
("Test.check_exn", Alcotest.[
318332
test_case "check_exn pass trivial" `Quick test_pass_trivial;
319333
test_case "check_exn pass random" `Quick test_pass_random;
320334
test_case "check_exn fail always" `Quick test_fail_always;
321335
test_case "check_exn fail random" `Quick test_fail_random;
322336
test_case "check_exn Error" `Quick test_error;
337+
test_case "check_exn negative pass trivial" `Quick test_negative_trivial;
338+
test_case "check_exn Unexpected success" `Quick test_negative_test_unexpected_success;
323339
])
324340
end
325341

test/core/QCheck_unit_tests.ml

+16
Original file line numberDiff line numberDiff line change
@@ -151,13 +151,29 @@ module Check_exn = struct
151151
then
152152
Alcotest.failf "%s: counter-example prefix. Received \"%s\"" name c_ex_str
153153

154+
let test_negative_trivial () =
155+
let run_test () = check_exn QCheck2.(Test.make_neg Gen.int (fun _ -> false)) in
156+
Alcotest.(check unit) "Success-negative-trivial" () @@ run_test ()
157+
158+
let test_negative_test_unexpected_success () =
159+
let name = "negative-trivial-test" in
160+
let run_test () = check_exn QCheck2.(Test.make_neg ~name Gen.int (fun _ -> true)) in
161+
try
162+
run_test ();
163+
Alcotest.failf "Negative test didn't raise expected exception."
164+
with
165+
Test.Test_unexpected_success n ->
166+
Alcotest.(check string) (Printf.sprintf "%s: name" name) n name
167+
154168
let tests =
155169
("Test.check_exn", Alcotest.[
156170
test_case "check_exn pass trivial" `Quick test_pass_trivial;
157171
test_case "check_exn pass random" `Quick test_pass_random;
158172
test_case "check_exn fail always" `Quick test_fail_always;
159173
test_case "check_exn fail random" `Quick test_fail_random;
160174
test_case "check_exn Error" `Quick test_error;
175+
test_case "check_exn negative pass trivial" `Quick test_negative_trivial;
176+
test_case "check_exn Unexpected success" `Quick test_negative_test_unexpected_success;
161177
])
162178
end
163179

0 commit comments

Comments
 (0)