Release coccinelle-0.2.4rc2
[bpt/coccinelle.git] / testing.ml
CommitLineData
34e49164 1open Common
90aeb998 2open Sexplib
34e49164
C
3
4(*****************************************************************************)
5(* Test framework *)
6(*****************************************************************************)
7
8(* There can be multiple .c for the same cocci file. The convention
9 * is to have one base.cocci and a base.c and some optional
10 * base_vernn.[c,res].
ae4735db 11 *
34e49164
C
12 * If want to test without iso, use -iso_file empty.iso option.
13 *)
c3e37e97 14let testone prefix x compare_with_expected_flag =
34e49164
C
15 let x = if x =~ "\\(.*\\)_ver0$" then matched1 x else x in
16 let base = if x =~ "\\(.*\\)_ver[0-9]+$" then matched1 x else x in
17
c3e37e97
C
18 let cfile = prefix ^ x ^ ".c" in
19 let cocci_file = prefix ^ base ^ ".cocci" in
34e49164 20
c3e37e97 21 let expected_res = prefix ^ x ^ ".res" in
34e49164 22 begin
b1b2de81
C
23 let cocci_infos = Cocci.pre_engine (cocci_file, !Config.std_iso) in
24 let res = Cocci.full_engine cocci_infos [cfile] in
25 Cocci.post_engine cocci_infos;
ae4735db 26 let generated =
34e49164 27 match Common.optionise (fun () -> List.assoc cfile res) with
ae4735db
C
28 | Some (Some outfile) ->
29 if List.length res > 1
34e49164
C
30 then pr2 ("note that not just " ^ cfile ^ " was involved");
31
32 let tmpfile = "/tmp/"^Common.basename cfile in
33 pr2 (sprintf "One file modified. Result is here: %s" tmpfile);
34 Common.command2 ("mv "^outfile^" "^tmpfile);
35 tmpfile
ae4735db 36 | Some None ->
34e49164
C
37 pr2 "no modification on the input file";
38 cfile
39 | None -> raise Impossible
40 in
41 if compare_with_expected_flag
ae4735db
C
42 then
43 Compare_c.compare_default generated expected_res
44 +> Compare_c.compare_result_to_string
34e49164
C
45 +> pr2;
46 end
ae4735db 47
34e49164
C
48
49(* ------------------------------------------------------------------------ *)
ae4735db
C
50(* note: if you get some weird results in -testall, and not in -test,
51 * it is possible that a test file work in -test but may not
708f4980 52 * work while used inside a -testall. If we have some bugs in our
ae4735db 53 * parser that modify some global state and that those states
708f4980
C
54 * are not reseted between each test file, then having run previous
55 * test files may have an influence on another test file which mean
56 * than a test may work in isolation (via -test) but not otherwise
57 * (via -testall). Fortunately such bugs are rare.
ae4735db 58 *
708f4980
C
59 *)
60let testall ?(expected_score_file="tests/SCORE_expected.sexp") () =
ae4735db 61
b1b2de81 62 let score = empty_score () in
34e49164 63
ae4735db
C
64 let expected_result_files =
65 Common.glob "tests/*.res"
34e49164
C
66 +> List.filter (fun f -> Common.filesize f > 0)
67 +> List.map Filename.basename
68 +> List.sort compare
69 in
70
71 begin
ae4735db
C
72 expected_result_files +> List.iter (fun res ->
73 let x =
b1b2de81 74 if res =~ "\\(.*\\).res" then matched1 res else raise Impossible in
ae4735db 75 let base = if x =~ "\\(.*\\)_ver[0-9]+" then matched1 x else x in
34e49164
C
76 let cfile = "tests/" ^ x ^ ".c" in
77 let cocci_file = "tests/" ^ base ^ ".cocci" in
78 let expected = "tests/" ^ res in
79
80 let timeout_testall = 30 in
81
82 try (
ae4735db 83 Common.timeout_function timeout_testall (fun () ->
b1b2de81 84
708f4980
C
85 pr2 res;
86
b1b2de81
C
87 let cocci_infos = Cocci.pre_engine (cocci_file, !Config.std_iso) in
88 let xs = Cocci.full_engine cocci_infos [cfile] in
89 Cocci.post_engine cocci_infos;
90
ae4735db 91 let generated =
34e49164
C
92 match List.assoc cfile xs with
93 | Some generated -> generated
94 | None -> cfile
95 in
96
97 let (correct, diffxs) = Compare_c.compare_default generated expected
98 in
99
34e49164
C
100 (* I don't use Compare_c.compare_result_to_string because
101 * I want to indent a little more the messages.
102 *)
103 (match correct with
b1b2de81 104 | Compare_c.Correct -> Hashtbl.add score res Common.Ok;
ae4735db
C
105 | Compare_c.Pb s ->
106 let s = Str.global_replace
34e49164 107 (Str.regexp "\"/tmp/cocci-output.*\"") "<COCCIOUTPUTFILE>" s
978fd7e5
C
108 in
109 (* on macos the temporary files are stored elsewhere *)
ae4735db 110 let s = Str.global_replace
978fd7e5 111 (Str.regexp "\"/var/folders/.*/cocci-output.*\"") "<COCCIOUTPUTFILE>" s
34e49164 112 in
ae4735db
C
113 let s =
114 "INCORRECT:" ^ s ^ "\n" ^
34e49164
C
115 " diff (result(<) vs expected_result(>)) = \n" ^
116 (diffxs +> List.map(fun s -> " "^s^"\n") +> Common.join "")
117 in
b1b2de81 118 Hashtbl.add score res (Common.Pb s)
ae4735db
C
119 | Compare_c.PbOnlyInNotParsedCorrectly s ->
120 let s =
34e49164
C
121 "seems incorrect, but only because of code that " ^
122 "was not parsable" ^ s
123 in
b1b2de81 124 Hashtbl.add score res (Common.Pb s)
34e49164
C
125 )
126 )
127 )
ae4735db 128 with exn ->
34e49164
C
129 Common.reset_pr_indent();
130 let s = "PROBLEM\n" ^ (" exn = " ^ Printexc.to_string exn ^ "\n") in
b1b2de81 131 Hashtbl.add score res (Common.Pb s)
34e49164
C
132 );
133
134
135 pr2 "--------------------------------";
136 pr2 "statistics";
137 pr2 "--------------------------------";
138
ae4735db 139 Common.hash_to_list score +> List.iter (fun (s, v) ->
34e49164
C
140 pr_no_nl (Printf.sprintf "%-30s: " s);
141 pr_no_nl (
142 match v with
ae4735db 143 | Common.Ok -> "CORRECT\n"
34e49164
C
144 | Common.Pb s -> s
145 )
146 );
147 flush stdout; flush stderr;
148
149 pr2 "--------------------------------";
150 pr2 "regression testing information";
151 pr2 "--------------------------------";
ae4735db 152
708f4980 153 (* now default argument of testall:
b1b2de81 154 let expected_score_file = "tests/SCORE_expected.sexp" in
708f4980
C
155 *)
156 let expected_score_file_orig = "tests/SCORE_expected_orig.sexp" in
b1b2de81
C
157 let best_of_both_file = "tests/SCORE_best_of_both.sexp" in
158 let actual_score_file = "tests/SCORE_actual.sexp" in
708f4980 159
b1b2de81 160 pr2 ("regression file: "^ expected_score_file);
ae4735db 161 let (expected_score : score) =
b1b2de81 162 if Sys.file_exists expected_score_file
ae4735db 163 then
b1b2de81
C
164 let sexp = Sexp.load_sexp expected_score_file in
165 Sexp_common.score_of_sexp sexp
ae4735db
C
166 else
167 if Sys.file_exists expected_score_file_orig
708f4980
C
168 then begin
169 pr2 (spf "use expected orig file (%s)" expected_score_file_orig);
ae4735db 170 Common.command2 (spf "cp %s %s" expected_score_file_orig
708f4980
C
171 expected_score_file);
172 let sexp = Sexp.load_sexp expected_score_file in
173 Sexp_common.score_of_sexp sexp
174 end
ae4735db 175 else
708f4980 176 empty_score()
b1b2de81 177 in
34e49164 178
b1b2de81
C
179 let new_bestscore = Common.regression_testing_vs score expected_score in
180
181
182 let xs = Common.hash_to_list score in
183 let sexp = Sexp_common.sexp_of_score_list xs in
184 let s_score = Sexp.to_string_hum sexp in
185 Common.write_file ~file:(actual_score_file) s_score;
186
187 let xs2 = Common.hash_to_list new_bestscore in
188 let sexp2 = Sexp_common.sexp_of_score_list xs2 in
189 let s_score2 = Sexp.to_string_hum sexp2 in
190 Common.write_file ~file:(best_of_both_file) s_score2;
191
192 Common.print_total_score score;
193
194 let (good, total) = Common.total_scores score in
195 let (expected_good, expected_total) = Common.total_scores expected_score in
196
ae4735db
C
197 if good = expected_good
198 then begin
b1b2de81
C
199 pr2 "Current score is equal to expected score; everything is fine";
200 raise (UnixExit 0);
201 end
ae4735db 202 else
b1b2de81 203 if good < expected_good
ae4735db 204 then begin
708f4980
C
205 pr2 "Current score is lower than expected :(";
206 pr2 (spf "(was expecting %d but got %d)" expected_good good);
b1b2de81
C
207 pr2 "";
208 pr2 "If you think it's normal, then maybe you need to update the";
209 pr2 (spf "score file %s, copying info from %s."
210 expected_score_file actual_score_file);
211 raise (UnixExit 1);
212 end
213 else begin
708f4980
C
214 pr2 "Current score is greater than expected :)";
215 pr2 (spf "(was expecting %d but got %d)" expected_good good);
b1b2de81
C
216 pr2 "Generating new expected score file and saving old one";
217 Common.command2_y_or_no_exit_if_no
218 (spf "mv %s %s" expected_score_file (expected_score_file ^ ".save"));
219 Common.command2_y_or_no_exit_if_no
220 (spf "mv %s %s" best_of_both_file expected_score_file);
221 raise (UnixExit 0);
222 end
ae4735db 223
34e49164
C
224 end
225
226(* ------------------------------------------------------------------------ *)
227
228type okfailed = Ok | SpatchOK | Failed
229
230(* test_to_string *)
231let t_to_s = function
232 | Ok -> ".ok"
233 | SpatchOK -> ".spatch_ok"
234 | Failed -> ".failed"
235
ae4735db
C
236let delete_previous_result_files infile =
237 [Ok;SpatchOK;Failed] +> List.iter (fun kind ->
34e49164
C
238 Common.command2 ("rm -f " ^ infile ^ t_to_s kind)
239 )
240
241(* quite similar to compare_with_expected below *)
ae4735db 242let test_okfailed cocci_file cfiles =
34e49164
C
243 cfiles +> List.iter delete_previous_result_files;
244
245 (* final_files contain the name of an output file (a .ok or .failed
246 * or .spatch_ok), and also some additionnal strings to be printed in
247 * this output file in addition to the general error message of
248 * full_engine. *)
ae4735db 249 let final_files = ref [] in
34e49164
C
250
251
ae4735db
C
252 let newout =
253 Common.new_temp_file "cocci" ".stdout"
34e49164
C
254 in
255
256 let t = Unix.gettimeofday () in
ae4735db 257 let time_per_file_str () =
34e49164
C
258 let t' = Unix.gettimeofday () in
259 let tdiff = t' -. t in
260 let tperfile = tdiff /. (float_of_int (List.length cfiles)) in
261 spf "time: %f" tperfile
262 in
ae4735db
C
263
264 Common.redirect_stdout_stderr newout (fun () ->
34e49164
C
265 try (
266 Common.timeout_function_opt !Flag_cocci.timeout (fun () ->
267
b1b2de81
C
268 let cocci_infos = Cocci.pre_engine (cocci_file, !Config.std_iso) in
269 let outfiles = Cocci.full_engine cocci_infos cfiles in
270 Cocci.post_engine cocci_infos;
34e49164
C
271
272 let time_str = time_per_file_str () in
ae4735db
C
273
274 outfiles +> List.iter (fun (infile, outopt) ->
34e49164 275 let (dir, base, ext) = Common.dbe_of_filename infile in
ae4735db 276 let expected_suffix =
34e49164
C
277 match ext with
278 | "c" -> "res"
279 | "h" -> "h.res"
0708f913 280 | s -> pr2 ("WEIRD: not a .c or .h :" ^ base ^ "." ^ s);
34e49164
C
281 "" (* no extension, will compare to same file *)
282 in
ae4735db 283 let expected_res =
34e49164 284 Common.filename_of_dbe (dir, base, expected_suffix) in
ae4735db
C
285 let expected_res2 =
286 Common.filename_of_dbe (dir,"corrected_"^ base,expected_suffix)
34e49164 287 in
ae4735db 288
34e49164
C
289 (* can delete more than the first delete_previous_result_files
290 * because here we can have more files than in cfiles, for instance
291 * the header files
292 *)
293 delete_previous_result_files infile;
ae4735db 294
34e49164 295 match outopt, Common.lfile_exists expected_res with
ae4735db 296 | None, false ->
34e49164 297 ()
ae4735db 298 | Some outfile, false ->
34e49164
C
299 let s =("PB: input file " ^ infile ^ " modified but no .res") in
300 push2 (infile^t_to_s Failed, [s;time_str]) final_files
ae4735db
C
301
302 | x, true ->
303 let outfile =
304 match x with
305 | Some outfile -> outfile
306 | None -> infile
34e49164 307 in
ae4735db 308
34e49164
C
309 let diff = Compare_c.compare_default outfile expected_res in
310 let s1 = (Compare_c.compare_result_to_string diff) in
b1b2de81 311 if fst diff =*= Compare_c.Correct
34e49164 312 then push2 (infile ^ (t_to_s Ok), [s1;time_str]) final_files
ae4735db 313 else
34e49164
C
314 if Common.lfile_exists expected_res2
315 then begin
316 let diff = Compare_c.compare_default outfile expected_res2 in
317 let s2 = Compare_c.compare_result_to_string diff in
b1b2de81 318 if fst diff =*= Compare_c.Correct
ae4735db 319 then push2 (infile ^ (t_to_s SpatchOK),[s2;s1;time_str])
34e49164 320 final_files
ae4735db 321 else push2 (infile ^ (t_to_s Failed), [s2;s1;time_str])
34e49164
C
322 final_files
323 end
324 else push2 (infile ^ (t_to_s Failed), [s1;time_str]) final_files
325 )
326 );
327 )
ae4735db 328 with exn ->
34e49164
C
329 let clean s =
330 Str.global_replace (Str.regexp "\\\\n") "\n"
331 (Str.global_replace (Str.regexp ("\\\\\"")) "\""
332 (Str.global_replace (Str.regexp "\\\\t") "\t" s)) in
333 let s = "PROBLEM\n"^(" exn = " ^ clean(Printexc.to_string exn) ^ "\n")
334 in
ae4735db 335 let time_str = time_per_file_str ()
34e49164
C
336 in
337 (* we may miss some file because cfiles is shorter than outfiles.
338 * For instance the detected local headers are not in cfiles, so
339 * may have less failed. But at least have some failed.
340 *)
ae4735db 341 cfiles +> List.iter (fun infile ->
34e49164
C
342 push2 (infile ^ (t_to_s Failed), [s;time_str]) final_files;
343 );
344 );
ae4735db 345 !final_files +> List.iter (fun (file, additional_strs) ->
34e49164 346 Common.command2 ("cp " ^ newout ^ " " ^ file);
ae4735db 347 with_open_outfile file (fun (pr, chan) ->
34e49164
C
348 additional_strs +> List.iter (fun s -> pr (s ^ "\n"))
349 );
ae4735db 350
34e49164
C
351 )
352
353
ae4735db 354let test_regression_okfailed () =
34e49164
C
355
356 (* it's xxx.c.ok *)
357 let chop_ext f = f +> Filename.chop_extension in
358
359 let newscore = Common.empty_score () in
ae4735db
C
360 let oks =
361 Common.cmd_to_list ("find -name \"*.ok\"")
34e49164
C
362 ++
363 Common.cmd_to_list ("find -name \"*.spatch_ok\"")
364 in
365 let failed = Common.cmd_to_list ("find -name \"*.failed\"") in
366
ae4735db 367 if null (oks ++ failed)
34e49164
C
368 then failwith "no ok/failed file, you certainly did a make clean"
369 else begin
ae4735db 370 oks +> List.iter (fun s ->
34e49164
C
371 Hashtbl.add newscore (chop_ext s) Common.Ok
372 );
ae4735db 373 failed +> List.iter (fun s ->
34e49164
C
374 Hashtbl.add newscore (chop_ext s) (Common.Pb "fail")
375 );
376 pr2 "--------------------------------";
377 pr2 "regression testing information";
378 pr2 "--------------------------------";
379 Common.regression_testing newscore ("score_failed.marshalled")
380 end
ae4735db 381
34e49164
C
382
383(* ------------------------------------------------------------------------ *)
384(* quite similar to test_ok_failed. Maybe could factorize code *)
385let compare_with_expected outfiles =
386 pr2 "";
ae4735db 387 outfiles +> List.iter (fun (infile, outopt) ->
34e49164 388 let (dir, base, ext) = Common.dbe_of_filename infile in
ae4735db 389 let expected_suffix =
34e49164
C
390 match ext with
391 | "c" -> "res"
392 | "h" -> "h.res"
0708f913 393 | s -> failwith ("weird C file, not a .c or .h :" ^ s)
34e49164 394 in
ae4735db 395 let expected_res =
34e49164 396 Common.filename_of_dbe (dir, base, expected_suffix) in
ae4735db
C
397 let expected_res2 =
398 Common.filename_of_dbe (dir,"corrected_"^ base,expected_suffix)
34e49164 399 in
ae4735db 400
34e49164
C
401 match outopt, Common.lfile_exists expected_res with
402 | None, false -> ()
ae4735db 403 | Some outfile, false ->
34e49164
C
404 let s =("PB: input file " ^ infile ^ " modified but no .res") in
405 pr2 s
ae4735db
C
406 | x, true ->
407 let outfile =
408 match x with
409 | Some outfile -> outfile
410 | None -> infile
34e49164
C
411 in
412 let diff = Compare_c.compare_default outfile expected_res in
413 let s1 = (Compare_c.compare_result_to_string diff) in
b1b2de81 414 if fst diff =*= Compare_c.Correct
34e49164 415 then pr2_no_nl (infile ^ " " ^ s1)
ae4735db 416 else
34e49164
C
417 if Common.lfile_exists expected_res2
418 then begin
419 let diff = Compare_c.compare_default outfile expected_res2 in
420 let s2 = Compare_c.compare_result_to_string diff in
b1b2de81 421 if fst diff =*= Compare_c.Correct
34e49164
C
422 then pr2 (infile ^ " is spatchOK " ^ s2)
423 else pr2 (infile ^ " is failed " ^ s2)
424 end
425 else pr2 (infile ^ " is failed " ^ s1)
426 )
427
428(*****************************************************************************)
429(* Subsystem testing *)
430(*****************************************************************************)
431
ae4735db
C
432let test_parse_cocci file =
433 if not (file =~ ".*\\.cocci")
34e49164
C
434 then pr2 "warning: seems not a .cocci file";
435
7f004419 436 let (_,xs,_,_,_,_,grep_tokens,query) =
34e49164
C
437 Parse_cocci.process file (Some !Config.std_iso) false in
438 xs +> List.iter Pretty_print_cocci.unparse;
174d1640
C
439 Format.print_newline();
440 (* compile ocaml script code *)
441 (match Prepare_ocamlcocci.prepare file xs with
442 None -> ()
443 | Some ocaml_script_file ->
444 (* compile file *)
445 Prepare_ocamlcocci.load_file ocaml_script_file;
446 (* remove file *)
447 Prepare_ocamlcocci.clean_file ocaml_script_file;
448 (* Print the list of registered functions *)
449 Prepare_ocamlcocci.test ());
34e49164 450 Printf.printf "grep tokens\n";
90aeb998
C
451 (match grep_tokens with
452 None -> pr "No query"
453 | Some x -> pr (String.concat " || " x));
951c7801 454 match !Flag.scanner with
90aeb998 455 Flag.NoScanner | Flag.Grep -> ()
951c7801 456 | Flag.Glimpse | Flag.Google _ ->
413ffc02
C
457 Printf.printf "%s tokens\n"
458 (if !Flag.scanner = Flag.Glimpse then "glimpse" else "google");
951c7801
C
459 (match query with
460 None -> pr "No query"
413ffc02 461 | Some x -> pr (String.concat " ||\n" x))
34e49164
C
462
463
464
465(*****************************************************************************)
466(* to be called by ocaml toplevel, to test. *)
467(*****************************************************************************)
468
469(* no point to memoize this one *)
470let sp_of_file file iso = Parse_cocci.process file iso false
471
472(* TODO: Remove
34e49164
C
473*)
474
475(*
ae4735db 476let flows_of_ast astc =
34e49164
C
477 astc +> Common.map_filter (fun e -> ast_to_flow_with_error_messages e)
478
ae4735db 479let one_flow flows =
34e49164
C
480 List.hd flows
481
482let one_ctl ctls = List.hd (List.hd ctls)
483*)
484