diff --git a/.gitignore b/.gitignore index 9d75e6842..d6a5048c6 100644 --- a/.gitignore +++ b/.gitignore @@ -24,22 +24,23 @@ duplicates.txt *.ast.sh *.ast.bdump *.ast.biniou +/infer/tests/build_systems/buck_flavors_deterministic/capture_hash-*.sha /infer/tests/build_systems/buck_flavors_diff/src/hello.c -/infer/tests/build_systems/codetoanalyze/ndk-build/hello_app/libs/ -/infer/tests/build_systems/codetoanalyze/ndk-build/hello_app/obj/ -/infer/tests/build_systems/codetoanalyze/utf8_*n_pwd -/infer/tests/build_systems/codetoanalyze/mvn/**/target/ -/infer/tests/build_systems/codetoanalyze/path with spaces/ /infer/tests/build_systems/clang_compilation_db_escaped/compile_commands.json /infer/tests/build_systems/clang_compilation_db_relpath/compile_commands.json +/infer/tests/build_systems/clang_test_determinator/*.test /infer/tests/build_systems/clang_with_MD_flag/hello.d +/infer/tests/build_systems/codetoanalyze/mvn/**/target/ +/infer/tests/build_systems/codetoanalyze/ndk-build/hello_app/libs/ +/infer/tests/build_systems/codetoanalyze/ndk-build/hello_app/obj/ +/infer/tests/build_systems/codetoanalyze/path with spaces/ +/infer/tests/build_systems/codetoanalyze/utf8_*n_pwd /infer/tests/build_systems/codetoanalyze/xcodebuild/simple_app/app_built /infer/tests/build_systems/codetoanalyze/xcodebuild/simple_app/build/ -/infer/tests/build_systems/differential_*/**/*.class -/infer/tests/build_systems/differential_*/**/Diff*.java /infer/tests/build_systems/diff/src /infer/tests/build_systems/diff_*/src -/infer/tests/build_systems/buck_flavors_deterministic/capture_hash-*.sha +/infer/tests/build_systems/differential_*/**/*.class +/infer/tests/build_systems/differential_*/**/Diff*.java /infer/tests/build_systems/genrule/report.json /infer/tests/build_systems/java_test_determinator/*.test /infer/tests/codetoanalyze/java/classloads/*.loads diff --git a/Makefile b/Makefile index b044ff0fb..330dbe0c5 100644 --- a/Makefile +++ b/Makefile @@ -45,6 +45,7 @@ BUILD_SYSTEMS_TESTS += \ run_hidden_linters \ tracebugs \ utf8_in_procname \ + clang_test_determinator \ DIRECT_TESTS += \ c_biabduction \ diff --git a/infer/man/man1/infer-full.txt b/infer/man/man1/infer-full.txt index 9c3e2c842..d514a3fe7 100644 --- a/infer/man/man1/infer-full.txt +++ b/infer/man/man1/infer-full.txt @@ -1514,6 +1514,11 @@ INTERNAL OPTIONS which speficy the relevant arguments. (Conversely: --no-test-determinator) + --test-determinator-clang + Activates: Run infer in Test Determinator mode for clang. It is + used together with the --modified-lines. (Conversely: + --no-test-determinator-clang) + --test-filtering Activates: List all the files Infer can report on (should be called from the root of the project) (Conversely: diff --git a/infer/src/backend/InferPrint.ml b/infer/src/backend/InferPrint.ml index 93a0996d9..5ec68ae2a 100644 --- a/infer/src/backend/InferPrint.ml +++ b/infer/src/backend/InferPrint.ml @@ -1068,6 +1068,9 @@ let register_perf_stats_report () = let main ~report_json = + if Config.test_determinator_clang then ( + TestDeterminator.emit_tests_to_run () ; + TestDeterminator.emit_relevant_methods () ) ; let issue_formats = init_issues_format_list report_json in let formats_by_report_kind = let costs_report_format_kind = diff --git a/infer/src/base/Config.ml b/infer/src/base/Config.ml index 6944a1950..860751ee2 100644 --- a/infer/src/base/Config.ml +++ b/infer/src/base/Config.ml @@ -2163,6 +2163,12 @@ and test_determinator = $(b,--test-profiler) flags, which speficy the relevant arguments." +and test_determinator_clang = + CLOpt.mk_bool ~long:"test-determinator-clang" ~default:false + "Run infer in Test Determinator mode for clang. It is used together with the \ + $(b,--modified-lines)." + + and test_filtering = CLOpt.mk_bool ~deprecated:["test_filtering"] ~long:"test-filtering" "List all the files Infer can report on (should be called from the root of the project)" @@ -3024,6 +3030,8 @@ and keep_going = !keep_going and test_determinator = !test_determinator +and test_determinator_clang = !test_determinator_clang + and test_filtering = !test_filtering and profiler_samples = !profiler_samples diff --git a/infer/src/base/Config.mli b/infer/src/base/Config.mli index 3aec27e3a..6818f0468 100644 --- a/infer/src/base/Config.mli +++ b/infer/src/base/Config.mli @@ -652,6 +652,8 @@ val symops_per_iteration : int option val test_determinator : bool +val test_determinator_clang : bool + val test_filtering : bool val testing_mode : bool diff --git a/infer/src/clang/cFrontend.ml b/infer/src/clang/cFrontend.ml index f377adef9..69a2fc82d 100644 --- a/infer/src/clang/cFrontend.ml +++ b/infer/src/clang/cFrontend.ml @@ -52,6 +52,8 @@ let do_source_file (translation_unit_context : CFrontend_config.translation_unit let cfg = compute_icfg translation_unit_context tenv ast in L.(debug Capture Verbose) "@\n End building call/cfg graph for '%a'.@\n" SourceFile.pp source_file ; + if Config.test_determinator_clang then + TestDeterminator.test_to_run_clang source_file cfg Config.modified_lines None ; (* This part below is a boilerplate in every frontends. *) (* This could be moved in the cfg_infer module *) NullabilityPreanalysis.analysis cfg tenv ; diff --git a/infer/src/integration/testDeterminator.ml b/infer/src/integration/testDeterminator.ml index ca4215fe5..1a662c6a8 100644 --- a/infer/src/integration/testDeterminator.ml +++ b/infer/src/integration/testDeterminator.ml @@ -100,8 +100,8 @@ module MethodRangeMap = struct end module DiffLines = struct - (* This is a map - file name |--> {set of changed line } + (* This is a map + file name |--> {set of changed line } *) let map : int list String.Map.t ref = ref String.Map.empty @@ -136,8 +136,8 @@ module DiffLines = struct end let pp_profiler_sample_set fmt s = - F.fprintf fmt " (size = %i) " (JPS.ProfilerSample.cardinal s) ; - JPS.ProfilerSample.iter (fun m -> F.fprintf fmt "@\n > %a " Typ.Procname.pp m) s + F.fprintf fmt " (set size = %i) " (JPS.ProfilerSample.cardinal s) ; + JPS.ProfilerSample.iter (fun m -> F.fprintf fmt "@\n %a " Typ.Procname.pp m) s module TestSample = struct @@ -201,7 +201,7 @@ let compute_affected_methods_clang source_file changed_lines_map method_range_ma L.(debug TestDeterminator Medium) "found!@\n" ; let affected_methods = affected_methods method_range_map fname changed_lines in L.(debug TestDeterminator Medium) - "== Resulting Affected Methods ==@\n%a@\n== End Affected Methods ==@\n" + "@\n@\n== Resulting Affected Methods ==%a@\n== End Affected Methods ==@\n\n" pp_profiler_sample_set affected_methods ; affected_methods | None -> @@ -212,6 +212,9 @@ let compute_affected_methods_clang source_file changed_lines_map method_range_ma let relevant_tests = ref [] +(* Methods modified in a diff *) +let relevant_methods = ref [] + let _get_relevant_test_to_run () = !relevant_tests let emit_tests_to_run () = @@ -221,14 +224,28 @@ let emit_tests_to_run () = L.progress "Tests to run: [%a]@\n" (Pp.seq ~sep:", " F.pp_print_string) !relevant_tests +let emit_relevant_methods () = + let methods = List.dedup_and_sort ~compare:String.compare !relevant_methods in + let json = `List (List.map ~f:(fun t -> `String t) methods) in + let outpath = Config.results_dir ^/ "diff_determinator.json" in + Yojson.Basic.to_file outpath json ; + L.progress "Methods modified in this Diff: [%a]@\n" + (Pp.seq ~sep:", " F.pp_print_string) + !relevant_methods + + let init_clang cfg changed_lines_file test_samples_file = DiffLines.init_changed_lines_map changed_lines_file ; DiffLines.print_changed_lines () ; MethodRangeMap.create_clang_method_range_map cfg ; L.(debug TestDeterminator Medium) "%a@\n" MethodRangeMap.pp_map () ; - TestSample.init_test_sample test_samples_file ; - L.(debug TestDeterminator Medium) "%a@\n" TestSample.pp_map () ; - initialized_test_determinator := true + match test_samples_file with + | Some _ -> + TestSample.init_test_sample test_samples_file + | _ -> + () ; + L.(debug TestDeterminator Medium) "%a@\n" TestSample.pp_map () ; + initialized_test_determinator := true let init_java changed_lines_file test_samples_file code_graph_file = @@ -242,7 +259,7 @@ let init_java changed_lines_file test_samples_file code_graph_file = (* test_to_run = { n | Affected_Method /\ ts_n != 0 } *) -let _test_to_run_clang source_file cfg changed_lines_file test_samples_file = +let test_to_run_clang source_file cfg changed_lines_file test_samples_file = L.(debug TestDeterminator Quiet) "****** Start Test Determinator for %s *****@\n" (SourceFile.to_string source_file) ; @@ -251,6 +268,10 @@ let _test_to_run_clang source_file cfg changed_lines_file test_samples_file = compute_affected_methods_clang source_file (DiffLines.changed_lines_map ()) (MethodRangeMap.method_range_map ()) in + let affected_methods_list = + JPS.ProfilerSample.fold (fun m acc -> Typ.Procname.to_string m :: acc) affected_methods [] + in + relevant_methods := List.append affected_methods_list !relevant_methods ; let test_to_run = if JPS.ProfilerSample.is_empty affected_methods then [] else diff --git a/infer/src/integration/testDeterminator.mli b/infer/src/integration/testDeterminator.mli index b67a90cec..c075ce23a 100644 --- a/infer/src/integration/testDeterminator.mli +++ b/infer/src/integration/testDeterminator.mli @@ -9,9 +9,11 @@ open! IStd val test_to_run_java : string option -> string option -> string option -> unit -val _test_to_run_clang : +val test_to_run_clang : SourceFile.t -> Procdesc.t Typ.Procname.Hash.t -> string option -> string option -> unit val emit_tests_to_run : unit -> unit +val emit_relevant_methods : unit -> unit + val _get_relevant_test_to_run : unit -> string list diff --git a/infer/tests/build_systems/clang_test_determinator/Makefile b/infer/tests/build_systems/clang_test_determinator/Makefile new file mode 100644 index 000000000..c239ede5a --- /dev/null +++ b/infer/tests/build_systems/clang_test_determinator/Makefile @@ -0,0 +1,46 @@ +# Copyright (c) 2017-present, Facebook, Inc. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +# E2E test involving the test_determinator feature + +TESTS_DIR = ../.. +include $(TESTS_DIR)/base.make + +A_CPP = A.cpp +TEST_DETERMINATOR_RESULT = infer-out-mod2/diff_determinator.json +DIFF_OUTPUT = diff.mod2.test + +default: $(TEST_DETERMINATOR_RESULT) + +$(DIFF_OUTPUT): + $(QUIET)echo -n '$(A_CPP):' > diff.mod1.test + $(QUIET)(diff -N --unchanged-line-format="U" --old-line-format="O" --new-line-format="N" \ + orig-$(A_CPP) mod1-$(A_CPP) || [ $$? = 1 ]) >> diff.mod1.test + $(QUIET)echo -n '$(A_CPP):' > diff.mod2.test + $(QUIET)(diff -N --unchanged-line-format="U" --old-line-format="O" --new-line-format="N" \ + orig-$(A_CPP) mod2-$(A_CPP) || [ $$? = 1 ]) >> diff.mod2.test + +$(TEST_DETERMINATOR_RESULT): $(DIFF_OUTPUT) + $(QUIET)$(call silent_on_success,Testing test-determinator with set of changes in mod1,\ + cp mod1-$(A_CPP) $(A_CPP);\ + $(INFER_BIN) -o infer-out-mod1 --test-determinator-clang --modified-lines diff.mod1.test -- clang -c $(A_CPP)) + $(QUIET)$(call silent_on_success,Testing test-determinator-clang with set of changes in mod2,\ + cp mod2-$(A_CPP) $(A_CPP);\ + $(INFER_BIN) -o infer-out-mod2 --test-determinator-clang --modified-lines diff.mod2.test -- clang -c $(A_CPP)) + $(QUIET) rm $(A_CPP) + +.PHONY: test +test: $(TEST_DETERMINATOR_RESULT) + $(QUIET)$(call check_no_diff,diff_determinator.json.mod1.exp,infer-out-mod1/diff_determinator.json) + $(QUIET)$(call check_no_diff,diff_determinator.json.mod2.exp,infer-out-mod2/diff_determinator.json) + +.PHONY: replace +replace: $(TEST_DETERMINATOR_RESULT) + $(COPY) infer-out-mod1/diff_determinator.json diff_determinator.json.mod1.exp + $(COPY) infer-out-mod2/diff_determinator.json diff_determinator.json.mod2.exp + +.PHONY: clean +clean: + $(REMOVE_DIR) *.test infer-out-mod* *.o diff --git a/infer/tests/build_systems/clang_test_determinator/diff_determinator.json.mod1.exp b/infer/tests/build_systems/clang_test_determinator/diff_determinator.json.mod1.exp new file mode 100644 index 000000000..dad09161e --- /dev/null +++ b/infer/tests/build_systems/clang_test_determinator/diff_determinator.json.mod1.exp @@ -0,0 +1 @@ +["Shapes::Cube::area","Shapes::Cube::sort","Shapes::Cube::sort::lambda_A.cpp:24:25::","Shapes::Cube::sort::lambda_A.cpp:24:25::operator()"] \ No newline at end of file diff --git a/infer/tests/build_systems/clang_test_determinator/diff_determinator.json.mod2.exp b/infer/tests/build_systems/clang_test_determinator/diff_determinator.json.mod2.exp new file mode 100644 index 000000000..3dc95fead --- /dev/null +++ b/infer/tests/build_systems/clang_test_determinator/diff_determinator.json.mod2.exp @@ -0,0 +1 @@ +["Shapes::Cube::sort"] \ No newline at end of file diff --git a/infer/tests/build_systems/clang_test_determinator/mod1-A.cpp b/infer/tests/build_systems/clang_test_determinator/mod1-A.cpp new file mode 100644 index 000000000..e5936db06 --- /dev/null +++ b/infer/tests/build_systems/clang_test_determinator/mod1-A.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2019-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include + +namespace Shapes { + +class Cube { + int size; + + public: + void set_size(int); + int area() { return size * size * size; }; + void sort(Cube*, unsigned); +}; + +void Cube::set_size(int s) { size = s; } + +void Cube::sort(Cube* xs, unsigned n) { + std::sort(xs, xs + n, [](Cube a, Cube b) { return (a.area() > b.area()); }); +} +} // namespace Shapes diff --git a/infer/tests/build_systems/clang_test_determinator/mod2-A.cpp b/infer/tests/build_systems/clang_test_determinator/mod2-A.cpp new file mode 100644 index 000000000..f4bb3a03e --- /dev/null +++ b/infer/tests/build_systems/clang_test_determinator/mod2-A.cpp @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2019-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include + +namespace Shapes { + +class Cube { + int size; + + public: + void set_size(int); + int area() { return size * size; }; + void sort(Cube*, unsigned); +}; + +void Cube::set_size(int s) { size = s; } + +void Cube::sort(Cube* xs, unsigned n) { + // this is a lambda folks + std::sort(xs, xs + n, [](Cube a, Cube b) { return (a.area() < b.area()); }); +} +} // namespace Shapes diff --git a/infer/tests/build_systems/clang_test_determinator/orig-A.cpp b/infer/tests/build_systems/clang_test_determinator/orig-A.cpp new file mode 100644 index 000000000..8b4c58595 --- /dev/null +++ b/infer/tests/build_systems/clang_test_determinator/orig-A.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2019-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include + +namespace Shapes { + +class Cube { + int size; + + public: + void set_size(int); + int area() { return size * size; }; + void sort(Cube*, unsigned); +}; + +void Cube::set_size(int s) { size = s; } + +void Cube::sort(Cube* xs, unsigned n) { + std::sort(xs, xs + n, [](Cube a, Cube b) { return (a.area() < b.area()); }); +} +} // namespace Shapes