forked from rescript-lang/rescript
-
Notifications
You must be signed in to change notification settings - Fork 0
/
oUnit.ml
621 lines (553 loc) · 16.3 KB
/
oUnit.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
(***********************************************************************)
(* The OUnit library *)
(* *)
(* Copyright (C) 2002-2008 Maas-Maarten Zeeman. *)
(* Copyright (C) 2010 OCamlCore SARL *)
(* *)
(* See LICENSE for details. *)
(***********************************************************************)
open OUnitUtils
include OUnitTypes
(*
* Types and global states.
*)
let global_verbose = ref false
let global_output_file =
let pwd = Sys.getcwd () in
let ocamlbuild_dir = Filename.concat pwd "_build" in
let dir =
if Sys.file_exists ocamlbuild_dir && Sys.is_directory ocamlbuild_dir then
ocamlbuild_dir
else
pwd
in
ref (Some (Filename.concat dir "oUnit.log"))
let global_logger = ref (fst OUnitLogger.null_logger)
let global_chooser = ref OUnitChooser.simple
let bracket set_up f tear_down () =
let fixture =
set_up ()
in
let () =
try
let () = f fixture in
tear_down fixture
with e ->
let () =
tear_down fixture
in
raise e
in
()
let bracket_tmpfile ?(prefix="ounit-") ?(suffix=".txt") ?mode f =
bracket
(fun () ->
Filename.open_temp_file ?mode prefix suffix)
f
(fun (fn, chn) ->
begin
try
close_out chn
with _ ->
()
end;
begin
try
Sys.remove fn
with _ ->
()
end)
exception Skip of string
let skip_if b msg =
if b then
raise (Skip msg)
exception Todo of string
let todo msg =
raise (Todo msg)
let assert_failure msg =
failwith ("OUnit: " ^ msg)
let assert_bool msg b =
if not b then assert_failure msg
let assert_string str =
if not (str = "") then assert_failure str
let assert_equal ?(cmp = ( = )) ?printer ?pp_diff ?msg expected actual =
let get_error_string () =
let res =
buff_format_printf
(fun fmt ->
Format.pp_open_vbox fmt 0;
begin
match msg with
| Some s ->
Format.pp_open_box fmt 0;
Format.pp_print_string fmt s;
Format.pp_close_box fmt ();
Format.pp_print_cut fmt ()
| None ->
()
end;
begin
match printer with
| Some p ->
Format.fprintf fmt
"@[expected: @[%s@]@ but got: @[%s@]@]@,"
(p expected)
(p actual)
| None ->
Format.fprintf fmt "@[not equal@]@,"
end;
begin
match pp_diff with
| Some d ->
Format.fprintf fmt
"@[differences: %a@]@,"
d (expected, actual)
| None ->
()
end;
Format.pp_close_box fmt ())
in
let len =
String.length res
in
if len > 0 && res.[len - 1] = '\n' then
String.sub res 0 (len - 1)
else
res
in
if not (cmp expected actual) then
assert_failure (get_error_string ())
let assert_command
?(exit_code=Unix.WEXITED 0)
?(sinput=Stream.of_list [])
?(foutput=ignore)
?(use_stderr=true)
?env
?verbose
prg args =
bracket_tmpfile
(fun (fn_out, chn_out) ->
let cmd_print fmt =
let () =
match env with
| Some e ->
begin
Format.pp_print_string fmt "env";
Array.iter (Format.fprintf fmt "@ %s") e;
Format.pp_print_space fmt ()
end
| None ->
()
in
Format.pp_print_string fmt prg;
List.iter (Format.fprintf fmt "@ %s") args
in
(* Start the process *)
let in_write =
Unix.dup (Unix.descr_of_out_channel chn_out)
in
let (out_read, out_write) =
Unix.pipe ()
in
let err =
if use_stderr then
in_write
else
Unix.stderr
in
let args =
Array.of_list (prg :: args)
in
let pid =
OUnitLogger.printf !global_logger "%s"
(buff_format_printf
(fun fmt ->
Format.fprintf fmt "@[Starting command '%t'@]\n" cmd_print));
Unix.set_close_on_exec out_write;
match env with
| Some e ->
Unix.create_process_env prg args e out_read in_write err
| None ->
Unix.create_process prg args out_read in_write err
in
let () =
Unix.close out_read;
Unix.close in_write
in
let () =
(* Dump sinput into the process stdin *)
let buff = Bytes.of_string " " in
Stream.iter
(fun c ->
let _i : int =
Bytes.set buff 0 c;
Unix.write out_write buff 0 1
in
())
sinput;
Unix.close out_write
in
let _, real_exit_code =
let rec wait_intr () =
try
Unix.waitpid [] pid
with Unix.Unix_error (Unix.EINTR, _, _) ->
wait_intr ()
in
wait_intr ()
in
let exit_code_printer =
function
| Unix.WEXITED n ->
Printf.sprintf "exit code %d" n
| Unix.WSTOPPED n ->
Printf.sprintf "stopped by signal %d" n
| Unix.WSIGNALED n ->
Printf.sprintf "killed by signal %d" n
in
(* Dump process output to stderr *)
begin
let chn = open_in fn_out in
let buff = Bytes.make 4096 'X' in
let len = ref (-1) in
while !len <> 0 do
len := input chn buff 0 (Bytes.length buff);
OUnitLogger.printf !global_logger "%s" (Bytes.to_string @@ Bytes.sub buff 0 !len);
done;
close_in chn
end;
(* Check process status *)
assert_equal
~msg:(buff_format_printf
(fun fmt ->
Format.fprintf fmt
"@[Exit status of command '%t'@]" cmd_print))
~printer:exit_code_printer
exit_code
real_exit_code;
begin
let chn = open_in fn_out in
try
foutput (Stream.of_channel chn)
with e ->
close_in chn;
raise e
end)
()
let raises f =
try
f ();
None
with e ->
Some e
let assert_raises ?msg exn (f: unit -> 'a) =
let pexn =
Printexc.to_string
in
let get_error_string () =
let str =
Format.sprintf
"expected exception %s, but no exception was raised."
(pexn exn)
in
match msg with
| None ->
assert_failure str
| Some s ->
assert_failure (s^"\n"^str)
in
match raises f with
| None ->
assert_failure (get_error_string ())
| Some e ->
assert_equal ?msg ~printer:pexn exn e
let assert_raise_any ?msg (f: unit -> 'a) =
let pexn =
Printexc.to_string
in
let get_error_string () =
let str =
Format.sprintf
"expected exception , but no exception was raised."
in
match msg with
| None ->
assert_failure str
| Some s ->
assert_failure (s^"\n"^str)
in
match raises f with
| None ->
assert_failure (get_error_string ())
| Some exn ->
assert_bool (pexn exn) true
(* Compare floats up to a given relative error *)
let cmp_float ?(epsilon = 0.00001) a b =
abs_float (a -. b) <= epsilon *. (abs_float a) ||
abs_float (a -. b) <= epsilon *. (abs_float b)
(* Now some handy shorthands *)
let (@?) = assert_bool
(* Some shorthands which allows easy test construction *)
let (>:) s t = TestLabel(s, t) (* infix *)
let (>::) s f = TestLabel(s, TestCase(f)) (* infix *)
let (>:::) s l = TestLabel(s, TestList(l)) (* infix *)
(* Utility function to manipulate test *)
let rec test_decorate g =
function
| TestCase f ->
TestCase (g f)
| TestList tst_lst ->
TestList (List.map (test_decorate g) tst_lst)
| TestLabel (str, tst) ->
TestLabel (str, test_decorate g tst)
let test_case_count = OUnitUtils.test_case_count
let string_of_node = OUnitUtils.string_of_node
let string_of_path = OUnitUtils.string_of_path
(* Returns all possible paths in the test. The order is from test case
to root
*)
let test_case_paths test =
let rec tcps path test =
match test with
| TestCase _ ->
[path]
| TestList tests ->
List.concat
(mapi (fun t i -> tcps ((ListItem i)::path) t) tests)
| TestLabel (l, t) ->
tcps ((Label l)::path) t
in
tcps [] test
(* Test filtering with their path *)
module SetTestPath = Set.Make(String)
let test_filter ?(skip=false) only test =
let set_test =
List.fold_left
(fun st str -> SetTestPath.add str st)
SetTestPath.empty
only
in
let rec filter_test path tst =
if SetTestPath.mem (string_of_path path) set_test then
begin
Some tst
end
else
begin
match tst with
| TestCase f ->
begin
if skip then
Some
(TestCase
(fun () ->
skip_if true "Test disabled";
f ()))
else
None
end
| TestList tst_lst ->
begin
let ntst_lst =
fold_lefti
(fun ntst_lst tst i ->
let nntst_lst =
match filter_test ((ListItem i) :: path) tst with
| Some tst ->
tst :: ntst_lst
| None ->
ntst_lst
in
nntst_lst)
[]
tst_lst
in
if not skip && ntst_lst = [] then
None
else
Some (TestList (List.rev ntst_lst))
end
| TestLabel (lbl, tst) ->
begin
let ntst_opt =
filter_test
((Label lbl) :: path)
tst
in
match ntst_opt with
| Some ntst ->
Some (TestLabel (lbl, ntst))
| None ->
if skip then
Some (TestLabel (lbl, tst))
else
None
end
end
in
filter_test [] test
(* The possible test results *)
let is_success = OUnitUtils.is_success
let is_failure = OUnitUtils.is_failure
let is_error = OUnitUtils.is_error
let is_skip = OUnitUtils.is_skip
let is_todo = OUnitUtils.is_todo
(* TODO: backtrace is not correct *)
let maybe_backtrace = ""
(* Printexc.get_backtrace () *)
(* (if Printexc.backtrace_status () then *)
(* "\n" ^ Printexc.get_backtrace () *)
(* else "") *)
(* Events which can happen during testing *)
(* DEFINE MAYBE_BACKTRACE = *)
(* IFDEF BACKTRACE THEN *)
(* (if Printexc.backtrace_status () then *)
(* "\n" ^ Printexc.get_backtrace () *)
(* else "") *)
(* ELSE *)
(* "" *)
(* ENDIF *)
(* Run all tests, report starts, errors, failures, and return the results *)
let perform_test report test =
let run_test_case f path =
try
f ();
RSuccess path
with
| Failure s ->
RFailure (path, s ^ maybe_backtrace)
| Skip s ->
RSkip (path, s)
| Todo s ->
RTodo (path, s)
| s ->
RError (path, (Printexc.to_string s) ^ maybe_backtrace)
in
let rec flatten_test path acc =
function
| TestCase(f) ->
(path, f) :: acc
| TestList (tests) ->
fold_lefti
(fun acc t cnt ->
flatten_test
((ListItem cnt)::path)
acc t)
acc tests
| TestLabel (label, t) ->
flatten_test ((Label label)::path) acc t
in
let test_cases = List.rev (flatten_test [] [] test) in
let runner (path, f) =
let result =
report (EStart path);
run_test_case f path
in
report (EResult result);
report (EEnd path);
result
in
let rec iter state =
match state.tests_planned with
| [] ->
state.results
| _ ->
let (path, f) = !global_chooser state in
let result = runner (path, f) in
iter
{
results = result :: state.results;
tests_planned =
List.filter
(fun (path', _) -> path <> path') state.tests_planned
}
in
iter {results = []; tests_planned = test_cases}
(* Function which runs the given function and returns the running time
of the function, and the original result in a tuple *)
let time_fun f x y =
let begin_time = Unix.gettimeofday () in
let result = f x y in
let end_time = Unix.gettimeofday () in
(end_time -. begin_time, result)
(* A simple (currently too simple) text based test runner *)
let run_test_tt ?verbose test =
let log, log_close =
OUnitLogger.create
!global_output_file
!global_verbose
OUnitLogger.null_logger
in
let () =
global_logger := log
in
(* Now start the test *)
let running_time, results =
time_fun
perform_test
(fun ev ->
log (OUnitLogger.TestEvent ev))
test
in
(* Print test report *)
log (OUnitLogger.GlobalEvent (GResults (running_time, results, test_case_count test)));
(* Reset logger. *)
log_close ();
global_logger := fst OUnitLogger.null_logger;
(* Return the results possibly for further processing *)
results
(* Call this one from you test suites *)
let run_test_tt_main ?(arg_specs=[]) ?(set_verbose=ignore) suite =
let only_test = ref [] in
let () =
Arg.parse
(Arg.align
[
"-verbose",
Arg.Set global_verbose,
" Run the test in verbose mode.";
"-only-test",
Arg.String (fun str -> only_test := str :: !only_test),
"path Run only the selected test";
"-output-file",
Arg.String (fun s -> global_output_file := Some s),
"fn Output verbose log in this file.";
"-no-output-file",
Arg.Unit (fun () -> global_output_file := None),
" Prevent to write log in a file.";
"-list-test",
Arg.Unit
(fun () ->
List.iter
(fun pth ->
print_endline (string_of_path pth))
(test_case_paths suite);
exit 0),
" List tests";
] @ arg_specs
)
(fun x -> raise (Arg.Bad ("Bad argument : " ^ x)))
("usage: " ^ Sys.argv.(0) ^ " [-verbose] [-only-test path]*")
in
let nsuite =
if !only_test = [] then
suite
else
begin
match test_filter ~skip:true !only_test suite with
| Some test ->
test
| None ->
failwith ("Filtering test "^
(String.concat ", " !only_test)^
" lead to no test")
end
in
let result =
set_verbose !global_verbose;
run_test_tt ~verbose:!global_verbose nsuite
in
if not (was_successful result) then
exit 1
else
result