Source file expect_test_helpers_base.ml
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
open! Base
open! Stdio
include Expect_test_helpers_base_intf
let print_string string _string string;
Out_channel.(flush stdout)
;;
module CR = struct
include CR
let message t here =
let cr cr =
String.concat
[ "(* "
; cr
; " require-failed: "
; here |> Source_code_position.to_string
; ".\n"
; " Do not 'X' this CR; instead make the required property true,\n"
; " which will make the CR disappear. For more information, see\n"
; " [Expect_test_helpers_base.require]. *)"
]
in
match t with
| CR -> cr "CR"
| CR_soon -> cr "CR-soon"
| CR_someday -> cr "CR-someday"
| Comment ->
String.concat
[ "(* require-failed: "; here |> Source_code_position.to_string; ". *)" ]
| Suppress -> ""
;;
let hide_unstable_output = function
| CR -> false
| CR_soon | CR_someday | Comment | Suppress -> true
;;
end
module Sexp_style = struct
include Sexp_style
let default_pretty = Pretty (Sexp_pretty.Config.create ~color:false ())
let simple_pretty =
Pretty
{ indent = 1
; data_alignment = Data_not_aligned
; color_scheme = [||]
; atom_coloring = Color_none
; atom_printing = Escaped
; paren_coloring = false
; opening_parens = Same_line
; closing_parens = Same_line
; comments = Drop
; singleton_limit = Singleton_limit (Atom_threshold 0, Character_threshold 0)
; leading_threshold = Atom_threshold 0, Character_threshold 0
; separator = No_separator
; sticky_comments = After
}
;;
end
module Phys_equal (M : sig
type t [@@deriving sexp_of]
end) =
struct
type t = M.t [@@deriving sexp_of]
let equal = phys_equal
end
let hide_temp_files_in_string =
let re = lazy (Re.compile (Re.seq [ Re.str ".tmp."; Re.repn Re.alnum 6 (Some 6) ])) in
fun string -> Re.replace_string (force re) ~by:".tmp.RANDOM" string
;;
let hide_positions_in_string =
let module Re = Re.Pcre in
let expanders =
lazy
([
"[a-zA-z]:[0-9]+:[0-9]+", 1, ":LINE:COL"
; "line [0-9]+:", 0, "line LINE:"
; "line [0-9]+, characters [0-9]+-[0-9]+", 0, "line LINE, characters C1-C2"
]
|> List.map ~f:(fun (pattern, prefix_len, expansion) ->
let rex = Re.regexp pattern in
fun string ->
Re.substitute ~rex string ~subst:(fun orig ->
String.concat [ String.prefix orig prefix_len; expansion ])))
in
fun string ->
List.fold (force expanders) ~init:string ~f:(fun ac expander -> expander ac)
;;
let maybe_hide_positions_in_string ?(hide_positions = false) string =
if hide_positions then hide_positions_in_string string else string
;;
let sexp_style = ref Sexp_style.default_pretty
let sexp_to_string ?hide_positions sexp =
let string =
match !sexp_style with
| To_string_mach -> Sexp.to_string_mach sexp ^ "\n"
| To_string_hum -> Sexp.to_string_hum sexp ^ "\n"
| Pretty config -> Sexp_pretty.pretty_string config sexp
in
maybe_hide_positions_in_string ?hide_positions string
;;
let replace = String.substr_replace_all
let rec replace_s (sexp : Sexp.t) ~pattern ~with_ : Sexp.t =
match sexp with
| Atom atom -> Atom (replace atom ~pattern ~with_)
| List list -> List (List.map list ~f:(replace_s ~pattern ~with_))
;;
let expect_test_output (here : Lexing.position) =
Ppx_expect_runtime.For_external.read_current_test_output_exn ~here
;;
let am_running_expect_test = Ppx_expect_runtime.For_external.am_running_expect_test
let assert_am_running_expect_test here =
if am_running_expect_test ()
then ()
else
raise_s
[%message
"This code should be run inside an expect test; currently, it is running outside \
an expect test."
~_:(here : Source_code_position.t)]
;;
let wrap f =
Staged.stage (fun ?hide_positions string ->
f (maybe_hide_positions_in_string ?hide_positions string))
;;
let rec smash_sexp sexp ~f =
match f sexp with
| Sexp.List list -> Sexp.List (List.map list ~f:(smash_sexp ~f))
| Sexp.Atom _ as atom -> atom
;;
let remove_backtraces =
let prefixes =
List.concat
[
[ "Raised at "
; "Re-raised at "
; "Raised by primitive operation at "
; "Called from "
]
; [ "Caught by monitor " ]
]
in
smash_sexp ~f:(function
| Sexp.(List (Atom hd :: _))
when List.exists prefixes ~f:(fun prefix -> String.is_prefix ~prefix hd) ->
Sexp.(List [ Atom "ELIDED BACKTRACE" ])
| s -> s)
;;
let print_endline = Staged.unstage (wrap print_endline)
let print_string = Staged.unstage (wrap print_string)
let print_s ?hide_positions sexp = print_string (sexp_to_string ?hide_positions sexp)
let on_print_cr = ref (fun string -> print_endline string)
let print_cr_with_optional_message
?(cr = CR.CR)
?(hide_positions = CR.hide_unstable_output cr)
here
optional_message
=
assert_am_running_expect_test here;
match cr with
| Suppress -> ()
| _ ->
let cr = CR.message cr here |> maybe_hide_positions_in_string ~hide_positions in
!on_print_cr
(match optional_message with
| None -> cr
| Some sexp ->
String.concat [ cr; "\n"; String.rstrip (sexp_to_string ~hide_positions sexp) ])
;;
let print_cr ?cr ?hide_positions here message =
print_cr_with_optional_message ?cr ?hide_positions here (Some message)
;;
let require ?cr ?hide_positions ?if_false_then_print_s here bool =
match bool with
| true -> ()
| false ->
print_cr_with_optional_message
?cr
?hide_positions
here
(Option.map if_false_then_print_s ~f:force)
;;
let require_equal
(type a)
?cr
?hide_positions
?if_false_then_print_s
?(message = "values are not equal")
here
(module M : With_equal with type t = a)
x
y
=
require
?cr
?hide_positions
here
(M.equal x y)
~if_false_then_print_s:
(lazy
[%message
message
~_:(x : M.t)
~_:(y : M.t)
~_:(if_false_then_print_s : (Sexp.t Lazy.t option[@sexp.option]))])
;;
let require_not_equal
(type a)
?cr
?hide_positions
?if_false_then_print_s
?(message = "values are equal")
here
(module M : With_equal with type t = a)
x
y
=
require
?cr
?hide_positions
here
(not (M.equal x y))
~if_false_then_print_s:
(lazy
[%message
message
~_:(x : M.t)
~_:(y : M.t)
~_:(if_false_then_print_s : (Sexp.t Lazy.t option[@sexp.option]))])
;;
let require_compare_equal
(type a)
?cr
?hide_positions
?message
here
(module M : With_compare with type t = a)
x
y
=
require_equal
?cr
?hide_positions
?message
here
(module struct
include M
let equal = [%compare.equal: t]
end)
x
y
;;
let require_compare_not_equal
(type a)
?cr
?hide_positions
?message
here
(module M : With_compare with type t = a)
x
y
=
require_not_equal
?cr
?hide_positions
?message
here
(module struct
include M
let equal = [%compare.equal: t]
end)
x
y
;;
let require_sets_are_equal
(type elt)
?cr
?hide_positions
?(names = "first", "second")
here
first
second
=
let module Elt = struct
type t = elt
let sexp_of_t = (Set.comparator first).sexp_of_t
end
in
require
?cr
?hide_positions
here
(Set.equal first second)
~if_false_then_print_s:
(lazy
(let show_diff (name1, set1) (name2, set2) =
let diff = Set.diff set1 set2 in
if Set.is_empty diff
then [%message]
else
[%message
(Printf.sprintf "in %s but not in %s" name1 name2) ~_:(diff : Set.M(Elt).t)]
in
let first = fst names, first in
let second = snd names, second in
[%message.omit_nil
"sets are not equal"
~_:(show_diff first second : Sexp.t)
~_:(show_diff second first : Sexp.t)]))
;;
type try_with_result =
| Did_not_raise
| Raised of Sexp.t
let try_with ?raise_message ?(show_backtrace = false) (type a) (f : unit -> a) =
Backtrace.Exn.with_recording show_backtrace ~f:(fun () ->
match ignore (f () : a) with
| () -> Did_not_raise
| exception exn ->
let backtrace =
if not show_backtrace then None else Some (Backtrace.Exn.most_recent ())
in
Ref.set_temporarily Backtrace.elide (not show_backtrace) ~f:(fun () ->
Raised
[%message
""
~_:(raise_message : (string option[@sexp.option]))
~_:(exn : exn)
(backtrace : (Backtrace.t option[@sexp.option]))])) [@nontail]
;;
let require_does_not_raise ?cr ?hide_positions ?show_backtrace here f =
match try_with f ?show_backtrace ~raise_message:"unexpectedly raised" with
| Did_not_raise -> ()
| Raised message -> print_cr ?cr ?hide_positions here message
;;
let require_does_raise ?cr ?(hide_positions = false) ?show_backtrace here f =
match try_with f ?show_backtrace with
| Raised message -> print_s ~hide_positions message
| Did_not_raise -> print_cr ?cr ~hide_positions here [%message "did not raise"]
;;
let require_first_gen
(type first second)
?cr
?hide_positions
?(print_first : (first -> Sexp.t) option)
~message
here
(sexp_of_second : second -> Sexp.t)
(either : (first, second) Either.t)
=
match either with
| First first ->
(match print_first with
| None -> ()
| Some sexp_of_first -> print_s [%sexp (first : first)])
| Second second ->
print_cr ?cr ?hide_positions here [%message message ~_:(second : second)]
;;
let require_first = require_first_gen ~message:"unexpected [Second]"
let require_second ?cr ?hide_positions ?print_second here print_first either =
require_first_gen
?cr
?hide_positions
?print_first:print_second
~message:"unexpected [First]"
here
print_first
(Either.swap either)
;;
let require_some ?cr ?hide_positions ?print_some here option =
require_first_gen
?cr
~message:"unexpected [None]"
?hide_positions
?print_first:print_some
here
[%sexp_of: unit]
(match option with
| Some some -> First some
| None -> Second ())
;;
let require_none ?cr ?hide_positions here sexp_of_some option =
require_first_gen
?cr
~message:"unexpected [Some]"
?hide_positions
here
sexp_of_some
(match option with
| None -> First ()
| Some some -> Second some)
;;
let require_ok_result ?cr ?hide_positions ?print_ok here sexp_of_error result =
require_first_gen
?cr
~message:"unexpected [Error]"
?hide_positions
?print_first:print_ok
here
sexp_of_error
(match result with
| Ok ok -> First ok
| Error error -> Second error)
;;
let require_error_result ?cr ?hide_positions ?print_error here sexp_of_ok result =
require_first_gen
?cr
~message:"unexpected [Ok]"
?hide_positions
?print_first:print_error
here
sexp_of_ok
(match result with
| Error error -> First error
| Ok ok -> Second ok)
;;
let require_ok ?cr ?hide_positions ?print_ok here res =
require_ok_result ?cr ?hide_positions ?print_ok here [%sexp_of: Error.t] res
;;
let require_error ?cr ?hide_positions ?(print_error = false) here sexp_of_ok res =
let print_error = Option.some_if print_error [%sexp_of: Error.t] in
require_error_result ?cr ?hide_positions ?print_error here sexp_of_ok res
;;
let print_and_check_round_trip
(type a)
?cr
?hide_positions
here
(module T : With_equal with type t = a)
reprs
examples
=
require_does_not_raise ?cr ?hide_positions here (fun () ->
let tag_of name =
String.map name ~f:(fun char -> if Char.is_alphanum char then char else '_')
in
List.iter examples ~f:(fun t ->
let conversions =
List.map reprs ~f:(fun (module M : With_round_trip with type t = a) ->
let repr = M.to_repr t in
let sexp = M.sexp_of_repr repr in
let round_trip_result = M.of_repr repr in
let name = M.repr_name in
name, sexp, round_trip_result)
in
(match conversions with
| [] -> ()
| [ (_, sexp, _) ] ->
print_s sexp
| _ :: _ :: _ ->
print_s
(List
(List.map conversions ~f:(fun (name, sexp, _) ->
[%sexp (tag_of name : string), (sexp : Sexp.t)]))));
List.iter conversions ~f:(fun (name, sexp, round_trip) ->
require
?cr
?hide_positions
here
(T.equal t round_trip)
~if_false_then_print_s:
(lazy
(Sexp.message
(name ^ " serialization failed to round-trip")
[ "original", T.sexp_of_t t
; tag_of name, sexp
; tag_of name ^ "_roundtrip", T.sexp_of_t round_trip
])))))
;;
let print_and_check_stringable
(type a)
?cr
?hide_positions
here
(module T : With_stringable with type t = a)
list
=
let module T = struct
include T
let sexp_of_t t = Sexp.Atom (to_string t)
end
in
let module Conv = struct
type t = T.t
type repr = string [@@deriving sexp_of]
let to_repr = T.to_string
let of_repr = T.of_string
let repr_name = "string"
end
in
print_and_check_round_trip ?cr ?hide_positions here (module T) [ (module Conv) ] list
;;
let print_and_check_sexpable
(type a)
?cr
?hide_positions
here
(module T : With_sexpable with type t = a)
list
=
let module Conv = struct
type t = T.t
type repr = Sexp.t [@@deriving sexp_of]
let to_repr = T.sexp_of_t
let of_repr = T.t_of_sexp
let repr_name = "sexp"
end
in
print_and_check_round_trip ?cr ?hide_positions here (module T) [ (module Conv) ] list
;;
let show_raise (type a) ?hide_positions ?show_backtrace (f : unit -> a) =
print_s
?hide_positions
(match try_with f ?show_backtrace ~raise_message:"raised" with
| Did_not_raise -> [%message "did not raise"]
| Raised message -> message)
;;
let quickcheck_m
(type a)
here
?config
?cr
?examples
?hide_positions
(module M : Base_quickcheck.Test.S with type t = a)
~f
=
Base_quickcheck.Test.result
?config
?examples
(module M)
~f:(fun elt ->
let crs = Queue.create () in
Ref.set_temporarily on_print_cr (Queue.enqueue crs) ~f:(fun () ->
require_does_not_raise here ?cr ?hide_positions (fun () -> f elt));
if Queue.is_empty crs then Ok () else Error (Queue.to_list crs))
|> Result.iter_error ~f:(fun (input, output) ->
print_s [%message "quickcheck: test failed" (input : M.t)];
List.iter output ~f:print_endline)
;;
let quickcheck
(type a)
here
?cr
?hide_positions
?(seed = Base_quickcheck.Test.default_config.seed)
?(sizes = Base_quickcheck.Test.default_config.sizes)
?(trials = Base_quickcheck.Test.default_config.test_count)
?(shrinker = Base_quickcheck.Shrinker.atomic)
?(shrink_attempts = Base_quickcheck.Test.default_config.shrink_count)
?examples
~sexp_of
~f
quickcheck_generator
=
quickcheck_m
here
~config:{ seed; test_count = trials; shrink_count = shrink_attempts; sizes }
?cr
?examples
?hide_positions
(module struct
type t = a
let sexp_of_t = sexp_of
let quickcheck_generator = quickcheck_generator
let quickcheck_shrinker = shrinker
end)
~f
;;
module Tuple2 (M : Quickcheck.Test.S) : Quickcheck.Test.S with type t = M.t * M.t = struct
type t = M.t * M.t [@@deriving sexp_of]
let quickcheck_generator = [%quickcheck.generator: M.t * M.t]
let quickcheck_shrinker = [%quickcheck.shrinker: M.t * M.t]
end
module Tuple3 (M : Quickcheck.Test.S) : Quickcheck.Test.S with type t = M.t * M.t * M.t =
struct
type t = M.t * M.t * M.t [@@deriving sexp_of]
let quickcheck_generator = [%quickcheck.generator: M.t * M.t * M.t]
let quickcheck_shrinker = [%quickcheck.shrinker: M.t * M.t * M.t]
end
let test_compare here ?config ?cr ?hide_positions (module M : With_quickcheck_and_compare)
=
let check bool msg = require here ?cr ?hide_positions bool ~if_false_then_print_s:msg in
let check_reflexive x =
let compare_x_x = M.compare x x in
check
(compare_x_x = 0)
[%lazy_message "[compare] is not reflexive" (x : M.t) (compare_x_x : int)]
in
let check_asymmetric x y =
let compare_x_y = M.compare x y in
let compare_y_x = M.compare y x in
check
(Sign.equal (Sign.of_int compare_x_y) (Sign.flip (Sign.of_int compare_y_x)))
[%lazy_message
"[compare] is not asymmetric"
(x : M.t)
(y : M.t)
(compare_x_y : int)
(compare_y_x : int)]
in
let check_transitive x y z =
let compare_x_y = M.compare x y in
let compare_y_z = M.compare y z in
let compare_x_z = M.compare x z in
let expected : Ordering.t option =
match Ordering.of_int compare_x_y, Ordering.of_int compare_y_z with
| Equal, Equal -> Some Equal
| Less, Less | Less, Equal | Equal, Less -> Some Less
| Greater, Greater | Greater, Equal | Equal, Greater -> Some Greater
| Less, Greater | Greater, Less -> None
in
Option.iter expected ~f:(fun expected ->
check
(Ordering.equal expected (Ordering.of_int compare_x_z))
[%lazy_message
"[compare] is not transitive"
(x : M.t)
(y : M.t)
(z : M.t)
(compare_x_y : int)
(compare_y_z : int)
(compare_x_z : int)])
in
let test m ~f = quickcheck_m here ?config ?cr ?hide_positions m ~f in
test
(module M)
~f:(fun x ->
check_reflexive x;
check_asymmetric x x;
check_transitive x x x);
test
(module Tuple2 (M))
~f:(fun (x, y) ->
check_asymmetric x y;
check_transitive x x y;
check_transitive x y x;
check_transitive y x x);
test
(module Tuple3 (M))
~f:(fun (x, y, z) ->
check_transitive x y z;
check_transitive x z y;
check_transitive y x z)
;;
let test_equal here ?config ?cr ?hide_positions (module M : With_quickcheck_and_equal) =
let check bool msg = require here ?cr ?hide_positions bool ~if_false_then_print_s:msg in
let check_reflexive x =
let equal_x_x = M.equal x x in
check
equal_x_x
[%lazy_message "[equal] is not reflexive" (x : M.t) (equal_x_x : bool)]
in
let check_symmetric x y =
let equal_x_y = M.equal x y in
let equal_y_x = M.equal y x in
check
(Bool.equal equal_x_y equal_y_x)
[%lazy_message
"[equal] is not symmetric"
(x : M.t)
(y : M.t)
(equal_x_y : bool)
(equal_y_x : bool)]
in
let check_transitive x y z =
let equal_x_y = M.equal x y in
let equal_y_z = M.equal y z in
let equal_x_z = M.equal x z in
let msg =
[%lazy_message
"[equal] is not transitive"
(x : M.t)
(y : M.t)
(z : M.t)
(equal_x_y : bool)
(equal_y_z : bool)
(equal_x_z : bool)]
in
match equal_x_y, equal_y_z with
| true, true -> check equal_x_z msg
| true, false | false, true -> check (not equal_x_z) msg
| false, false -> ()
in
let test m ~f = quickcheck_m here ?config ?cr ?hide_positions m ~f in
test
(module M)
~f:(fun x ->
check_reflexive x;
check_symmetric x x;
check_transitive x x x);
test
(module Tuple2 (M))
~f:(fun (x, y) ->
check_symmetric x y;
check_transitive x x y;
check_transitive x y x;
check_transitive y x x);
test
(module Tuple3 (M))
~f:(fun (x, y, z) ->
check_transitive x y z;
check_transitive x z y;
check_transitive y x z)
;;
let test_compare_and_equal
here
?config
?cr
?hide_positions
(module M : With_quickcheck_and_compare_and_equal)
=
test_compare here ?config ?cr ?hide_positions (module M);
test_equal here ?config ?cr ?hide_positions (module M);
let check_agreement x y =
let compare_x_y = M.compare x y in
let equal_x_y = M.equal x y in
require
here
?cr
?hide_positions
(Bool.equal equal_x_y (compare_x_y = 0))
~if_false_then_print_s:
[%lazy_message
"[compare] and [equal] do not agree"
(x : M.t)
(y : M.t)
(compare_x_y : int)
(equal_x_y : bool)]
in
quickcheck_m
here
?config
?cr
?hide_positions
(module Tuple2 (M))
~f:(fun (x, y) ->
check_agreement x y;
check_agreement y x)
;;