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." 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 = and merge =
CLOpt.mk_bool ~deprecated:["merge"] ~long:"merge" CLOpt.mk_bool ~deprecated:["merge"] ~long:"merge"
~in_help:InferCommand.[(Analyze, manual_buck_flavors)] ~in_help:InferCommand.[(Analyze, manual_buck_flavors)]
@ -1561,10 +1569,11 @@ and ml_buckets =
and modified_lines = 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. \ "Specifies the file containing the modified lines when Infer is run Test Determinator mode. \
--test-determinator --modified-lines modified_lines_file --profiler-sample \ Used in combination with other options as follows:\n \
profiler_sample_file" $(b, --test-determinator --modified-lines modified_lines_file --profiler-sample \
profiler_sample_file)"
and modified_targets = and modified_targets =
@ -2031,9 +2040,8 @@ and symops_per_iteration =
and test_determinator = and test_determinator =
CLOpt.mk_bool ~long:"test-determinator" ~default:false CLOpt.mk_bool ~long:"test-determinator" ~default:false
"Run infer in Test Determinator mode. It is used together with the --modified-lines and \ "Run infer in Test Determinator mode. It is used together with the --modified-lines and \
--test-profiler flags \n \ --test-profiler flags which speficy the relevant arguments. E.g. $(b, --test-determinator \
which speficy the relevant arguments. E.g. --test-determinator --modified-lines \ --modified-lines modified_line_file --profiler-sample profiler_sample_file)"
modified_line_file --profiler-sample profiler_sample_file"
and test_filtering = 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)" "List all the files Infer can report on (should be called from the root of the project)"
and profiler_sample = and profiler_samples =
CLOpt.mk_string ~long:"profiler-sample" ~default:"" CLOpt.mk_string_opt ~long:"profiler-samples"
"Specifies the file containing the profiler samples when Infer is run Test Determinator mode. \ "Specifies the file containing the profiler samples when Infer is run Test Determinator mode. \
--test-determinator --modified-lines modified_line_file --profiler-sample \ Used in combination with other options as follows:\n \
profiler_samples_file" $(b,--test-determinator --modified-lines modified_line_file --profiler-samples \
profiler_samples_file)"
and testing_mode = and testing_mode =
@ -2650,6 +2659,8 @@ and log_file = !log_file
and max_nesting = !max_nesting and max_nesting = !max_nesting
and method_decls_info = !method_decls_info
and merge = !merge and merge = !merge
and ml_buckets = !ml_buckets and ml_buckets = !ml_buckets
@ -2824,7 +2835,7 @@ and test_determinator = !test_determinator
and test_filtering = !test_filtering and test_filtering = !test_filtering
and profiler_sample = !profiler_sample and profiler_samples = !profiler_samples
and testing_mode = !testing_mode and testing_mode = !testing_mode

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

@ -120,7 +120,12 @@ let () =
prepare_events_logging () ; prepare_events_logging () ;
if Config.debug_mode && CLOpt.is_originator then if Config.debug_mode && CLOpt.is_originator then
L.progress "Logs in %s@." (Config.results_dir ^/ Config.log_file) ; 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 -> | Analyze ->
run Driver.Analyze run Driver.Analyze
| Capture | Compile | Run -> | Capture | Compile | Run ->

@ -264,7 +264,7 @@ module JNI = struct
end end
end end
let create ~classname ~methodname ~signature ~kind = let create ~classname ~methodname ~signature =
let name = Typ.Name.Java.from_string classname in let name = Typ.Name.Java.from_string classname in
let args, ret_typ = JNI.parse_method_str signature in let args, ret_typ = JNI.parse_method_str signature in
let java_type_args = List.map ~f:JNI.to_java_type args 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 then None
else Some (JNI.to_java_type ret_typ) else Some (JNI.to_java_type ret_typ)
in 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] type labeled_profiler_sample = string * ProfilerSample.t [@@deriving compare]
@ -300,9 +302,7 @@ let from_json j =
; ("signature", `String signature) ; ("signature", `String signature)
; _ ] ; _ ]
:: tl -> :: tl ->
let procname = let procname = create ~classname ~methodname ~signature in
create ~kind:Typ.Procname.Java.Non_Static ~classname ~methodname ~signature
in
parse_json tl (procname :: acc) parse_json tl (procname :: acc)
| [] -> | [] ->
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_string : string -> labeled_profiler_sample list
val from_json_file : 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 JFrontend.compute_source_icfg linereader classes program tenv source_basename package_opt
source_file source_file
in in
if Config.test_determinator then
TestDeterminator.test_to_run source_file cfg Config.modified_lines Config.profiler_sample ;
store_icfg source_file cfg store_icfg source_file cfg

@ -7,103 +7,253 @@
* of patent rights can be found in the PATENTS file in the same directory. * of patent rights can be found in the PATENTS file in the same directory.
*) *)
module L = Logging module L = Logging
module F = Format
open JavaProfilerSamples open JavaProfilerSamples
open! IStd open! IStd
module LineRangeMap = Caml.Map.Make (struct module RangeMap = Caml.Map.Make (struct
type t = Typ.Procname.t type t = Typ.Procname.t
let compare = Typ.Procname.compare let compare = Typ.Procname.compare
end) 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 start_node = Procdesc.get_start_node pdesc in
let exit_node = Procdesc.get_exit_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 range = (Procdesc.Node.get_loc start_node, Procdesc.Node.get_loc exit_node) in
let key = Procdesc.get_proc_name pdesc 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 in_range l range = l >= (fst range).Location.line && l <= (snd range).Location.line
let affected_methods line_range_map changed_lines = let affected_methods method_range_map changed_lines =
LineRangeMap.fold RangeMap.fold
(fun key range acc -> (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 ) else acc )
line_range_map ProfilerSample.empty method_range_map ProfilerSample.empty
let compute_affected_methods fname cfg files_changed_lines_map = let compute_affected_methods_java changed_lines_map method_range_map =
L.(debug Capture Verbose) "@\n Looking for file %s in files_changed_line_map @\n" fname ; let affected_methods =
match String.Map.find files_changed_lines_map fname with String.Map.fold changed_lines_map ~init:ProfilerSample.empty ~f:(fun ~key:_ ~data acc ->
| Some changed_lines -> let am = affected_methods method_range_map data in
let line_range_map : (Location.t * Location.t) LineRangeMap.t = LineRangeMap.empty in ProfilerSample.union am acc )
let line_range_map' =
Typ.Procname.Hash.fold
(fun _ pdesc acc -> update_line_range_map pdesc acc)
cfg line_range_map
in in
L.(debug Capture Verbose) "@\n Line Range Map" ; L.progress "== Resulting Affected Methods ==@\n%a@\n== End Affected Methods ==@\n"
LineRangeMap.iter pp_profiler_sample_set affected_methods ;
(fun key range -> affected_methods
L.(debug Capture Verbose)
"@\n %a --> (%a,%a)" Typ.Procname.pp key Location.pp (fst range) Location.pp
(snd range) ) let compute_affected_methods_clang source_file changed_lines_map method_range_map =
line_range_map' ; let fname = SourceFile.to_rel_path source_file in
L.(debug Capture Verbose) "@\n End Line Range Map @\n" ; L.progress "@\n Looking for file %s in changed-line map..." fname ;
let affected_methods = affected_methods line_range_map' changed_lines in match String.Map.find changed_lines_map fname with
L.(debug Capture Verbose) "@\n == Start Printing Affected Methods == " ; | Some changed_lines ->
ProfilerSample.iter L.progress " found!@\n" ;
(fun m -> L.(debug Capture Verbose) "@\n METHOD> %a " Typ.Procname.pp m) let affected_methods = affected_methods method_range_map changed_lines in
affected_methods ; L.progress "== Resulting Affected Methods ==@\n%a@\n== End Affected Methods ==@\n"
L.(debug Capture Verbose) "@\n == End Printing Affected Methods == @\n" ; pp_profiler_sample_set affected_methods ;
affected_methods affected_methods
| None -> | None ->
L.(debug Capture Verbose) L.progress "@\n%s not found in changed-line map. Nothing else to do for it.@\n" fname ;
"@\n File name %s was not found in files_changed_line_map @\n" fname ;
ProfilerSample.empty ProfilerSample.empty
let read_changed_lines_file changed_lines_file = let relevant_tests = ref []
match Utils.read_file changed_lines_file with
| Ok cl_list -> let _get_relevant_test_to_run () = !relevant_tests
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 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 = {" ; let init_java changed_lines_file test_samples_file code_graph_file =
String.Map.iteri changed_lines ~f:(fun ~key:k ~data:d -> DiffLines.init_changed_lines_map changed_lines_file ;
L.(debug Capture Verbose) "\n %s --> [" k ; DiffLines.print_changed_lines () ;
List.iter d ~f:(L.(debug Capture Verbose) " %i ") ; MethodRangeMap.create_java_method_range_map code_graph_file ;
L.(debug Capture Verbose) " ] " ) ; L.(debug Analysis Medium) "%a@\n" MethodRangeMap.pp_map () ;
L.(debug Capture Verbose) "@\n } @\n" 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 } *) (* test_to_run = { n | Affected_Method /\ ts_n != 0 } *)
let test_to_run 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 Capture Verbose) "@\n ***** Start Test Determinator ***** @\n" ; L.progress "@\n ***** Start Test Determinator for %s ***** @\n"
let fname = SourceFile.to_rel_path source_file in (SourceFile.to_string source_file) ;
let changed_lines = read_changed_lines_file changed_lines_file in if is_test_determinator_init () then () else init_clang cfg changed_lines_file test_samples_file ;
print_changed_lines changed_lines ; let affected_methods =
let test_samples = JavaProfilerSamples.from_json_file test_samples_file in compute_affected_methods_clang source_file (DiffLines.changed_lines_map ())
let affected_methods = compute_affected_methods fname cfg changed_lines in (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 = 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 let intersection = ProfilerSample.inter affected_methods profiler_samples in
if ProfilerSample.is_empty intersection then acc else label :: acc ) if ProfilerSample.is_empty intersection then acc else label :: acc )
in 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. * 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