Making Test Determinator parametric to Java and Clang

Reviewed By: martinoluca

Differential Revision: D8010835

fbshipit-source-id: 3182c79
master
Dino Distefano 7 years ago committed by Facebook Github Bot
parent 9ca945aa2f
commit d922b37ab8

@ -1544,6 +1544,14 @@ and max_nesting =
skipped. If omitted, all levels are shown."
and method_decls_info =
CLOpt.mk_string_opt ~long:"method-decls-info"
"Specifies the file containing the method declarations info (eg. start line, end line, \
class, method name, etc.) when Infer is run Test Determinator mode. Used in combination with \
other options as follows: $(b,--test-determinator --modified-lines modified_line_file \
--profiler-sample profiler_sample_file --method-decls-info minfo.json)"
and merge =
CLOpt.mk_bool ~deprecated:["merge"] ~long:"merge"
~in_help:InferCommand.[(Analyze, manual_buck_flavors)]
@ -1561,10 +1569,11 @@ and ml_buckets =
and modified_lines =
CLOpt.mk_string ~long:"modified-lines" ~default:""
CLOpt.mk_string_opt ~long:"modified-lines"
"Specifies the file containing the modified lines when Infer is run Test Determinator mode. \
--test-determinator --modified-lines modified_lines_file --profiler-sample \
profiler_sample_file"
Used in combination with other options as follows:\n \
$(b, --test-determinator --modified-lines modified_lines_file --profiler-sample \
profiler_sample_file)"
and modified_targets =
@ -2031,9 +2040,8 @@ and symops_per_iteration =
and test_determinator =
CLOpt.mk_bool ~long:"test-determinator" ~default:false
"Run infer in Test Determinator mode. It is used together with the --modified-lines and \
--test-profiler flags \n \
which speficy the relevant arguments. E.g. --test-determinator --modified-lines \
modified_line_file --profiler-sample profiler_sample_file"
--test-profiler flags which speficy the relevant arguments. E.g. $(b, --test-determinator \
--modified-lines modified_line_file --profiler-sample profiler_sample_file)"
and test_filtering =
@ -2041,11 +2049,12 @@ and test_filtering =
"List all the files Infer can report on (should be called from the root of the project)"
and profiler_sample =
CLOpt.mk_string ~long:"profiler-sample" ~default:""
and profiler_samples =
CLOpt.mk_string_opt ~long:"profiler-samples"
"Specifies the file containing the profiler samples when Infer is run Test Determinator mode. \
--test-determinator --modified-lines modified_line_file --profiler-sample \
profiler_samples_file"
Used in combination with other options as follows:\n \
$(b,--test-determinator --modified-lines modified_line_file --profiler-samples \
profiler_samples_file)"
and testing_mode =
@ -2650,6 +2659,8 @@ and log_file = !log_file
and max_nesting = !max_nesting
and method_decls_info = !method_decls_info
and merge = !merge
and ml_buckets = !ml_buckets
@ -2824,7 +2835,7 @@ and test_determinator = !test_determinator
and test_filtering = !test_filtering
and profiler_sample = !profiler_sample
and profiler_samples = !profiler_samples
and testing_mode = !testing_mode

@ -493,6 +493,8 @@ val log_file : string
val max_nesting : int option
val method_decls_info : string option
val merge : bool
val ml_buckets :
@ -500,7 +502,7 @@ val ml_buckets :
val models_mode : bool
val modified_lines : string
val modified_lines : string option
val modified_targets : string option
@ -626,7 +628,7 @@ val test_determinator : bool
val test_filtering : bool
val profiler_sample : string
val profiler_samples : string option
val testing_mode : bool

@ -120,7 +120,12 @@ let () =
prepare_events_logging () ;
if Config.debug_mode && CLOpt.is_originator then
L.progress "Logs in %s@." (Config.results_dir ^/ Config.log_file) ;
( match Config.command with
( if Config.test_determinator then (
TestDeterminator.test_to_run_java Config.modified_lines Config.profiler_samples
Config.method_decls_info ;
TestDeterminator.print_test_to_run () )
else
match Config.command with
| Analyze ->
run Driver.Analyze
| Capture | Compile | Run ->

@ -264,7 +264,7 @@ module JNI = struct
end
end
let create ~classname ~methodname ~signature ~kind =
let create ~classname ~methodname ~signature =
let name = Typ.Name.Java.from_string classname in
let args, ret_typ = JNI.parse_method_str signature in
let java_type_args = List.map ~f:JNI.to_java_type args in
@ -275,7 +275,9 @@ let create ~classname ~methodname ~signature ~kind =
then None
else Some (JNI.to_java_type ret_typ)
in
Typ.Procname.Java (Typ.Procname.Java.make name java_type_ret_typ methodname java_type_args kind)
Typ.Procname.Java
(Typ.Procname.Java.make name java_type_ret_typ methodname java_type_args
Typ.Procname.Java.Non_Static)
type labeled_profiler_sample = string * ProfilerSample.t [@@deriving compare]
@ -300,9 +302,7 @@ let from_json j =
; ("signature", `String signature)
; _ ]
:: tl ->
let procname =
create ~kind:Typ.Procname.Java.Non_Static ~classname ~methodname ~signature
in
let procname = create ~classname ~methodname ~signature in
parse_json tl (procname :: acc)
| [] ->
acc

@ -48,3 +48,5 @@ val equal_labeled_profiler_sample : labeled_profiler_sample -> labeled_profiler_
val from_json_string : string -> labeled_profiler_sample list
val from_json_file : string -> labeled_profiler_sample list
val create : classname:string -> methodname:string -> signature:string -> ProfilerSample.elt

@ -36,8 +36,6 @@ let do_source_file linereader classes program tenv source_basename package_opt s
JFrontend.compute_source_icfg linereader classes program tenv source_basename package_opt
source_file
in
if Config.test_determinator then
TestDeterminator.test_to_run source_file cfg Config.modified_lines Config.profiler_sample ;
store_icfg source_file cfg

@ -7,103 +7,253 @@
* of patent rights can be found in the PATENTS file in the same directory.
*)
module L = Logging
module F = Format
open JavaProfilerSamples
open! IStd
module LineRangeMap = Caml.Map.Make (struct
module RangeMap = Caml.Map.Make (struct
type t = Typ.Procname.t
let compare = Typ.Procname.compare
end)
let update_line_range_map pdesc map =
let initialized_test_determinator = ref false
let is_test_determinator_init () = !initialized_test_determinator
module MethodRangeMap = struct
let map : (Location.t * Location.t) RangeMap.t ref = ref RangeMap.empty
let method_range_map () = !map
let split_class_method_name qualified_method_name =
String.rsplit2_exn qualified_method_name ~on:'.'
let create_java_method_range_map code_graph_file' =
match code_graph_file' with
| Some code_graph_file ->
let open Java_method_decl_j in
let json_string =
match Utils.read_file code_graph_file with
| Ok cl_list ->
let json = List.fold cl_list ~init:"" ~f:(fun acc s -> acc ^ s) in
json
| Error _ ->
L.die UserError "Could not read file %s" code_graph_file
in
let method_decls = java_method_decls_of_string json_string in
let map' =
List.fold method_decls ~init:RangeMap.empty ~f:(fun acc decl ->
let start_location =
{ Location.line= decl.start_line
; col= -1
; file= SourceFile.create ~warn_on_error:false decl.source_file }
in
let end_location =
{ Location.line= decl.end_line
; col= -1
; file= SourceFile.create ~warn_on_error:false decl.source_file }
in
let range = (start_location, end_location) in
let classname, methodname = split_class_method_name decl.method_name in
let signature = match decl.signature with Some s -> s | _ -> "" in
let key = JavaProfilerSamples.create ~classname ~methodname ~signature in
RangeMap.add key range acc )
in
map := map'
| _ ->
L.die UserError "Missing method declaration info argument"
let create_clang_method_range_map cfg =
let update_method_range_map pdesc =
let start_node = Procdesc.get_start_node pdesc in
let exit_node = Procdesc.get_exit_node pdesc in
let range = (Procdesc.Node.get_loc start_node, Procdesc.Node.get_loc exit_node) in
let key = Procdesc.get_proc_name pdesc in
LineRangeMap.add key range map
map := RangeMap.add key range !map
in
Typ.Procname.Hash.iter (fun _ pdesc -> update_method_range_map pdesc) cfg
let pp_map fmt () =
let pp_map' fmt m =
RangeMap.iter
(fun key range ->
F.fprintf fmt "@\n %a --> (%a,%a)" Typ.Procname.pp key Location.pp (fst range)
Location.pp (snd range) )
m
in
F.fprintf fmt "@\n--- Method Range Map ---%a@\n--- End Method Range Map --- @\n" pp_map' !map
end
module DiffLines = struct
(* This is a map
file name |--> {set of changed line }
*)
let map : int list String.Map.t ref = ref String.Map.empty
let changed_lines_map () = !map
(* Read the file containing info on changed lines and populate the map *)
let init_changed_lines_map changed_lines_file' =
match changed_lines_file' with
| Some changed_lines_file
-> (
L.progress "@\n Initializing modified lines map from file '%s'... " changed_lines_file ;
match Utils.read_file changed_lines_file with
| Ok cl_list ->
let changed_lines =
List.fold cl_list ~init:String.Map.empty ~f:(fun acc cl_item ->
let fname, cl = String.rsplit2_exn ~on:':' cl_item in
String.Map.set acc ~key:fname ~data:(FileDiff.parse_unix_diff cl) )
in
L.progress " done! @\n" ;
map := changed_lines
| Error _ ->
L.die UserError "Could not read file %s" changed_lines_file )
| _ ->
L.die UserError "Missing modified lines argument"
let print_changed_lines () =
L.(debug Analysis Medium) "@\n--- Changed Lines Map --- " ;
String.Map.iteri !map ~f:(fun ~key:k ~data:d ->
L.(debug Analysis Medium) "\n %s --> [" k ;
List.iter d ~f:(L.(debug Analysis Medium) " %i ") ;
L.(debug Analysis Medium) " ] " ) ;
L.(debug Analysis Medium) "@\n--- End Changed Lines Map --- @\n"
end
let pp_profiler_sample_set fmt s =
F.fprintf fmt "size = %i" (ProfilerSample.cardinal s) ;
ProfilerSample.iter (fun m -> F.fprintf fmt "@\n > %a " Typ.Procname.pp m) s
module TestSample = struct
let labeled_test_samples = ref []
let test_sample () = !labeled_test_samples
let init_test_sample test_samples_file' =
match test_samples_file' with
| Some test_samples_file ->
L.progress "@\nReading Profiler Samples File '%s'...." test_samples_file ;
let ts = JavaProfilerSamples.from_json_file test_samples_file in
labeled_test_samples := ts
| _ ->
L.die UserError "Missing profiler samples argument"
let pp_map fmt () =
List.iter !labeled_test_samples ~f:(fun (label, profiler_samples) ->
F.fprintf fmt "=== Samples for %s ===@\n%a@\n=== End Samples for %s ===@\n" label
pp_profiler_sample_set profiler_samples label )
end
let in_range l range = l >= (fst range).Location.line && l <= (snd range).Location.line
let affected_methods line_range_map changed_lines =
LineRangeMap.fold
let affected_methods method_range_map changed_lines =
RangeMap.fold
(fun key range acc ->
if List.exists ~f:(fun l -> in_range l range) changed_lines then ProfilerSample.add key acc
if List.exists ~f:(fun l -> in_range l range) changed_lines then
(*L.progress "@\n ADDING '%a' in affected methods..." Typ.Procname.pp key ; *)
ProfilerSample.add key acc
else acc )
line_range_map ProfilerSample.empty
method_range_map ProfilerSample.empty
let compute_affected_methods fname cfg files_changed_lines_map =
L.(debug Capture Verbose) "@\n Looking for file %s in files_changed_line_map @\n" fname ;
match String.Map.find files_changed_lines_map fname with
| Some changed_lines ->
let line_range_map : (Location.t * Location.t) LineRangeMap.t = LineRangeMap.empty in
let line_range_map' =
Typ.Procname.Hash.fold
(fun _ pdesc acc -> update_line_range_map pdesc acc)
cfg line_range_map
let compute_affected_methods_java changed_lines_map method_range_map =
let affected_methods =
String.Map.fold changed_lines_map ~init:ProfilerSample.empty ~f:(fun ~key:_ ~data acc ->
let am = affected_methods method_range_map data in
ProfilerSample.union am acc )
in
L.(debug Capture Verbose) "@\n Line Range Map" ;
LineRangeMap.iter
(fun key range ->
L.(debug Capture Verbose)
"@\n %a --> (%a,%a)" Typ.Procname.pp key Location.pp (fst range) Location.pp
(snd range) )
line_range_map' ;
L.(debug Capture Verbose) "@\n End Line Range Map @\n" ;
let affected_methods = affected_methods line_range_map' changed_lines in
L.(debug Capture Verbose) "@\n == Start Printing Affected Methods == " ;
ProfilerSample.iter
(fun m -> L.(debug Capture Verbose) "@\n METHOD> %a " Typ.Procname.pp m)
affected_methods ;
L.(debug Capture Verbose) "@\n == End Printing Affected Methods == @\n" ;
L.progress "== Resulting Affected Methods ==@\n%a@\n== End Affected Methods ==@\n"
pp_profiler_sample_set affected_methods ;
affected_methods
let compute_affected_methods_clang source_file changed_lines_map method_range_map =
let fname = SourceFile.to_rel_path source_file in
L.progress "@\n Looking for file %s in changed-line map..." fname ;
match String.Map.find changed_lines_map fname with
| Some changed_lines ->
L.progress " found!@\n" ;
let affected_methods = affected_methods method_range_map changed_lines in
L.progress "== Resulting Affected Methods ==@\n%a@\n== End Affected Methods ==@\n"
pp_profiler_sample_set affected_methods ;
affected_methods
| None ->
L.(debug Capture Verbose)
"@\n File name %s was not found in files_changed_line_map @\n" fname ;
L.progress "@\n%s not found in changed-line map. Nothing else to do for it.@\n" fname ;
ProfilerSample.empty
let read_changed_lines_file changed_lines_file =
match Utils.read_file changed_lines_file with
| Ok cl_list ->
let changed_lines =
List.fold cl_list ~init:String.Map.empty ~f:(fun acc cl_item ->
let fname, cl = String.rsplit2_exn ~on:':' cl_item in
String.Map.set acc ~key:fname ~data:(FileDiff.parse_unix_diff cl) )
in
changed_lines
| Error _ ->
String.Map.empty
let relevant_tests = ref []
let _get_relevant_test_to_run () = !relevant_tests
let print_test_to_run () =
L.progress "@\n [TEST DETERMINATOR] Relevant Tests to run = [" ;
List.iter ~f:(L.progress " %s ") !relevant_tests ;
L.progress " ] @\n" ;
let json = `List (List.map ~f:(fun t -> `String t) !relevant_tests) in
Yojson.Basic.to_file (Config.results_dir ^/ "test_determinator.json") json
let print_test_to_run test_to_run =
L.result "@\n [Result Test Determinator:] Test to run = [" ;
List.iter ~f:(L.result " %s ") test_to_run ;
L.result " ] @\n"
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 Analysis Medium) "%a@\n" MethodRangeMap.pp_map () ;
TestSample.init_test_sample test_samples_file ;
L.(debug Analysis Medium) "%a@\n" TestSample.pp_map () ;
initialized_test_determinator := true
let print_changed_lines changed_lines =
L.(debug Capture Verbose) "@\n Changed lines = {" ;
String.Map.iteri changed_lines ~f:(fun ~key:k ~data:d ->
L.(debug Capture Verbose) "\n %s --> [" k ;
List.iter d ~f:(L.(debug Capture Verbose) " %i ") ;
L.(debug Capture Verbose) " ] " ) ;
L.(debug Capture Verbose) "@\n } @\n"
let init_java changed_lines_file test_samples_file code_graph_file =
DiffLines.init_changed_lines_map changed_lines_file ;
DiffLines.print_changed_lines () ;
MethodRangeMap.create_java_method_range_map code_graph_file ;
L.(debug Analysis Medium) "%a@\n" MethodRangeMap.pp_map () ;
TestSample.init_test_sample test_samples_file ;
L.(debug Analysis Medium) "%a@\n" TestSample.pp_map () ;
initialized_test_determinator := true
(* test_to_run = { n | Affected_Method /\ ts_n != 0 } *)
let test_to_run source_file cfg changed_lines_file test_samples_file =
L.(debug Capture Verbose) "@\n ***** Start Test Determinator ***** @\n" ;
let fname = SourceFile.to_rel_path source_file in
let changed_lines = read_changed_lines_file changed_lines_file in
print_changed_lines changed_lines ;
let test_samples = JavaProfilerSamples.from_json_file test_samples_file in
let affected_methods = compute_affected_methods fname cfg changed_lines in
let _test_to_run_clang source_file cfg changed_lines_file test_samples_file =
L.progress "@\n ***** Start Test Determinator for %s ***** @\n"
(SourceFile.to_string source_file) ;
if is_test_determinator_init () then () else init_clang cfg changed_lines_file test_samples_file ;
let affected_methods =
compute_affected_methods_clang source_file (DiffLines.changed_lines_map ())
(MethodRangeMap.method_range_map ())
in
let test_to_run =
if ProfilerSample.is_empty affected_methods then []
else
List.fold (TestSample.test_sample ()) ~init:[] ~f:(fun acc (label, profiler_samples) ->
let intersection = ProfilerSample.inter affected_methods profiler_samples in
if ProfilerSample.is_empty intersection then acc else label :: acc )
in
relevant_tests := List.append test_to_run !relevant_tests
let test_to_run_java changed_lines_file test_samples_file code_graph_file =
L.progress "@\n ***** Start Test Determinator ***** @\n" ;
if is_test_determinator_init () then ()
else init_java changed_lines_file test_samples_file code_graph_file ;
let affected_methods =
compute_affected_methods_java (DiffLines.changed_lines_map ())
(MethodRangeMap.method_range_map ())
in
let test_to_run =
List.fold test_samples ~init:[] ~f:(fun acc (label, profiler_samples) ->
if ProfilerSample.is_empty affected_methods then []
else
List.fold (TestSample.test_sample ()) ~init:[] ~f:(fun acc (label, profiler_samples) ->
let intersection = ProfilerSample.inter affected_methods profiler_samples in
if ProfilerSample.is_empty intersection then acc else label :: acc )
in
print_test_to_run test_to_run
relevant_tests := List.append test_to_run !relevant_tests

@ -7,4 +7,11 @@
* of patent rights can be found in the PATENTS file in the same directory.
*)
val test_to_run : SourceFile.t -> Procdesc.t Typ.Procname.Hash.t -> string -> string -> unit
val test_to_run_java : string option -> string option -> string option -> unit
val _test_to_run_clang :
SourceFile.t -> Procdesc.t Typ.Procname.Hash.t -> string option -> string option -> unit
val print_test_to_run : unit -> unit
val _get_relevant_test_to_run : unit -> string list

Loading…
Cancel
Save