Source file clerk_report.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
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
(** This module defines and manipulates Clerk test reports, which can be written
by `clerk runtest` and read to provide test result summaries. This only
concerns cli tests (```catala-test-cli blocks). *)
open Catala_utils
open Shared_ast
type pos = Lexing.position * Lexing.position
type inline_test = {
i_success : bool;
i_command_line : string list;
i_expected : pos;
i_result : pos;
}
type scope_test = {
s_success : bool;
s_name : string;
s_command_line : string list;
s_errors : (pos * string) list;
s_time : float;
s_coverage : Coverage.coverage_map option;
}
type file = {
name : File.t;
successful : int;
total : int;
tests : inline_test list;
scopes : scope_test list;
coverage : Coverage.coverage_map option;
}
type disp_flags = {
mutable files : [ `All | `Failed | `None ];
mutable tests : [ `All | `FailedFile | `Failed | `None ];
mutable diffs : bool;
mutable diff_command : string option option;
mutable fix_path : File.t -> File.t;
mutable coverage : bool;
}
let disp_flags =
{
files = `Failed;
tests = `FailedFile;
diffs = true;
diff_command = None;
fix_path = Fun.id;
coverage = false;
}
let set_display_flags
?(files = disp_flags.files)
?(tests = disp_flags.tests)
?(diffs = disp_flags.diffs)
?(diff_command = disp_flags.diff_command)
?(fix_path = disp_flags.fix_path)
?(coverage = disp_flags.coverage)
() =
disp_flags.files <- files;
disp_flags.tests <- tests;
disp_flags.diffs <- diffs;
disp_flags.diff_command <- diff_command;
disp_flags.fix_path <- fix_path;
disp_flags.coverage <- coverage
let write_to f file =
File.with_out_channel ~bin:true f (fun oc ->
Marshal.to_channel oc (file : file) [])
let read_from f = File.with_in_channel ~bin:true f Marshal.from_channel
let read_many f =
Message.debug "Reading test results from %a" File.format f;
File.with_in_channel ~bin:true f
@@ fun ic ->
let rec results acc =
match Marshal.from_channel ic with
| file -> results (File.Map.add file.name file acc)
| exception End_of_file -> acc
in
results File.Map.empty
let has_command cmd =
match File.get_command cmd with _ -> true | exception Not_found -> false
type 'a diff = Eq of 'a | Subs of 'a * 'a | Del of 'a | Add of 'a
let colordiff_str s1 s2 =
let split_re =
Re.(compile (alt [set "=()[]{};-,"; rep1 space; rep1 digit]))
in
let split s =
Re.Seq.split_full split_re s
|> Seq.map (function `Text t -> t | `Delim g -> Re.Group.get g 0)
in
let a1 = Array.of_seq (split s1) in
let n1 = Array.length a1 in
let a2 = Array.of_seq (split s2) in
let n2 = Array.length a2 in
let d = Array.make_matrix n1 n2 (0, []) in
let get i1 i2 =
if i1 < 0 then
( i2 + 1,
Array.fold_left (fun acc c -> Add c :: acc) [] (Array.sub a2 0 (i2 + 1))
)
else if i2 < 0 then
( i1 + 1,
Array.fold_left (fun acc c -> Del c :: acc) [] (Array.sub a1 0 (i1 + 1))
)
else d.(i1).(i2)
in
for i1 = 0 to n1 - 1 do
for i2 = 0 to n2 - 1 do
if a1.(i1) = a2.(i2) then
let eq, eqops = get (i1 - 1) (i2 - 1) in
d.(i1).(i2) <- eq, Eq a1.(i1) :: eqops
else
let del, delops = get (i1 - 1) i2 in
let add, addops = get i1 (i2 - 1) in
let subs, subsops = get (i1 - 1) (i2 - 1) in
if subs <= del && subs <= add then
d.(i1).(i2) <- subs + 1, Subs (a1.(i1), a2.(i2)) :: subsops
else if del <= add then d.(i1).(i2) <- del + 1, Del a1.(i1) :: delops
else d.(i1).(i2) <- add + 1, Add a2.(i2) :: addops
done
done;
let _, rops = get (n1 - 1) (n2 - 1) in
let ops = List.rev rops in
let pr_left ppf () =
Format.pp_print_list
~pp_sep:(fun _ () -> ())
(fun ppf -> function
| Eq w -> Format.fprintf ppf "%s" w
| Subs (w, _) | Del w -> Format.fprintf ppf "@{<green>%s@}" w
| Add _ -> ())
ppf ops
in
let pr_right ppf () =
Format.pp_print_list
~pp_sep:(fun _ () -> ())
(fun ppf -> function
| Eq w -> Format.fprintf ppf "%s" w
| Subs (_, w) | Add w -> Format.fprintf ppf "@{<red>%s@}" w
| Del _ -> ())
ppf ops
in
pr_left, pr_right
let no_tabs s = Re.(replace_string ~by:" " (compile (char '\t')) s)
let diff_command =
lazy begin match disp_flags.diff_command with
| None ->
let open Clerk_utils.Diff.Make (struct
include String
end) in
let stringdiff ppf s1 s2 =
let width = Message.terminal_columns () - 5 in
let mid = (width - 1) / 2 in
let cut s = String.cut_at_width (no_tabs s) mid in
let pad s =
let s = cut s in
Printf.sprintf "%s%*s" s (mid - String.width s) ""
in
let rec print_diff = function
| [] -> ()
| Equal a :: diff ->
Array.iter
(fun l -> Format.fprintf ppf "%s@{<blue>│@}%s@," (pad l) (cut l))
a;
print_diff diff
| Deleted l :: Added r :: diff | Added r :: Deleted l :: diff ->
let n_changed_lines = min (Array.length l) (Array.length r) in
for i = 0 to min (Array.length l) (Array.length r) - 1 do
let ppleft, ppright = colordiff_str (pad l.(i)) (cut r.(i)) in
Format.fprintf ppf "%a@{<blue>╳@}%a@," ppleft () ppright ()
done;
for i = n_changed_lines to Array.length l - 1 do
Format.fprintf ppf "%s@{<blue>╳@}@{<red>-@}@," (pad l.(i))
done;
for i = n_changed_lines to Array.length r - 1 do
Format.fprintf ppf "%*s@{<red>-@}@{<blue>╳@}@{<red>%s@}@," (mid - 1)
""
(cut r.(i))
done;
print_diff diff
| Deleted l :: diff ->
Array.iter
(fun l -> Format.fprintf ppf "%s@{<blue>╳@}@{<red>-@}@," (pad l))
l;
print_diff diff
| Added r :: diff ->
Array.iter
(fun r ->
Format.fprintf ppf "%*s@{<red>-@}@{<blue>╳@}@{<red>%s@}@,"
(mid - 1) "" (cut r))
r;
print_diff diff
in
let to_array s =
String.split_on_char '\n' s
|> List.to_seq
|> Seq.map String.trim_end
|> Array.of_seq
in
match get_diff (to_array s1) (to_array s2) with
| [Equal _] ->
Format.fprintf ppf "@[<hov>@{<red>%a@}@]" Format.pp_print_text
"Test failed, but no file differences were found. Maybe check \
whitespace, or try running with `--diff` ?"
| diff ->
Format.fprintf ppf "@{<blue;ul>%*sReference%*s│%*sResult%*s@}@,"
((mid - 9) / 2)
""
(mid - 9 - ((mid - 9) / 2))
""
((width - mid - 7) / 2)
""
(width - mid - 7 - ((width - mid - 7) / 2))
"";
print_diff diff
in
`Stringdiff stringdiff
| Some cmd_opt ->
let command =
match cmd_opt with
| Some str -> (
match String.split_on_char ' ' str with
| cmd :: args -> cmd, args
| [] -> assert false)
| None ->
if Message.has_color stdout && has_command "patdiff" then
"patdiff", ["-alt-old"; "Reference"; "-alt-new"; "Result"]
else "diff", ["-u"; "-L"; "Reference"; "-L"; "Result"]
in
`Command
( command,
fun ppf s ->
s
|> String.trim_end
|> String.split_on_char '\n'
|> Format.pp_print_list Format.pp_print_string ppf )
end
let print_diff ppf p1 p2 =
let get_str (pstart, pend) =
assert (pstart.Lexing.pos_fname = pend.Lexing.pos_fname);
File.with_in_channel ~bin:false pstart.Lexing.pos_fname
@@ fun ic ->
let pos_char pos =
if Sys.win32 then pos.Lexing.pos_lnum - 1 + pos.Lexing.pos_cnum
else pos.Lexing.pos_cnum
in
try
seek_in ic (max 0 (pos_char pstart));
really_input_string ic (pend.Lexing.pos_cnum - pstart.Lexing.pos_cnum)
with Sys_error _ | End_of_file -> "<could not read file>"
in
match Lazy.force diff_command with
| `Stringdiff f -> f ppf (get_str p1) (get_str p2)
| `Command ((cmd, args), printer) ->
File.with_temp_file "clerk-diff" "a" ~contents:(get_str p1)
@@ fun f1 ->
File.with_temp_file "clerk_diff" "b" ~contents:(get_str p2)
@@ fun f2 ->
File.process_out ~check_exit:(fun _ -> ()) cmd (args @ [f1; f2])
|> printer ppf
let catala_commands_with_output_flag =
["makefile"; "html"; "latex"; "ocaml"; "python"; "java"; "r"; "c"]
let pfile =
let open File in
fun ~build_dir f ->
f
|> File.remove_prefix (Sys.getcwd ())
|> File.remove_prefix build_dir
|> File.make_relative_to ~dir:original_cwd
let quote_if_needed f =
if
String.exists
(function
| 'a' .. 'z'
| 'A' .. 'Z'
| '0' .. '9'
| '.' | '-' | '_' | '/' | ':' | '%' ->
false
| _ -> true)
f
then "\"" ^ f ^ "\""
else f
let clean_command_line ~build_dir file cl =
cl
|> List.filter_map (fun s ->
if s = "--directory=" ^ build_dir then None
else if
String.starts_with ~prefix:"-" s
|| not (String.contains s '/' || String.contains s '\\')
then Some s
else Some (quote_if_needed (pfile ~build_dir s)))
|> (function
| catala :: cmd :: args ->
catala
:: cmd
::
(let rel_bindir = File.make_relative_to ~dir:File.original_cwd build_dir in
if rel_bindir = "_build" then [] else ["--bin=" ^ rel_bindir])
@ "-I"
:: quote_if_needed (pfile ~build_dir (Filename.dirname file))
:: args
| cl -> cl)
|> function
| catala :: cmd :: args
when List.mem (String.lowercase_ascii cmd) catala_commands_with_output_flag
->
(catala :: cmd :: args) @ ["-o -"]
| cl -> cl
let pp_pos ~build_dir ppf (start, stop) =
assert (start.Lexing.pos_fname = stop.Lexing.pos_fname);
let pos_fname = pfile ~build_dir start.Lexing.pos_fname in
Format.fprintf ppf "@{<cyan>%a@}" Message.pp_pos
(Pos.from_lpos ({ start with pos_fname }, { stop with pos_fname }))
let print_command ~build_dir ppf file cmd =
Format.fprintf ppf "@,@[<h>$ @{<yellow>%a@}@]"
(Format.pp_print_list ~pp_sep:Format.pp_print_space Format.pp_print_string)
(clean_command_line ~build_dir file cmd)
let display ~build_dir file ppf t =
Format.pp_open_vbox ppf 2;
if t.i_success then (
Format.fprintf ppf "@{<green>■@} %a cli test passed" (pp_pos ~build_dir)
t.i_expected;
if Global.options.debug then
print_command ~build_dir ppf file t.i_command_line)
else (
Format.fprintf ppf "@{<red>■@} %a cli test failed" (pp_pos ~build_dir)
t.i_expected;
print_command ~build_dir ppf file t.i_command_line;
if disp_flags.diffs then (
Format.pp_print_cut ppf ();
print_diff ppf t.i_expected t.i_result));
Format.pp_close_box ppf ()
let display_scope ~build_dir file ppf scope_test =
Format.pp_open_vbox ppf 2;
if scope_test.s_success then (
Format.fprintf ppf
"@{<green>■@} scope @{<hi_magenta>%s@} passed (@{<hi_magenta>%d µs@})"
scope_test.s_name
(int_of_float (scope_test.s_time *. 1000000.));
if Global.options.debug then
print_command ~build_dir ppf file scope_test.s_command_line)
else (
Format.fprintf ppf "@{<red>■@} scope @{<hi_magenta>%s@} failed"
scope_test.s_name;
if disp_flags.diffs || Global.options.debug then
print_command ~build_dir ppf file scope_test.s_command_line;
List.iter
(fun (pos, msg) ->
Format.fprintf ppf "@,%a %s" (pp_pos ~build_dir) pos msg)
scope_test.s_errors);
Format.pp_close_box ppf ()
let display_file ~build_dir ppf (t : file) =
let pp_file ppf f =
let f = pfile ~build_dir f in
Message.pp_link ~target:(Message.file_url f) ppf "@{<cyan>%s@}" f
in
let print_tests tests =
let tests =
match disp_flags.tests with
| `All | `FailedFile -> tests
| `Failed -> List.filter (fun t -> not t.i_success) tests
| `None -> assert false
in
if tests <> [] then (
Format.pp_print_break ppf 0 3;
Format.pp_open_vbox ppf 0;
Format.pp_print_list (display ~build_dir t.name) ppf tests;
Format.pp_close_box ppf ())
in
let print_scopes scopes =
let scopes =
match disp_flags.tests with
| `All | `FailedFile -> scopes
| `Failed -> List.filter (fun s -> not s.s_success) scopes
| `None -> assert false
in
if scopes <> [] then (
Format.pp_print_break ppf 0 3;
Format.pp_open_vbox ppf 0;
Format.pp_print_list (display_scope ~build_dir t.name) ppf scopes;
Format.pp_close_box ppf ())
in
let print_code_coverage (full_code_coverage : Coverage.coverage_map) =
let {
Clerk_coverage.total_reachable_lines;
total_reached_lines;
total_unreached_lines;
} =
Clerk_coverage.compute_coverage_per_line full_code_coverage
in
let percentage =
int_of_float
(float_of_int total_reached_lines
/. float_of_int total_unreached_lines
*. 100.)
in
Format.pp_print_break ppf 0 3;
Format.pp_open_vbox ppf 0;
Format.fprintf ppf
"@{<green>■@} code coverage @{<cyan>%d@} / @{<cyan>%d@} lines \
(@{<hi_magenta>%d %%@})"
total_reached_lines total_reachable_lines percentage;
Format.pp_close_box ppf ()
in
if t.successful = t.total then (
if disp_flags.files = `All then (
Format.fprintf ppf
"@{<green;reverse>__@} %a: @{<green;bold>%d@} / %d tests passed" pp_file
t.name t.successful t.total;
if disp_flags.tests = `All then (
print_tests t.tests;
print_scopes t.scopes;
if disp_flags.coverage then Option.iter print_code_coverage t.coverage);
Format.pp_print_cut ppf ()))
else
let () =
match t.successful with
| 0 -> Format.fprintf ppf "@{<red;reverse>__@}"
| _ -> Format.fprintf ppf "@{<yellow;reverse>__@}"
in
Format.fprintf ppf " %a: " pp_file t.name;
(function
| 0 -> Format.fprintf ppf "@{<red;bold>0@}"
| n -> Format.fprintf ppf "@{<yellow;bold>%d@}" n)
t.successful;
Format.fprintf ppf " / %d tests passed" t.total;
if disp_flags.tests <> `None then (
print_tests t.tests;
print_scopes t.scopes;
if disp_flags.coverage then Option.iter print_code_coverage t.coverage);
Format.pp_print_cut ppf ()
type box = { print_line : 'a. ('a, Format.formatter, unit) format -> 'a }
[@@ocaml.unboxed]
let print_box tcolor ppf title (pcontents : box -> unit) =
let columns = Message.terminal_columns () in
let tpad = columns - String.width title - 6 in
Format.fprintf ppf "@,%t┏%t @{<bold;reverse> %s @} %t┓@}@," tcolor
(Message.pad (tpad / 2) "━")
title
(Message.pad (tpad - (tpad / 2)) "━");
Format.pp_open_tbox ppf ();
Format.fprintf ppf "%t@<1>%s@}%*s" tcolor "┃" (columns - 2) "";
Format.pp_set_tab ppf ();
Format.fprintf ppf "%t┃@}@," tcolor;
let box =
{
print_line =
(fun fmt ->
Format.kfprintf
(fun ppf ->
Format.pp_print_tab ppf ();
Format.fprintf ppf "%t┃@}@," tcolor)
ppf ("%t@<1>%s@} " ^^ fmt) tcolor "┃");
}
in
pcontents box;
box.print_line "";
Format.pp_close_tbox ppf ();
Format.fprintf ppf "%t┗%t┛@}@," tcolor (Message.pad (columns - 2) "━")
let summary ~build_dir ?(backend_tests = []) tests =
let ppf = Message.formatter_of_out_channel stdout () in
Format.pp_open_vbox ppf 0;
let tests = List.filter (fun f -> f.total > 0) tests in
let files, _success_files, success, total =
List.fold_left
(fun (files, success_files, success, total) file ->
( files + 1,
(if file.successful <> file.total then success_files
else success_files + 1),
success + file.successful,
total + file.total ))
(0, 0, 0, 0) tests
in
let backend_results =
List.fold_left
(fun acc ((_, bk), (success, total)) ->
if bk = `Interpret then acc
else
let bk =
match bk with
| `Interpret -> "Interpreted"
| `OCaml -> "OCaml"
| `C -> "C"
| `Python -> "Python"
| `Java -> "Java"
in
String.Map.update bk
(fun r ->
let success0, total0 = Option.value r ~default:(0, 0) in
Some (success + success0, total + total0))
acc)
String.Map.empty backend_tests
in
let full_coverage =
List.fold_left
(fun (acc : Coverage.coverage_map) (file : file) ->
match file.coverage with
| None -> acc
| Some file_coverage -> Coverage.union acc file_coverage)
Coverage.empty tests
in
let {
Clerk_coverage.total_reachable_lines;
total_reached_lines;
total_unreached_lines;
} =
Clerk_coverage.compute_coverage_per_line full_coverage
in
if disp_flags.files <> `None then
List.iter (fun f -> display_file ~build_dir ppf f) tests;
let all_successful =
success = total
&& String.Map.for_all
(fun _ (success, total) -> success = total)
backend_results
in
let result_box =
if files = 0 && String.Map.is_empty backend_results then (
let columns = Message.terminal_columns () in
let title = "NO TESTS WERE RUN" in
let tpad = columns - String.width title - 4 in
Format.fprintf ppf "@,@{<yellow>%t @{<bold;reverse> %s @} %t@}@,"
(Message.pad (tpad / 2) "━")
title
(Message.pad (tpad - (tpad / 2)) "━");
fun _ -> ())
else if all_successful then
print_box
(fun ppf -> Format.fprintf ppf "@{<green>")
ppf "ALL TESTS PASSED"
else print_box (fun ppf -> Format.fprintf ppf "@{<red>") ppf "TESTS FAILED"
in
result_box (fun box ->
box.print_line "@{<ul>%-13s %10s %10s %10s %10s@}" "" "FAILED" "PASSED"
"TOTAL" "RATIO";
let ratio ppf (m, n) =
let color =
let open Ocolor_types in
if m >= n then C4 green
else C24 { r24 = 255; g24 = m * 255 / n; b24 = 0 }
in
Format.pp_open_stag ppf (Ocolor_format.Ocolor_style_tag (Fg color));
if n > 0 then Format.fprintf ppf "%8d %%" (m * 100 / n)
else Format.fprintf ppf "%8s %%" "-";
Format.pp_close_stag ppf ()
in
if files > 0 then
box.print_line
"@{<hi_blue;bold>%-13s@} @{<red;bold>%a@} @{<green;bold>%a@} \
@{<bold>%10d@} @{<bold>%a@}"
"Interpreted"
(fun ppf -> function
| 0 -> Format.fprintf ppf "@{<green>%10d@}" 0
| n -> Format.fprintf ppf "%10d" n)
(total - success)
(fun ppf -> function
| 0 -> Format.fprintf ppf "@{<red>%10d@}" 0
| n -> Format.fprintf ppf "%10d" n)
success total ratio (success, total);
if disp_flags.coverage then
box.print_line
"@{<hi_magenta;bold>%-13s@} @{<red;bold>%a@} @{<green;bold>%a@} \
@{<bold>%10d@} @{<bold>%a@}"
"Lines covered"
(fun ppf -> function
| 0 -> Format.fprintf ppf "@{<green>%10d@}" 0
| n -> Format.fprintf ppf "%10d" n)
total_unreached_lines
(fun ppf -> function
| 0 -> Format.fprintf ppf "@{<red>%10d@}" 0
| n -> Format.fprintf ppf "%10d" n)
total_reached_lines total_reachable_lines ratio
(total_reached_lines, total_reachable_lines);
String.Map.iter
(fun bk (success, total) ->
box.print_line
"@{<blue;bold>%-13s@} @{<red;bold>%a@} @{<green;bold>%a@} \
@{<bold>%10d@} @{<bold>%a@}"
bk
(fun ppf -> function
| 0 -> Format.fprintf ppf "@{<green>%10d@}" 0
| n -> Format.fprintf ppf "%10d" n)
(total - success)
(fun ppf -> function
| 0 -> Format.fprintf ppf "@{<red>%10d@}" 0
| n -> Format.fprintf ppf "%10d" n)
success total ratio (success, total))
backend_results);
Format.pp_close_box ppf ();
Format.pp_print_flush ppf ();
all_successful
let print_json ~(build_dir : string) ?(backend_tests = []) (tests : file list) =
let cwd = Sys.getcwd () in
let ppf = Message.formatter_of_out_channel stdout () in
let success, total =
List.fold_left
(fun (success, total) file ->
success + file.successful, total + file.total)
(0, 0) tests
in
let scope_to_json scope =
`Assoc
[
"scope_name", `String scope.s_name;
"success", `Bool scope.s_success;
( "errors",
`List
(List.map
(fun ((pos : pos), e) ->
`Assoc
[
( "location",
Clerk_coverage.pos_to_json_location ~build_dir ~cwd
(Pos.from_lpos pos) );
"message", `String e;
])
scope.s_errors) );
"time", `Float (scope.s_time *. 1000.);
]
in
let inline_tests_to_json (inline_test : inline_test) =
`Assoc
[
"cmd", `String (String.concat " " inline_test.i_command_line);
"success", `Bool inline_test.i_success;
]
in
let bk_map =
List.fold_left
(fun acc ((item, bk), result) ->
if bk = `Interpret then acc
else
String.Map.update item.Clerk_utils.Scan.file_name
(fun v -> Some ((bk, result) :: Option.value v ~default:[]))
acc)
String.Map.empty backend_tests
in
let full_coverage =
List.fold_left
(fun acc (file : file) ->
match file.coverage with
| None -> acc
| Some file_code_coverage -> Coverage.union acc file_code_coverage)
Coverage.empty tests
in
let json =
`Assoc
[
( "test-results",
`List
(List.filter_map
(fun (test : file) ->
Some
(`Assoc
[
( "file",
`String File.(cwd / remove_prefix build_dir test.name)
);
( "tests",
`Assoc
(( "scopes",
`List (List.map scope_to_json test.scopes) )
:: ( "inline-tests",
`List
(List.map inline_tests_to_json test.tests) )
:: List.map
(fun (bk, (success, total)) ->
( Clerk_cli.backend_name bk,
`Assoc
[
"success", `Int success;
"total", `Int total;
] ))
(Option.value
(String.Map.find_opt
(File.remove_prefix build_dir test.name)
bk_map)
~default:[])) );
]))
tests) );
( "coverage",
Clerk_coverage.coverage_to_json ~build_dir ~cwd full_coverage );
]
in
Format.fprintf ppf "%s@." (Yojson.to_string json);
success = total
let print_xml ~build_dir ?(backend_tests = []) tests =
let ffile ppf f = Format.pp_print_string ppf (pfile ~build_dir f) in
let ppf = Message.formatter_of_out_channel stdout () in
let tests = List.filter (fun f -> f.total > 0) tests in
let bk_map =
List.fold_left
(fun acc ((item, bk), result) ->
if bk = `Interpret then acc
else
String.Map.update item.Clerk_utils.Scan.file_name
(fun v -> Some ((bk, result) :: Option.value v ~default:[]))
acc)
String.Map.empty backend_tests
in
let success, total =
List.fold_left
(fun (success, total) file ->
success + file.successful, total + file.total)
(0, 0) tests
in
let success, total =
List.fold_left
(fun (success, total) ((_item, _bk), (tsuccess, ttotal)) ->
success + tsuccess, total + ttotal)
(success, total) backend_tests
in
Format.fprintf ppf "@[<v><?xml version=\"1.0\" encoding=\"UTF-8\"?>@,";
Format.fprintf ppf "@[<v 2><testsuites tests=\"%d\" failures=\"%d\">@,"
success (total - success);
Format.pp_print_list
(fun ppf f ->
let backend_tests =
Option.value
(String.Map.find_opt (File.remove_prefix build_dir f.name) bk_map)
~default:[]
in
let successful =
List.fold_left
(fun acc (_bk, (success, _total)) -> acc + success)
f.successful backend_tests
in
let total =
List.fold_left
(fun acc (_bk, (_success, total)) -> acc + total)
f.total backend_tests
in
Format.fprintf ppf
"@[<v 2>@[<hov 1><testsuite@ name=\"%a\"@ tests=\"%d\"@ \
failures=\"%d\">@]"
ffile f.name successful (total - successful);
List.iter
(fun t ->
Format.fprintf ppf "@,@[<v 2><testcase line=\"%d\">"
(fst t.i_expected).Lexing.pos_lnum;
Format.fprintf ppf
"@,\
@[<hv 2><property name=\"description\">@,\
@[<hov 2>%a@]@;\
<0 -2></property>@]"
(Format.pp_print_list ~pp_sep:Format.pp_print_space
Format.pp_print_string)
(clean_command_line ~build_dir f.name t.i_command_line);
if not t.i_success then (
Format.fprintf ppf
"@,@[<v 2><failure message=\"Output differs from reference\">@,";
print_diff ppf t.i_expected t.i_result;
Format.fprintf ppf "@]@,</failure>");
Format.fprintf ppf "@]@,</testcase>")
f.tests;
List.iter
(fun t ->
(match t.s_errors with
| ((pos, _), _) :: _ ->
Format.fprintf ppf "@,@[<v 2><testcase name=\"%s\" line=\"%d\">"
t.s_name pos.pos_lnum
| _ -> Format.fprintf ppf "@,@[<v 2><testcase name=\"%s\">" t.s_name);
Format.fprintf ppf
"@,\
@[<hv 2><property name=\"description\">@,\
@[<hov 2>%a@]@;\
<0 -2></property>@]"
(Format.pp_print_list ~pp_sep:Format.pp_print_space
Format.pp_print_string)
(clean_command_line ~build_dir f.name t.s_command_line);
if not t.s_success then (
Format.fprintf ppf "@,@[<v 2><failure message=\"Scope failed\">@,";
Format.pp_print_list
(fun ppf (pos, msg) ->
pp_pos ~build_dir ppf pos;
Format.pp_print_space ppf ();
Format.pp_print_string ppf msg)
ppf t.s_errors;
Format.fprintf ppf "@]@,</failure>");
Format.fprintf ppf "@]@,</testcase>")
f.scopes;
List.iter
(fun (bk, (success, total)) ->
Format.fprintf ppf "@,@[<v 2><testcase name=\"%s\">"
(Clerk_cli.backend_name bk);
Format.fprintf ppf
"@,\
@[<hv 2><property name=\"description\">@,\
@[<hov 2>%d scopes compiled to %s@]@;\
<0 -2></property>@]"
total
(Clerk_cli.backend_name bk);
if success < total then
Format.fprintf ppf
"@,\
@[<v 2><failure message=\"%d out of %d scopes \
failed\"></failure>@]"
(total - success) total;
Format.fprintf ppf "@]@,</testcase>")
backend_tests;
Format.fprintf ppf "@]@,</testsuite>")
ppf tests;
Format.fprintf ppf "@]@,</testsuites>@]@.";
success = total