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
module Testable = Testable
module Tag = Tag
module Pp = Pp
module Ppx_runtime = Ppx_runtime
type 'a testable = 'a Testable.t
let unit = Testable.unit
let bool = Testable.bool
let int = Testable.int
let small_int = Testable.small_int
let nat = Testable.nat
let int32 = Testable.int32
let int64 = Testable.int64
let nativeint = Testable.nativeint
let float = Testable.float
let float_rel = Testable.float_rel
let char = Testable.char
let string = Testable.string
let bytes = Testable.bytes
let option = Testable.option
let result = Testable.result
let either = Testable.either
let list = Testable.list
let array = Testable.array
let pair = Testable.pair
let triple = Testable.triple
let quad = Testable.quad
let pass = Testable.pass
let slist = Testable.slist
let of_equal = Testable.of_equal
let contramap = Testable.contramap
let seq = Testable.seq
let lazy_t = Testable.lazy_t
let testable = Testable.make
type test = Test.t
type pos = string * int * int * int
type here = Lexing.position
let test = Test.test
let ftest = Test.ftest
let group = Test.group
let fgroup = Test.fgroup
let slow = Test.slow
let bracket = Test.bracket
let cases testable cases name fn =
let pp = Testable.pp testable in
let tests =
List.map
(fun case ->
let case_name = Pp.str "%s (%a)" name pp case in
Test.test case_name (fun () -> fn case))
cases
in
Test.group name tests
let fixture create =
let v = lazy (create ()) in
fun () -> Lazy.force v
type format = Progress.mode = Compact | Verbose | Tap | Junit
let run ?quick ?bail ?fail_fast ?output_dir ?stream ?update ?snapshot_dir
?filter ?exclude ?failed ?list_only ?format ?junit ?seed ?timeout
?prop_count ?tags ?exclude_tags ?argv name tests =
let cli = Cli.parse (Option.value ~default:Sys.argv argv) in
let list_only =
Option.fold ~none:cli.list_only ~some:Option.some list_only
|> Option.value ~default:false
in
if list_only then begin
Runner.list_tests name tests;
exit 0
end;
let config =
Cli.resolve_config ?quick ?bail ?fail_fast ?output_dir ?stream ?update
?snapshot_dir ?filter ?exclude ?failed ?format ?junit ?seed ?timeout
?prop_count ?tags ?exclude_tags cli
in
let result = Runner.run ~config name tests in
if result.failed > 0 then exit 1
else if result.passed = 0 && result.skipped = 0 then begin
Pp.epr "%a No tests matched the current filter.@."
(Pp.styled `Yellow Pp.string)
"[WARNING]";
exit 2
end
else exit 0
let equal = Check.equal
let not_equal = Check.not_equal
let is_true = Check.is_true
let is_false = Check.is_false
let is_some = Check.is_some
let is_none = Check.is_none
let some = Check.some
let is_ok = Check.is_ok
let is_error = Check.is_error
let ok = Check.ok
let error = Check.error
let raises = Check.raises
let raises_match = Check.raises_match
let no_raise = Check.no_raise
let raises_invalid_arg = Check.raises_invalid_arg
let raises_failure = Check.raises_failure
let fail = Check.fail
let failf = Check.failf
let skip = Failure.skip
let snapshot = Snapshot.snapshot
let snapshot_pp = Snapshot.snapshot_pp
let snapshotf = Snapshot.snapshotf
let output = Expect.output
let expect = Expect.expect
let expect_exact = Expect.expect_exact
let capture = Expect.capture
let capture_exact = Expect.capture_exact
module Gen = Windtrap_prop.Gen
let assume = Windtrap_prop.assume
let reject = Windtrap_prop.reject
let collect = Windtrap_prop.collect
let classify = Windtrap_prop.classify
let cover = Windtrap_prop.cover
let testable_to_arbitrary name testable =
let gen =
match Testable.gen testable with
| Some g -> g
| None -> invalid_arg (Pp.str "Testable for '%s' has no generator" name)
in
Windtrap_prop.Arbitrary.make ~gen ~print:(Pp.to_string (Testable.pp testable))
let prop ?(config = Windtrap_prop.Prop.default_config) ?pos ?tags ?timeout name
testable law =
let arb = testable_to_arbitrary name testable in
Test.test ?pos ?tags ?timeout name (fun () ->
match Windtrap_prop.Prop.check ~config arb law with
| Windtrap_prop.Prop.Success _ -> ()
| Windtrap_prop.Prop.Failed
{ count; seed; shrunk_counterexample; shrink_steps; _ } ->
let shrink_info =
if shrink_steps > 0 then
Printf.sprintf ", shrunk in %d steps" shrink_steps
else ""
in
let test_num = count + 1 in
let plural = if test_num = 1 then "" else "s" in
let msg =
Printf.sprintf
"Property failed after %d test%s%s (seed=%d)\n\
Counterexample: %s\n\
%s"
test_num plural shrink_info seed
(Pp.styled_string `Red shrunk_counterexample)
(Pp.styled_string `Faint
(Printf.sprintf "Replay with: WINDTRAP_SEED=%d" seed))
in
Failure.raise_failure ?pos msg
| Windtrap_prop.Prop.Error
{ count; seed; counterexample; exn; backtrace; _ } ->
let test_num = count + 1 in
let plural = if test_num = 1 then "" else "s" in
let backtrace_str = String.trim backtrace in
let backtrace_part =
if backtrace_str = "" then "" else "\n" ^ backtrace_str
in
let msg =
Printf.sprintf
"Property raised exception after %d test%s (seed=%d)\n\
Exception: %s\n\
Counterexample: %s\n\
%s%s"
test_num plural seed
(Pp.styled_string `Red (Printexc.to_string exn))
(Pp.styled_string `Red counterexample)
(Pp.styled_string `Faint
(Printf.sprintf "Replay with: WINDTRAP_SEED=%d" seed))
backtrace_part
in
Failure.raise_failure ?pos msg
| Windtrap_prop.Prop.Coverage_failed
{ count; discarded; seed; missing; collected } ->
let coverage_lines =
missing
|> List.map (fun issue ->
Printf.sprintf "- %s: required >= %.1f%%, got %.1f%% (%d/%d)"
issue.Windtrap_prop.Prop.label
issue.Windtrap_prop.Prop.required
issue.Windtrap_prop.Prop.actual issue.Windtrap_prop.Prop.hits
count)
in
let collected_lines =
match collected with
| [] -> [ "- (none)" ]
| _ ->
List.map
(fun (label, hits) ->
let pct =
if count <= 0 then 0.0
else float_of_int hits *. 100.0 /. float_of_int count
in
Printf.sprintf "- %s: %.1f%% (%d/%d)" label pct hits count)
collected
in
let msg =
Printf.sprintf
"Property coverage failed after %d successful tests (%d \
discarded, seed=%d)\n\
Missing coverage:\n\
%s\n\
Collected buckets:\n\
%s\n\
%s"
count discarded seed
(String.concat "\n" coverage_lines)
(String.concat "\n" collected_lines)
(Pp.styled_string `Faint
(Printf.sprintf "Replay with: WINDTRAP_SEED=%d" seed))
in
Failure.raise_failure ?pos msg
| Windtrap_prop.Prop.Gave_up { count; discarded; seed } ->
let msg =
Printf.sprintf
"Gave up after %d successful tests (%d discarded, seed=%d). Too \
many cases discarded."
count discarded seed
in
Failure.raise_failure ?pos msg)
let prop' ?config ?pos ?tags ?timeout name testable fn =
let law x =
fn x;
true
in
prop ?config ?pos ?tags ?timeout name testable law
let prop2 ?config ?pos ?tags ?timeout name a b law =
prop ?config ?pos ?tags ?timeout name (Testable.pair a b) (fun (x, y) ->
law x y)
let prop3 ?config ?pos ?tags ?timeout name a b c law =
prop ?config ?pos ?tags ?timeout name (Testable.triple a b c)
(fun (x, y, z) -> law x y z)
let prop4 ?config ?pos ?tags ?timeout name a b c d law =
prop ?config ?pos ?tags ?timeout name (Testable.quad a b c d)
(fun (w, x, y, z) -> law w x y z)