123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205openCommonopenOUnit(*****************************************************************************)(* Sgrep Unit tests *)(*****************************************************************************)(* See https://github.com/facebook/pfff/wiki/Sgrep *)(* run by sgrep -test *)letsgrep_unittest~ast_fuzzy_of_string="sgrep features">::(fun()->(* spec: pattern string, code string, should_match boolean *)lettriples=[(* ------------ *)(* spacing *)(* ------------ *)(* basic string match of course *)"foo(1,2);","foo(1,2);",true;"foo(1,3);","foo(1,2);",false;(* matches even when space or newline differs *)"foo(1,2);","foo(1, 2);",true;"foo(1,2);","foo(1,
2);",true;(* matches even when have comments in the middle *)"foo(1,2);","foo(1, /* foo */ 2);",true;(* ------------ *)(* metavariables *)(* ------------ *)(* for identifiers *)"class $X { int x; }","class Foo { int x; }",true;(* for expressions *)"foo($X);","foo(1);",true;"foo($X);","foo(1+1);",true;(* for lvalues *)"$X->method();","this->method();",true;(*TODO "$X->method();" , "this->foo()->method();", true; *)(* this will work though: "->method();" , "$this->foo()->method();", true; *)(* "linear" patterns, a la Prolog *)"$X && $X;","(a || b) && (a || b);",true;"foo($X, $X);","foo(a, a);",true;"foo($X, $X);","foo(a, b);",false;(* many arguments metavariables *)(*TODO "foo($MANYARGS);", "foo(1,2,3);", true; *)(* metavariable on function name *)"$X(1,2);","foo(1,2);",true;(* metavariable on class name *)"$X::foo();","Ent::foo();",true;(* metavariable string for identifiers *)(*TODO "foo('X');", "foo('a_func');", true; *)(* metavariable on reference arguments *)"foo($X,$Y);","foo(&a, b);",true;(* metavariable on class name reference *)"new $X(...);","new $dyn();",true;"new $X(...);","new self();",true;(* ------------ *)(* ... *)(* ------------ *)(* for stmts *)"class Foo { ... }","class Foo { int x; }",true;(* '...' in funcall *)"foo(...);","foo();",true;"foo(...);","foo(1);",true;"foo(...);","foo(1,2);",true;"foo($X,...);","foo(1,2);",true;(* ... also match when there is no additional arguments *)"foo($X,...);","foo(1);",true;(* TODO: foo(..., 3, ...), foo(1,2,3,4) *)(* '...' in arrays *)"foo($X, array(...));","foo(1, array(2, 3));",true;(* '...' in strings *)(*TODO "foo(\"...\");", "foo(\"a string\");", true; *)(*TODO "foo(\"...\");", "foo(\"a string\" . \"another string\");", true;*)(* '...' in new *)"new Foo(...);","new Foo(1);",true;"new Foo(...);","new Foo();",true;(* more complex expressions *)"strstr(...) == false;","strstr(x)==false;",true;(* ------------ *)(* Misc isomorphisms *)(* ------------ *)(*TODO "new Foo(...);","new Foo;", true; *)]intriples+>List.iter(fun(spattern,scode,should_match)->letpattern=ast_fuzzy_of_stringspatterninletcode=ast_fuzzy_of_stringscodeinletmatches_with_env=Matching_fuzzy.match_trees_treespatterncodeinifshould_matchthenassert_bool(spf"pattern:|%s| should match |%s"spatternscode)(matches_with_env<>[])elseassert_bool(spf"pattern:|%s| should not match |%s"spatternscode)(matches_with_env=[])))(*****************************************************************************)(* Spatch Unit tests *)(*****************************************************************************)(* See https://github.com/facebook/pfff/wiki/Spatch *)(* run by spatch -test *)letspatch_unittest~ast_fuzzy_of_string~parse_file~kind_and_info_of_tok="spatch regressions files">::(fun()->lettestdir=Filename.concatConfig_pfff.path"tests/fuzzy/spatch/"inletexpfiles=Common2.glob(testdir^"*.exp")inexpfiles+>List.iter(funexpfile->(* todo: this regexp should just be .*? but ocaml regexp do not
* have the greedy feature :( Also note that expfile is a fullpath
* so it can contains /, hence this ugly regexp
*)ifexpfile=~"\\([a-zA-Z_/]+\\)\\([0-9]*\\)\\.exp$"thenbeginlet(prefix,variant)=Common.matched2expfileinletspatchfile=prefix^".spatch"inletsrcfile=prefix^variant^".fuzzy"inletpattern=Spatch_fuzzy.parse~pattern_of_string:ast_fuzzy_of_string~ii_of_pattern:Ast_fuzzy.toks_of_treesspatchfileinlettrees,toks=parse_filesrcfileinletwas_modified=Spatch_fuzzy.spatchpatterntreesinletunparsetoks=Lib_unparser.string_of_toks_using_transfo~kind_and_info_of_toktoksinletresopt=ifwas_modifiedthenSome(unparsetoks)elseNoneinletfile_res=matchresoptwith|None->srcfile|Somes->lettmpfile=Common.new_temp_file"spatch_test"".fuzzy"inCommon.write_file~file:tmpfiles;tmpfileinletdiff=Common2.unix_difffile_resexpfileindiff+>List.iterpr;ifList.lengthdiff>1thenassert_failure(spf"spatch %s on %s should have resulted in %s"(Filename.basenamespatchfile)(Filename.basenamesrcfile)(Filename.basenameexpfile))endelsefailwith("wrong format for expfile: "^expfile)))(*****************************************************************************)(* Misc unit tests *)(*****************************************************************************)(*
let misc_unittest =
"misc" >::: [
"join_with_space" >:: (fun () ->
assert_equal
(Matching_report.join_with_space_if_needed ["$x";"=";"print";"FOO"])
"$x=print FOO"
)
]
*)(*****************************************************************************)(* Final suite *)(*****************************************************************************)(*
let unittest =
"matcher" >::: (
sgrep_unittest ++ spatch_unittest ++ [misc_unittest]
)
*)