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