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