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