2 * Copyright 2012, INRIA
3 * Julia Lawall, Gilles Muller
4 * Copyright 2010-2011, INRIA, University of Copenhagen
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.
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.
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.
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/>.
22 * The authors reserve the right to distribute this or future versions of
23 * Coccinelle under other licenses.
30 (*****************************************************************************)
32 (*****************************************************************************)
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
38 * If want to test without iso, use -iso_file empty.iso option.
40 let testone prefix x compare_with_expected_flag
=
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
44 let cfile = prefix ^
x ^
".c" in
45 let cocci_file = prefix ^
base ^
".cocci" in
47 let expected_res = prefix ^
x ^
".res" in
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;
53 match Common.optionise
(fun () -> List.assoc
cfile res) with
54 | Some
(Some outfile
) ->
55 if List.length
res > 1
56 then pr2
("note that not just " ^
cfile ^
" was involved");
57 let tmpfile = sprintf
"%s/%s" Filename.temp_dir_name
(Common.basename
cfile) in
58 pr2
(sprintf
"One file modified. Result is here: %s" tmpfile);
59 Common.command2
("mv "^outfile^
" "^
tmpfile);
62 pr2
"no modification on the input file";
64 | None
-> raise
(Impossible
163)
66 if compare_with_expected_flag
68 Compare_c.compare_default
generated expected_res
69 +> Compare_c.compare_result_to_string
74 (* ------------------------------------------------------------------------ *)
75 (* note: if you get some weird results in -testall, and not in -test,
76 * it is possible that a test file work in -test but may not
77 * work while used inside a -testall. If we have some bugs in our
78 * parser that modify some global state and that those states
79 * are not reseted between each test file, then having run previous
80 * test files may have an influence on another test file which mean
81 * than a test may work in isolation (via -test) but not otherwise
82 * (via -testall). Fortunately such bugs are rare.
85 let testall expected_score_file update_score_file
=
87 let score = empty_score
() in
89 let expected_result_files =
90 Common.glob
"tests/*.res"
91 +> List.filter
(fun f
-> Common.filesize f
> 0)
92 +> List.map
Filename.basename
97 expected_result_files +> List.iter
(fun res ->
99 if res =~
"\\(.*\\).res" then matched1
res else raise
(Impossible
164) in
100 let base = if x =~
"\\(.*\\)_ver[0-9]+" then matched1
x else x in
101 let cfile = "tests/" ^
x ^
".c" in
102 let cocci_file = "tests/" ^
base ^
".cocci" in
103 let expected = "tests/" ^
res in
105 let timeout_testall = 60 in
108 Common.timeout_function
timeout_testall (fun () ->
112 let cocci_infos = Cocci.pre_engine
(cocci_file, !Config.std_iso
) in
113 let xs = Cocci.full_engine
cocci_infos [cfile] in
114 Cocci.post_engine
cocci_infos;
117 match List.assoc
cfile xs with
118 | Some
generated -> generated
122 let (correct
, diffxs
) = Compare_c.compare_default
generated expected
125 (* I don't use Compare_c.compare_result_to_string because
126 * I want to indent a little more the messages.
129 | Compare_c.Correct
-> Hashtbl.add
score res Common.Ok
;
131 let s = Str.global_replace
132 (Str.regexp
"\"/tmp/cocci-output.*\"") "<COCCIOUTPUTFILE>" s
134 (* on macos the temporary files are stored elsewhere *)
135 let s = Str.global_replace
136 (Str.regexp
"\"/var/folders/.*/cocci-output.*\"") "<COCCIOUTPUTFILE>" s
139 "INCORRECT:" ^
s ^
"\n" ^
140 " diff (result(<) vs expected_result(>)) = \n" ^
141 (diffxs
+> List.map
(fun s -> " "^
s^
"\n") +> Common.join
"")
143 Hashtbl.add
score res (Common.Pb
s)
144 | Compare_c.PbOnlyInNotParsedCorrectly
s ->
146 "seems incorrect, but only because of code that " ^
147 "was not parsable" ^
s
149 Hashtbl.add
score res (Common.Pb
s)
154 Common.reset_pr_indent
();
155 let s = "PROBLEM\n" ^
(" exn = " ^
Printexc.to_string exn ^
"\n") in
156 Hashtbl.add
score res (Common.Pb
s)
160 pr2
"--------------------------------";
162 pr2
"--------------------------------";
164 Common.hash_to_list
score +> List.iter
(fun (s, v
) ->
165 pr_no_nl
(Printf.sprintf
"%-30s: " s);
168 | Common.Ok
-> "CORRECT\n"
172 flush stdout
; flush stderr
;
174 pr2
"--------------------------------";
175 pr2
"regression testing information";
176 pr2
"--------------------------------";
178 let expected_score_file_orig = "tests/SCORE_expected_orig.sexp" in
179 let best_of_both_file = "tests/SCORE_best_of_both.sexp" in
180 let actual_score_file = "tests/SCORE_actual.sexp" in
182 pr2
("regression file: "^ expected_score_file
);
183 let (expected_score
: score) =
184 if Sys.file_exists expected_score_file
186 Common.load_score expected_score_file
()
188 let sexp = Sexp.load_sexp expected_score_file in
189 Sexp_common.score_of_sexp sexp
192 if Sys.file_exists
expected_score_file_orig
194 pr2
(spf
"use expected orig file (%s)" expected_score_file_orig);
195 Common.command2
(spf
"cp %s %s" expected_score_file_orig
196 expected_score_file
);
198 let sexp = Sexp.load_sexp expected_score_file in
199 Sexp_common.score_of_sexp sexp
201 Common.load_score expected_score_file
()
207 let new_bestscore = Common.regression_testing_vs
score expected_score
in
210 let xs = Common.hash_to_list score in
211 let sexp = Sexp_common.sexp_of_score_list xs in
212 let s_score = Sexp.to_string_hum sexp in
213 Common.write_file ~file:(actual_score_file) s_score;
215 Common.save_score
score actual_score_file;
218 let xs2 = Common.hash_to_list new_bestscore in
219 let sexp2 = Sexp_common.sexp_of_score_list xs2 in
220 let s_score2 = Sexp.to_string_hum sexp2 in
221 Common.write_file ~file:(best_of_both_file) s_score2;
223 Common.save_score
new_bestscore best_of_both_file;
225 Common.print_total_score
score;
227 let (good
, total
) = Common.total_scores
score in
228 let (expected_good
, expected_total
) = Common.total_scores expected_score
in
230 if good
= expected_good
232 pr2
"Current score is equal to expected score; everything is fine";
236 if good
< expected_good
238 pr2
"Current score is lower than expected :(";
239 pr2
(spf
"(was expecting %d but got %d)" expected_good good
);
241 pr2
"If you think it's normal, then maybe you need to update the";
242 pr2
(spf
"score file %s, copying info from %s."
243 expected_score_file
actual_score_file);
247 pr2
"Current score is greater than expected :)";
248 pr2
(spf
"(was expecting %d but got %d)" expected_good good
);
249 if update_score_file
then
251 pr2
"Generating new expected score file and saving old one";
252 Common.command2_y_or_no_exit_if_no
253 (spf
"mv %s %s" expected_score_file
(expected_score_file ^
".save"));
254 Common.command2_y_or_no_exit_if_no
255 (spf
"mv %s %s" best_of_both_file expected_score_file
);
258 (* when there are sufficient number of tests, abort if a substantial
259 * amount of tests fail, which would indicate a broken build.
261 if total
> 40 && good
< (total
* 3) / 4
263 pr2
"Still, less 75% the tests passed. Returning a nonzero exist status.";
272 (* ------------------------------------------------------------------------ *)
274 type okfailed
= Ok
| SpatchOK
| Failed
277 let t_to_s = function
279 | SpatchOK
-> ".spatch_ok"
280 | Failed
-> ".failed"
282 let delete_previous_result_files infile
=
283 [Ok
;SpatchOK
;Failed
] +> List.iter
(fun kind
->
284 Common.remove_file
(infile ^
t_to_s kind
)
287 (* quite similar to compare_with_expected below *)
288 let test_okfailed cocci_file cfiles
=
289 cfiles
+> List.iter
delete_previous_result_files;
291 (* final_files contain the name of an output file (a .ok or .failed
292 * or .spatch_ok), and also some additionnal strings to be printed in
293 * this output file in addition to the general error message of
295 let final_files = ref [] in
299 Common.new_temp_file
"cocci" ".stdout"
302 let t = Unix.gettimeofday
() in
303 let time_per_file_str () =
304 let t'
= Unix.gettimeofday
() in
305 let tdiff = t'
-. t in
306 let tperfile = tdiff /. (float_of_int
(List.length cfiles
)) in
307 spf
"time: %f" tperfile
310 Common.redirect_stdout_stderr
newout (fun () ->
312 Common.timeout_function_opt
!Flag_cocci.timeout
(fun () ->
314 let cocci_infos = Cocci.pre_engine
(cocci_file, !Config.std_iso
) in
315 let outfiles = Cocci.full_engine
cocci_infos cfiles
in
316 Cocci.post_engine
cocci_infos;
318 let time_str = time_per_file_str () in
320 outfiles +> List.iter
(fun (infile
, outopt
) ->
321 let (dir
, base, ext
) = Common.dbe_of_filename infile
in
322 let expected_suffix =
326 | s -> pr2
("WEIRD: not a .c or .h :" ^
base ^
"." ^
s);
327 "" (* no extension, will compare to same file *)
330 Common.filename_of_dbe
(dir
, base, expected_suffix) in
332 Common.filename_of_dbe
(dir
,"corrected_"^
base,expected_suffix)
335 (* can delete more than the first delete_previous_result_files
336 * because here we can have more files than in cfiles, for instance
339 delete_previous_result_files infile
;
341 match outopt
, Common.lfile_exists
expected_res with
344 | Some outfile
, false ->
345 let s =("PB: input file " ^ infile ^
" modified but no .res") in
346 push2
(infile^
t_to_s Failed
, [s;time_str]) final_files
351 | Some
outfile -> outfile
355 let diff = Compare_c.compare_default
outfile expected_res in
356 let s1 = (Compare_c.compare_result_to_string
diff) in
357 if fst
diff =*= Compare_c.Correct
358 then push2
(infile ^
(t_to_s Ok
), [s1;time_str]) final_files
360 if Common.lfile_exists
expected_res2
362 let diff = Compare_c.compare_default
outfile expected_res2 in
363 let s2 = Compare_c.compare_result_to_string
diff in
364 if fst
diff =*= Compare_c.Correct
365 then push2
(infile ^
(t_to_s SpatchOK
),[s2;s1;time_str])
367 else push2
(infile ^
(t_to_s Failed
), [s2;s1;time_str])
370 else push2
(infile ^
(t_to_s Failed
), [s1;time_str]) final_files
376 Str.global_replace
(Str.regexp
"\\\\n") "\n"
377 (Str.global_replace
(Str.regexp
("\\\\\"")) "\""
378 (Str.global_replace
(Str.regexp
"\\\\t") "\t" s)) in
379 let s = "PROBLEM\n"^
(" exn = " ^
clean(Printexc.to_string exn
) ^
"\n")
381 let time_str = time_per_file_str ()
383 (* we may miss some file because cfiles is shorter than outfiles.
384 * For instance the detected local headers are not in cfiles, so
385 * may have less failed. But at least have some failed.
387 cfiles
+> List.iter
(fun infile
->
388 push2
(infile ^
(t_to_s Failed
), [s;time_str]) final_files;
391 !final_files +> List.iter
(fun (file
, additional_strs
) ->
392 Common.command2
("cp " ^
newout ^
" " ^ file
);
393 with_open_outfile file
(fun (pr
, chan
) ->
394 additional_strs
+> List.iter
(fun s -> pr
(s ^
"\n"))
400 let test_regression_okfailed () =
403 let chop_ext f
= f
+> Filename.chop_extension
in
405 let newscore = Common.empty_score
() in
407 Common.cmd_to_list
("find . -name \"*.ok\"")
409 Common.cmd_to_list
("find . -name \"*.spatch_ok\"")
411 let failed = Common.cmd_to_list
("find . -name \"*.failed\"") in
413 if null
(oks ++ failed)
414 then failwith
"no ok/failed file, you certainly did a make clean"
416 oks +> List.iter
(fun s ->
417 Hashtbl.add
newscore (chop_ext s) Common.Ok
419 failed +> List.iter
(fun s ->
420 Hashtbl.add
newscore (chop_ext s) (Common.Pb
"fail")
422 pr2
"--------------------------------";
423 pr2
"regression testing information";
424 pr2
"--------------------------------";
425 Common.regression_testing
newscore ("score_failed.marshalled")
429 (* ------------------------------------------------------------------------ *)
430 (* quite similar to test_ok_failed. Maybe could factorize code *)
431 let compare_with_expected outfiles =
433 outfiles +> List.iter
(fun (infile
, outopt
) ->
434 let (dir
, base, ext
) = Common.dbe_of_filename infile
in
435 let expected_suffix =
439 | s -> failwith
("weird C file, not a .c or .h :" ^
s)
442 Common.filename_of_dbe
(dir
, base, expected_suffix) in
444 Common.filename_of_dbe
(dir
,"corrected_"^
base,expected_suffix)
447 match outopt
, Common.lfile_exists
expected_res with
449 | Some
outfile, false ->
450 let s =("PB: input file " ^ infile ^
" modified but no .res") in
455 | Some
outfile -> outfile
458 let diff = Compare_c.compare_default
outfile expected_res in
459 let s1 = (Compare_c.compare_result_to_string
diff) in
460 if fst
diff =*= Compare_c.Correct
461 then pr2_no_nl
(infile ^
" " ^
s1)
463 if Common.lfile_exists
expected_res2
465 let diff = Compare_c.compare_default
outfile expected_res2 in
466 let s2 = Compare_c.compare_result_to_string
diff in
467 if fst
diff =*= Compare_c.Correct
468 then pr2
(infile ^
" is spatchOK " ^
s2)
469 else pr2
(infile ^
" is failed " ^
s2)
471 else pr2
(infile ^
" is failed " ^
s1)
474 (*****************************************************************************)
475 (* Subsystem testing *)
476 (*****************************************************************************)
478 let test_parse_cocci file
=
479 if not
(file
=~
".*\\.cocci")
480 then pr2
"warning: seems not a .cocci file";
482 let (_
,xs,_
,_
,_
,_
,(grep_tokens
,query
,_
)) =
483 Parse_cocci.process file
(Some
!Config.std_iso
) false in
484 xs +> List.iter
Pretty_print_cocci.unparse
;
485 Format.print_newline
();
486 (* compile ocaml script code *)
487 (match Prepare_ocamlcocci.prepare file
xs with
489 | Some ocaml_script_file
->
491 Prepare_ocamlcocci.load_file ocaml_script_file
;
493 (if not
!Common.save_tmp_files
494 then Prepare_ocamlcocci.clean_file ocaml_script_file
);
495 (* Print the list of registered functions *)
496 Prepare_ocamlcocci.test
());
497 Printf.printf
"grep tokens\n";
498 (match grep_tokens
with
499 None
-> pr
"No query"
500 | Some
x -> pr
(String.concat
" || " x));
501 match !Flag.scanner
with
502 Flag.NoScanner
| Flag.Grep
-> ()
503 | Flag.Glimpse
| Flag.IdUtils
| Flag.Google _
->
504 Printf.printf
"%s tokens\n"
505 (if !Flag.scanner
= Flag.Glimpse
then "glimpse" else "google");
507 None
-> pr
"No query"
508 | Some
x -> pr
(String.concat
"\nor on glimpse failure\n" x))
512 (*****************************************************************************)
513 (* to be called by ocaml toplevel, to test. *)
514 (*****************************************************************************)
516 (* no point to memoize this one *)
517 let sp_of_file file iso
= Parse_cocci.process file iso
false
523 let flows_of_ast astc =
524 astc +> Common.map_filter (fun e -> ast_to_flow_with_error_messages e)
529 let one_ctl ctls = List.hd (List.hd ctls)