Don't execute semant test if parsing fails
[tiger.ml.git] / compiler / src / lib / tiger / tiger_test.ml
CommitLineData
e69e4e8b
SK
1(* "exe" is for status of execution (whether any exceptions were raised)
2 * "out" is for status of output comparison (whether what was outputted is
3 * what was expected)
4 *
d3bdde4b
SK
5 * code |> pass_a_exe |> pass_a_out |> ... |> pass_z_exe |> pass_z_out
6 *
7 * pass a:
8 * exe: OK
9 * out: n/a
10 * pass b:
11 * exe: OK
12 * out: OK
13 * pass c:
14 * exe: OK
15 * out: ERROR
16 * ...
17 *
18 * name | pass a | ... | pass z
19 * ---------+--------+-----+--------
20 * exe foo | OK | ... | OK
21 * out foo | OK | ... | ERROR
22 *
23 * *)
24
25open Printf
26
27module List = ListLabels
28module String = StringLabels
29
30module Option : sig
31 type 'a t = 'a option
32
33 val map : 'a t -> ('a -> 'b) -> 'b t
34end = struct
35 type 'a t = 'a option
36
37 let map t f =
38 match t with
39 | None -> None
40 | Some x -> Some (f x)
41end
42
38ffcb1f 43(* TODO: ~expect:Output of 'a | Exception of (exn -> bool) *)
d3bdde4b
SK
44type t =
45 { name : string
46 ; code : string
47 ; out_lexing : (Tiger_parser.token list) option
48 ; out_parsing : Tiger_absyn.t option
89037894 49 ; is_error_expected_parsing : (Tiger_error.t -> bool) option
38ffcb1f 50 ; is_error_expected_semant : (Tiger_error.t -> bool) option
d3bdde4b
SK
51 }
52
b53d50d3
SK
53type status =
54 | Pass
55 | Fail
56 | Skip
57
58type 'a t_result =
59 { exe_stat : status
60 ; exe_msg : string
61 ; out_stat : status
62 ; out_val : 'a option
63 ; out_msg : string
64 }
65
d3bdde4b
SK
66type color =
67 | Red
b53d50d3 68 | Red_bold
d3bdde4b
SK
69 | Yellow
70 | Green
b53d50d3
SK
71 | Green_bold
72 | Grey_bold
d3bdde4b
SK
73
74
75let color_to_ansi_code = function
b53d50d3
SK
76 | Grey_bold -> "\027[1;30m"
77 | Red -> "\027[0;31m"
78 | Red_bold -> "\027[1;31m"
79 | Yellow -> "\027[0;33m"
80 | Green -> "\027[0;32m"
81 | Green_bold -> "\027[1;32m"
82
83let color_off = "\027[0m"
d3bdde4b
SK
84
85let color color string =
86 let color_on = color_to_ansi_code color in
d3bdde4b
SK
87 sprintf "%s%s%s" color_on string color_off
88
b53d50d3
SK
89let colorize str = function
90 | Some c -> (color_to_ansi_code c) ^ str ^ color_off
91 | None -> str
92
93let status_to_color = function
94 | Pass -> Some Green_bold
95 | Fail -> Some Red_bold
96 | Skip -> Some Grey_bold
97
98let status_to_str = function
99 (* Expected to be a single character, but using string to allow unicode. *)
100 | Pass -> "✓"
101 | Fail -> "X"
102 | Skip -> "-"
103
39dd0869
SK
104let status indicator info =
105 match info with
106 | "" -> indicator
107 | _ -> sprintf "%s: %s" indicator info
108
fca3442c 109(* TODO: Perhaps a global option whether to print non-fail info? *)
9949f15b 110let status_pass ?(info="") () =
b53d50d3 111 status (color Green "P") info
39dd0869 112
9949f15b 113let status_fail ?(info="") () =
b53d50d3 114 status (color Red "F") info
39dd0869 115
9949f15b 116let status_skip ?(info="") () =
f5fc22dd 117 (*let indicator = (color Yellow "Skip") in*)
b53d50d3 118 let indicator = "S" in
f5fc22dd 119 status indicator info
39dd0869 120
e69e4e8b 121let case
38ffcb1f
SK
122 ?(out_lexing=None)
123 ?(out_parsing=None)
89037894 124 ?(is_error_expected_parsing=None)
38ffcb1f 125 ?(is_error_expected_semant=None)
e69e4e8b
SK
126 ~code
127 name
128 =
d3bdde4b
SK
129 { name
130 ; code
131 ; out_lexing
132 ; out_parsing
89037894 133 ; is_error_expected_parsing
5da420a8 134 ; is_error_expected_semant
d3bdde4b
SK
135 }
136
b53d50d3
SK
137let bar_horiz_minor = color Grey_bold (String.make 80 '-')
138let bar_horiz_major = color Grey_bold (String.make 80 '=')
139let bar_vert = color Grey_bold "|"
d3bdde4b
SK
140
141let indent =
142 let unit_spaces = 2 in
143 fun n ->
144 String.make (n * unit_spaces) ' '
145
38ffcb1f
SK
146let lexbuf_set_filename lb filename
147: unit
148=
149 let Lexing.({lex_start_p; lex_curr_p; _}) = lb in
150 lb.Lexing.lex_start_p <- {lex_start_p with Lexing.pos_fname = filename};
151 lb.Lexing.lex_curr_p <- {lex_curr_p with Lexing.pos_fname = filename}
152
153let lexbuf_create ~filename ~code =
154 let lb = Lexing.from_string code in
155 lexbuf_set_filename lb filename;
156 lb
157
158let pass_lexing ~fake_filename ~code
159: (Tiger_parser.token list, string) result
160=
161 let lexbuf = lexbuf_create ~filename:fake_filename ~code in
d3bdde4b
SK
162 let rec tokens () =
163 let token = Tiger_lexer.token lexbuf in
164 (* Avoiding fragile pattern-matching *)
165 if token = Tiger_parser.EOF then [] else token :: tokens ()
166 in
167 match tokens () with
168 | exception e -> Error (Printexc.to_string e)
169 | tokens -> Ok tokens
170
38ffcb1f
SK
171let pass_parsing ~fake_filename ~code
172: (Tiger_absyn.t, string) result
173=
174 let lb = lexbuf_create ~filename:fake_filename ~code in
d3bdde4b
SK
175 match Tiger_parser.program Tiger_lexer.token lb with
176 | exception Parsing.Parse_error ->
177 let module L = Lexing in
178 let L.({lex_curr_p = {pos_lnum=l; pos_bol=b; pos_cnum=c; _}; _}) = lb in
179 let msg = sprintf "Syntax error around line: %d, column: %d" l (c - b) in
180 Error msg
181 | ast ->
182 Ok ast
183
217e9638 184let pass_semant (absyn : Tiger_absyn.t)
38ffcb1f
SK
185: (unit, string) result
186=
217e9638 187 Ok (Tiger_semant.transProg absyn)
ea3f5e0c 188
b53d50d3
SK
189let str_exact str exact =
190 let len = String.length str in
191 let take = if len > exact then exact else len in
192 let str = String.sub str 0 take in
193 let pad = exact - take in
194 let pad = String.make pad ' ' in
195 str ^ pad
196
d3bdde4b
SK
197let s = sprintf
198let p = printf
199let p_ln = print_newline
200let p_indent n = p "%s" (indent n)
201
202let run tests =
b53d50d3
SK
203 Printexc.record_backtrace true;
204 let count_fail_all = ref 0 in
38ffcb1f
SK
205 let run_pass ~f ~expect_output ~is_error_expected =
206 let is_error_expected =
207 match is_error_expected with
208 | None -> (fun _ -> false)
209 | Some f -> f
210 in
38ffcb1f 211 match f () with
d3bdde4b 212 | exception e ->
b53d50d3
SK
213 let backtrace = Printexc.get_backtrace () in
214 let (exe_stat, exe_msg) =
e69e4e8b
SK
215 (match e with
216 | Tiger_error.T e when is_error_expected e ->
b53d50d3 217 (Pass, (Tiger_error.to_string e))
e69e4e8b 218 | Tiger_error.T e ->
b53d50d3
SK
219 incr count_fail_all;
220 (Fail, (Tiger_error.to_string e))
e69e4e8b 221 | e ->
b53d50d3
SK
222 incr count_fail_all;
223 (Fail, (Printexc.to_string e))
e69e4e8b
SK
224 )
225 in
b53d50d3
SK
226 { exe_stat
227 ; exe_msg = s "\n\tException: %s.\n\tBacktrace: %s" exe_msg backtrace
228 ; out_stat = Skip
229 ; out_val = None
230 ; out_msg = "" (* old "info" goes here *)
231 }
39dd0869 232 | Error info ->
b53d50d3
SK
233 incr count_fail_all;
234 { exe_stat = Fail
235 ; exe_msg = info
236 ; out_stat = Skip
237 ; out_val = None
238 ; out_msg = "" (* old "info" goes here *)
239 }
d3bdde4b 240 | Ok produced ->
b53d50d3 241 let (out_stat, out_msg) =
d3bdde4b 242 match
e69e4e8b 243 Option.map expect_output (fun expected -> expected = produced)
d3bdde4b
SK
244 with
245 | None ->
b53d50d3 246 (Skip, "expected output not provided")
d3bdde4b 247 | Some true ->
b53d50d3 248 (Pass, "")
d3bdde4b 249 | Some false ->
b53d50d3
SK
250 incr count_fail_all;
251 (* TODO pretty print expected and produced *)
252 (Fail, "unexpected output")
d3bdde4b 253 in
b53d50d3
SK
254 { exe_stat = Pass
255 ; exe_msg = "" (* old "info" goes here *)
256 ; out_stat
257 ; out_val = Some produced
258 ; out_msg
259 }
d3bdde4b 260 in
0f031bf2 261 let test_case_count = ref 0 in
b53d50d3
SK
262 let col_1_width = 25 in
263 let col_i_width = 10 in
264 let p_stat width (exe, out) =
265 (* All this gymnastics to ignore color codes in cell width *)
266 let min = 5 in
267 let width = if width > min then width else min in
268 p "%s" (String.concat "" (List.init ~len:width ~f:(function
269 | 0 -> " "
270 | 1 -> bar_vert
271 | 2 -> " "
272 | 3 -> colorize (status_to_str exe) (status_to_color exe)
273 | 4 -> colorize (status_to_str out) (status_to_color out)
274 | _ -> " "
275 )))
276
277 in
278 p "%s" bar_horiz_major; p_ln ();
279 p "%s" (str_exact "Test case" col_1_width);
280 List.iter ~f:(fun header -> p " %s %s" bar_vert header)
281 [ "Lexing"
282 ; "Parsing"
283 ; "Semant"
284 ];
285 p_ln ();
286 p "%s" bar_horiz_major; p_ln ();
d3bdde4b 287 List.iter tests ~f:(
5da420a8
SK
288 fun
289 { name
290 ; code
291 ; out_lexing
292 ; out_parsing
89037894 293 ; is_error_expected_parsing
5da420a8
SK
294 ; is_error_expected_semant
295 }
296 ->
0f031bf2 297 incr test_case_count;
b53d50d3 298 let res_lex =
e69e4e8b 299 run_pass
38ffcb1f 300 ~f:(fun () -> pass_lexing ~fake_filename:name ~code)
e69e4e8b 301 ~expect_output:out_lexing
38ffcb1f 302 ~is_error_expected:None
e69e4e8b 303 in
b53d50d3 304 let res_pars =
e69e4e8b 305 run_pass
38ffcb1f 306 ~f:(fun () -> pass_parsing ~fake_filename:name ~code)
e69e4e8b 307 ~expect_output:out_parsing
89037894 308 ~is_error_expected:is_error_expected_parsing
e69e4e8b 309 in
b53d50d3 310 let res_sem =
217e9638
SK
311 (* TODO: Replace this hack with general test-dependency checking *)
312 match res_pars.out_val with
313 | None ->
314 { exe_stat = Skip
315 ; exe_msg = "No AST provided"
316 ; out_stat = Skip
317 ; out_val = None
318 ; out_msg = ""
319 }
320 | Some absyn ->
321 run_pass
322 ~f:(fun () -> pass_semant absyn)
323 ~expect_output:(Some ())
324 ~is_error_expected:is_error_expected_semant
ea3f5e0c 325 in
b53d50d3
SK
326 let results =
327 (* Replacing out_val for type compatibility *)
328 [ "Lexing" , {res_lex with out_val = None}
329 ; "Parsing" , {res_pars with out_val = None}
330 ; "Semant" , {res_sem with out_val = None}
331 ]
332 in
333 if !test_case_count > 1 then (p "%s" bar_horiz_minor; p_ln ());
334 p "%s" (str_exact name col_1_width);
335 List.iter results ~f:(fun (stage, {exe_stat=e; out_stat=o; _}) ->
336 p_stat ((String.length stage) + 3) (e, o)
337 );
338 p_ln ();
339 let printed_error = ref false in
340 List.iter results ~f:(
341 fun (stage, {exe_stat; exe_msg; out_stat; out_msg; _}) ->
342 (match exe_stat with
343 | Pass -> ()
344 | Skip -> ()
345 | Fail ->
346 printed_error := true;
347 p "%s: %s" (color Grey_bold stage) (color Red exe_msg);
348 p_ln ()
349 );
350 (match out_stat with
351 | Pass -> ()
352 | Skip -> ()
353 | Fail ->
354 printed_error := true;
355 p "%s: %s" (color Grey_bold stage) (color Red out_msg)
356 );
357 );
d3bdde4b 358 );
b53d50d3 359 p "%s" bar_horiz_major; p_ln ();
b762cacb 360 p "%s"
0f031bf2 361 ( let info =
b53d50d3 362 s "%d failures in %d test cases" !count_fail_all !test_case_count
0f031bf2 363 in
b53d50d3 364 match !count_fail_all with
0f031bf2
SK
365 | 0 -> status_pass () ~info
366 | _ -> status_fail () ~info
b762cacb
SK
367 );
368 p_ln ();
b53d50d3
SK
369 p "%s" bar_horiz_major; p_ln ();
370 exit !count_fail_all
This page took 0.06133 seconds and 4 git commands to generate.