123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334openCommonopenOUnit(*****************************************************************************)(* Sgrep Fuzzy Unit tests *)(*****************************************************************************)(* See https://github.com/facebook/pfff/wiki/Sgrep *)(* run also by sgrep -test *)letsgrep_fuzzy_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=[])))(*****************************************************************************)(* Sgrep generic Unit tests *)(*****************************************************************************)letsgrep_gen_unittest~any_gen_of_string="sgrep features">::(fun()->(* spec: pattern string, code string, should_match boolean *)lettriples=[(* right now any_gen_of_string use the Python sgrep_spatch_pattern
* parser so the syntax below must be valid Python code
*)(* ------------ *)(* 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 *)"import $X","import Foo",true;"x.$X","x.foo",true;(* for expressions *)"foo($X)","foo(1)",true;"foo($X)","foo(1+1)",true;(* for lvalues *)"$X.method()","foo.method()",true;"$X.method()","foo.bar.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;(* metavariable on function name *)"$X(1,2)","foo(1,2)",true;(* metavariable on method call *)"$X.foo()","Bar.foo()",true;(* should not match infix expressions though, even if those
* are transformed internally in Calls *)"$X(...)","a+b",false;(* metavariable for statements *)"if($X):
$S
","if(True):
return 1
",true;(* metavariable string for identifiers *)(* "foo('X');", "foo('a_func');", true; *)(* many arguments metavariables *)(* "foo($MANYARGS);", "foo(1,2,3);", 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;"foo(..., 3, ...)","foo(1,2,3,4)",true;(* ... in more complex expressions *)"strstr(...) == False","strstr(x)==False",true;(* in strings *)"foo(\"...\")","foo(\"this is a long string\")",true;(* for stmts *)(* "class Foo { ... }", "class Foo { int x; }", true; *)(* '...' in strings *)(* "foo(\"...\");", "foo(\"a string\" . \"another string\");", true;*)(* '...' in new *)(* "new Foo(...);","new Foo(1);", true;*)(* "new Foo(...);","new Foo();", true; *)(* '...' in arrays *)(* "foo($X, array(...));", "foo(1, array(2, 3));", true; *)(* ------------ *)(* Misc isomorphisms *)(* ------------ *)(* flexible keyword argument matching, the order does not matter *)"foo(kwd1=$X, kwd2=$Y)","foo(kwd2=1, kwd1=3)",true;(* regexp matching in strings *)"foo(\"=~/a+/\")","foo(\"aaaa\")",true;"foo(\"=~/a+/\")","foo(\"bbbb\")",false;(* "new Foo(...);","new Foo;", true; *)]intriples|>List.iter(fun(spattern,scode,should_match)->tryletpattern=any_gen_of_stringspatterninletcode=any_gen_of_stringscodeinletmatches_with_env=Sgrep_generic.match_any_anypatterncodeinifshould_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=[])withParsing.Parse_error->failwith(spf"problem parsing %s or %s"spatternscode)))(*****************************************************************************)(* Spatch Unit tests *)(*****************************************************************************)(* See https://github.com/facebook/pfff/wiki/Spatch *)(* run by spatch -test *)letspatch_fuzzy_unittest~ast_fuzzy_of_string~parse_file="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:Lib_ast_fuzzy.toks_of_treesspatchfileinlettrees,toks=parse_filesrcfileinletwas_modified=Spatch_fuzzy.spatchpatterntreesinletresopt=ifwas_modifiedthenSome(Lib_unparser.string_of_toks_using_transfotoks)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]
)
*)