Deprecate incremental

Summary:public
Deprecate the incremental mode.
Several parts of the back-end can be removed.
The options for incremental analysis -i at the python level are now deprecated, and re-routed to --reactive.
The main difference with --reactive is that it does not produce an analysis of the whole project, but is limited to what is reachable via reactive propagation starting from the changed files.

Reviewed By: sblackshear

Differential Revision: D2960078

fb-gh-sync-id: 6e8b46b
shipit-source-id: 6e8b46b
master
Cristiano Calcagno 9 years ago committed by Facebook Github Bot 5
parent c868f51b2d
commit e0d5847eb8

@ -118,7 +118,6 @@ def main():
args = global_argparser.parse_args(to_parse) args = global_argparser.parse_args(to_parse)
remove_infer_out = (imported_module remove_infer_out = (imported_module
and not args.incremental
and not args.reactive and not args.reactive
and capture_module_name != 'analyze' and capture_module_name != 'analyze'
and not args.buck) and not args.buck)

@ -52,15 +52,6 @@ class VersionAction(argparse._VersionAction):
option_string) option_string)
class ConfirmIncrementalAction(argparse._StoreTrueAction):
def __call__(self, parser, namespace, values, option_string=None):
if not getattr(namespace, 'incremental'):
parser.error('-ic/--changed-only should only be used with -i')
super(ConfirmIncrementalAction, self).__call__(parser,
namespace,
values,
option_string)
base_parser = argparse.ArgumentParser(add_help=False) base_parser = argparse.ArgumentParser(add_help=False)
base_group = base_parser.add_argument_group('global arguments') base_group = base_parser.add_argument_group('global arguments')
@ -68,13 +59,12 @@ base_group.add_argument('-o', '--out', metavar='<directory>',
default=config.DEFAULT_INFER_OUT, dest='infer_out', default=config.DEFAULT_INFER_OUT, dest='infer_out',
action=utils.AbsolutePathAction, action=utils.AbsolutePathAction,
help='Set the Infer results directory') help='Set the Infer results directory')
base_group.add_argument('-i', '--incremental', action='store_true', base_group.add_argument('-i', '--incremental',
help='''Analyze only changed procedures and their dest='reactive', action='store_true',
dependencies''') help='''DEPRECATED: use --reactive.''')
base_group.add_argument('-ic', '--changed-only', base_group.add_argument('-ic', '--changed-only',
action=ConfirmIncrementalAction, dest='reactive', action='store_true',
help='''Same as -i, but does not analyze help='''DEPRECATED: use --reactive.''')
dependencies of changed procedures.''')
base_group.add_argument('--reactive', action='store_true', base_group.add_argument('--reactive', action='store_true',
help='''Analyze in reactive propagation mode help='''Analyze in reactive propagation mode
starting from changed files.''') starting from changed files.''')
@ -313,12 +303,6 @@ class AnalyzerWrapper(object):
infer_options.append('-developer_mode') infer_options.append('-developer_mode')
self.args.no_filtering = True self.args.no_filtering = True
if self.args.incremental:
if self.args.changed_only:
infer_options.append('-incremental_changed_only')
else:
infer_options.append('-incremental')
if self.args.reactive: if self.args.reactive:
infer_options.append('-reactive') infer_options.append('-reactive')

@ -126,15 +126,6 @@ module Node = struct
let pdesc_tbl_find cfg proc_name = let pdesc_tbl_find cfg proc_name =
Procname.Hash.find cfg.name_pdesc_tbl proc_name Procname.Hash.find cfg.name_pdesc_tbl proc_name
let proc_name_to_proc_desc cfg proc_name =
pdesc_tbl_find cfg proc_name
let proc_name_is_changed cfg proc_name =
try
let pdesc = proc_name_to_proc_desc cfg proc_name in
pdesc.pd_attributes.changed
with Not_found -> true
let iter_proc_desc cfg f = let iter_proc_desc cfg f =
Procname.Hash.iter f cfg.name_pdesc_tbl Procname.Hash.iter f cfg.name_pdesc_tbl
@ -895,10 +886,6 @@ let get_block_pdesc cfg block =
Some block_pdesc Some block_pdesc
with Not_found -> None with Not_found -> None
(** return true if the pdesc associated with [pname] changed since the last analysis run or did
not exist in the last analysis run *)
let pdesc_is_changed cfg pname = Node.proc_name_is_changed cfg pname
(** Removes seeds variables from a prop corresponding to captured variables in an objc block *) (** Removes seeds variables from a prop corresponding to captured variables in an objc block *)
let remove_seed_captured_vars_block captured_vars prop = let remove_seed_captured_vars_block captured_vars prop =
let is_captured pname vn = Mangled.equal pname vn in let is_captured pname vn = Mangled.equal pname vn in

@ -312,9 +312,5 @@ val check_cfg_connectedness : cfg -> unit
(** Given a mangled name of a block return its procdesc if exists*) (** Given a mangled name of a block return its procdesc if exists*)
val get_block_pdesc : cfg -> Mangled.t -> Procdesc.t option val get_block_pdesc : cfg -> Mangled.t -> Procdesc.t option
(** return true if the pdesc associated with [pname] changed since the last analysis run or did
not exist in the last analysis run *)
val pdesc_is_changed : cfg -> Procname.t -> bool
(** Removes seeds variables from a prop corresponding to captured variables in an objc block *) (** Removes seeds variables from a prop corresponding to captured variables in an objc block *)
val remove_seed_captured_vars_block : Mangled.t list -> Prop.normal Prop.t -> Prop.normal Prop.t val remove_seed_captured_vars_block : Mangled.t list -> Prop.normal Prop.t -> Prop.normal Prop.t

@ -81,8 +81,9 @@ let add_active_proc exe_env proc_name =
| None -> () | None -> ()
| Some procset -> exe_env.active_opt <- Some (Procname.Set.add proc_name procset) | Some procset -> exe_env.active_opt <- Some (Procname.Set.add proc_name procset)
(** like add_cg, but use exclude_fun to determine files to be excluded *) (** add call graph from fname in the spec db,
let add_cg_exclude_fun (exe_env: t) (source_dir : DB.source_dir) exclude_fun = with relative tenv and cfg, to the execution environment *)
let add_cg (exe_env: t) (source_dir : DB.source_dir) =
let cg_fname = DB.source_dir_get_internal_file source_dir ".cg" in let cg_fname = DB.source_dir_get_internal_file source_dir ".cg" in
let cg_opt = match Cg.load_from_file cg_fname with let cg_opt = match Cg.load_from_file cg_fname with
| None -> (L.stderr "cannot load %s@." (DB.filename_to_string cg_fname); None) | None -> (L.stderr "cannot load %s@." (DB.filename_to_string cg_fname); None)
@ -94,26 +95,25 @@ let add_cg_exclude_fun (exe_env: t) (source_dir : DB.source_dir) exclude_fun =
| Some cg -> | Some cg ->
let source = Cg.get_source cg in let source = Cg.get_source cg in
exe_env.source_files <- DB.SourceFileSet.add source exe_env.source_files; exe_env.source_files <- DB.SourceFileSet.add source exe_env.source_files;
if exclude_fun source then None let nLOC = Cg.get_nLOC cg in
else Cg.extend exe_env.cg cg;
let nLOC = Cg.get_nLOC cg in let file_data = new_file_data source nLOC cg_fname in
Cg.extend exe_env.cg cg; let defined_procs = Cg.get_defined_nodes cg in
let file_data = new_file_data source nLOC cg_fname in IList.iter (fun pname ->
let defined_procs = Cg.get_defined_nodes cg in let should_update =
IList.iter (fun pname -> if Procname.Hash.mem exe_env.proc_map pname then
let should_update = let old_source = (Procname.Hash.find exe_env.proc_map pname).source in
if Procname.Hash.mem exe_env.proc_map pname then exe_env.procs_defined_in_several_files <-
let old_source = (Procname.Hash.find exe_env.proc_map pname).source in Procname.Set.add pname exe_env.procs_defined_in_several_files;
exe_env.procs_defined_in_several_files <- Procname.Set.add pname exe_env.procs_defined_in_several_files; (* L.err "Warning: procedure %a is defined in both %s and %s@."*)
(* L.err "Warning: procedure %a is defined in both %s and %s@." Procname.pp pname (DB.source_file_to_string source) (DB.source_file_to_string old_source); *) (* Procname.pp pname (DB.source_file_to_string source)*)
source < old_source (* when a procedure is defined in several files, map to the first alphabetically *) (* (DB.source_file_to_string old_source); *)
else true in source < old_source (* when a procedure is defined in several files,
if should_update then Procname.Hash.replace exe_env.proc_map pname file_data) defined_procs; map to the first alphabetically *)
Some cg else true in
if should_update
(** add call graph from fname in the spec db, with relative tenv and cfg, to the execution environment *) then Procname.Hash.replace exe_env.proc_map pname file_data) defined_procs;
let add_cg exe_env (source_dir : DB.source_dir) = Some cg
add_cg_exclude_fun exe_env source_dir (fun _ -> false)
(** get the procedures defined in more than one file *) (** get the procedures defined in more than one file *)
let get_procs_defined_in_several_files exe_env = let get_procs_defined_in_several_files exe_env =

@ -22,12 +22,10 @@ val freeze : initial -> t
(** create a new execution environment, given an optional set restricting the active procedures *) (** create a new execution environment, given an optional set restricting the active procedures *)
val create : Procname.Set.t option -> initial val create : Procname.Set.t option -> initial
(** add call graph from the source dir in the spec db, with relative tenv and cfg, to the execution environment *) (** add call graph from the source dir in the spec db,
with relative tenv and cfg, to the execution environment *)
val add_cg : initial -> DB.source_dir -> Cg.t option val add_cg : initial -> DB.source_dir -> Cg.t option
(** like add_cg, but use exclude_fun to determine files to be excluded *)
val add_cg_exclude_fun : initial -> DB.source_dir -> (DB.source_file -> bool) -> Cg.t option
(** get the global call graph *) (** get the global call graph *)
val get_cg : t -> Cg.t val get_cg : t -> Cg.t

@ -45,13 +45,6 @@ end
(** if true, save file dependency graph to disk *) (** if true, save file dependency graph to disk *)
let save_file_dependency = ref false let save_file_dependency = ref false
type incremental =
| ANALYZE_ALL (** analyze all the files in the results dir *)
| ANALYZE_CHANGED_AND_DEPENDENCIES (** analyze the files that have changed, as well as those dependent on them *)
| ANALYZE_CHANGED_ONLY (** only analyze the files that have changed *)
let incremental_mode = ref ANALYZE_ALL
(** command line option: if true, simulate the analysis only *) (** command line option: if true, simulate the analysis only *)
let simulate = ref false let simulate = ref false
@ -76,31 +69,12 @@ let out_file_cmdline = ref ""
(** value of -err_file command-line *) (** value of -err_file command-line *)
let err_file_cmdline = ref "" let err_file_cmdline = ref ""
(** Files excluded from the analysis *)
let excluded_files : string list ref = ref []
(** Absolute path to the project source, used for relative paths in the exclude list *)
let source_path = ref ""
(** List of obj memory leak buckets to be checked in Objective-C/C++ *) (** List of obj memory leak buckets to be checked in Objective-C/C++ *)
let ml_buckets_arg = ref "cf" let ml_buckets_arg = ref "cf"
(** Whether specs can be cleaned up before starting analysis *) (** Whether specs can be cleaned up before starting analysis *)
let allow_specs_cleanup = ref false let allow_specs_cleanup = ref false
(** Compute the exclude function from excluded_files and source_path.
The exclude function builds an exclude list of file path prefixes, and checks if one
of them is a prefix of the given source file.
Prefixes are obtained by prepending source_path, if any, to relative paths in excluded_fies *)
let compute_exclude_fun () : DB.source_file -> bool =
let prepend_source_path s =
if Filename.is_relative s then Filename.concat !source_path s
else s in
let excluded_list = IList.map (fun file_path -> prepend_source_path file_path) !excluded_files in
let exclude_fun (source_file : DB.source_file) =
IList.exists (fun excluded_path -> string_is_prefix excluded_path (DB.source_file_to_string source_file)) excluded_list in
exclude_fun
let version_string () = let version_string () =
"Infer version " ^ Version.versionString ^ "\nCopyright 2009 - present Facebook. All Rights Reserved.\n" "Infer version " ^ Version.versionString ^ "\nCopyright 2009 - present Facebook. All Rights Reserved.\n"
@ -114,24 +88,6 @@ let print_version_json () =
let arg_desc = let arg_desc =
let base_arg = let base_arg =
let exclude s = match read_file s with
| None ->
F.fprintf F.std_formatter "ERROR: cannot read the exclude file %s@." s;
exit 1
| Some files -> excluded_files := files in
let source_path s =
if Filename.is_relative s then
begin
F.fprintf F.std_formatter "ERROR: source_path must be an absolute path: %s @." s;
exit 1
end;
source_path := s in
let incremental_changed_only () =
Config.ondemand_enabled := false;
incremental_mode := ANALYZE_CHANGED_ONLY in
let incremental () =
Config.ondemand_enabled := false;
incremental_mode := ANALYZE_CHANGED_AND_DEPENDENCIES in
let desc = let desc =
base_arg_desc @ base_arg_desc @
[ [
@ -140,21 +96,6 @@ let arg_desc =
Some "file", Some "file",
"use file for the err channel" "use file for the err channel"
; ;
"-exclude",
Arg.String exclude,
Some "file",
"exclude from analysis the files and directories specified in file"
;
"-incremental_changed_only",
Arg.Unit incremental_changed_only,
None,
"only analyze files captured since the last analysis"
;
"-incremental",
Arg.Unit incremental,
None,
"analyze files captured since the last analysis plus any dependencies"
;
"-iterations", "-iterations",
Arg.Int set_iterations, Arg.Int set_iterations,
Some "n", Some "n",
@ -182,12 +123,6 @@ let arg_desc =
None, None,
"analyze in reactive propagation mode starting from changed files" "analyze in reactive propagation mode starting from changed files"
; ;
"-source_path",
Arg.String source_path,
Some "path",
"specify the absolute path to the root of the source files. \
Used to interpret relative paths when using option -exclude."
;
(* TODO: merge with the -project_root option *) (* TODO: merge with the -project_root option *)
"-java", "-java",
Arg.Unit (fun () -> Config.curr_language := Config.Java), Arg.Unit (fun () -> Config.curr_language := Config.Java),
@ -573,66 +508,10 @@ let create_minimal_clusters file_cg exe_env to_analyze_map : Cluster.t list =
let proc_list_to_set proc_list = let proc_list_to_set proc_list =
IList.fold_left (fun s p -> Procname.Set.add p s) Procname.Set.empty proc_list IList.fold_left (fun s p -> Procname.Set.add p s) Procname.Set.empty proc_list
(** compute the files to analyze map for incremental mode *)
let compute_to_analyze_map_incremental files_changed_map global_cg exe_env =
let changed_fold_f files_changed_map f =
let apply_f _ procs acc =
Procname.Set.fold (fun proc acc -> Procname.Set.union acc (f proc)) procs acc in
Procname.Map.fold apply_f files_changed_map Procname.Set.empty in
(* all callers of changed procedures *)
let callers_of_changed =
changed_fold_f files_changed_map (fun p -> Cg.get_ancestors global_cg p) in
(* all callees of changed procedures *)
let callees_of_changed =
changed_fold_f files_changed_map (fun p -> Cg.get_heirs global_cg p) in
(* add a procedure to the file -> (procedures to analyze) map *)
let add_proc_to_map proc map =
let source_opt =
try Some (Exe_env.get_source exe_env proc)
with Not_found -> None in
match source_opt with
| Some source ->
let proc_file_pname = ClusterMakefile.source_file_to_pname source in
let procs_to_analyze =
try Procname.Map.find proc_file_pname map
with Not_found -> Procname.Set.empty in
let procs_to_analyze' = Procname.Set.add proc procs_to_analyze in
Procname.Map.add proc_file_pname procs_to_analyze' map
| None -> map in
let get_specs_filename pname =
Specs.res_dir_specs_filename pname in
let is_stale pname =
let specs_file = get_specs_filename pname in
match Specs.load_summary specs_file with
| Some summary -> summary.Specs.status = Specs.STALE
| None -> false in
(* add stale callees to the map *)
let add_stale_callee_to_map callee map =
if is_stale callee then add_proc_to_map callee map
else map in
let files_changed_map' =
Procname.Set.fold add_stale_callee_to_map callees_of_changed files_changed_map in
if !incremental_mode = ANALYZE_CHANGED_ONLY then
(* mark all caller specs stale. this ensures future runs of the analysis that need to use specs
from callers will re-compute them first. we do this instead of deleting the specs because
that would force the next analysis run to re-compute them even if it doesn't need them. *)
let mark_spec_as_stale pname =
let specs_file = get_specs_filename pname in
match Specs.load_summary specs_file with
| Some summary ->
let summary' = { summary with Specs.status = Specs.STALE } in
Specs.store_summary pname summary'
| None -> () in
Procname.Set.iter (fun caller -> mark_spec_as_stale caller) callers_of_changed;
files_changed_map'
else (* !incremental_mode = ANALYZE_CHANGED_AND_DEPENDENTS *)
(* add callers of changed procedures to the map of stuff to analyze *)
Procname.Set.fold add_proc_to_map callers_of_changed files_changed_map'
(** compute the clusters *) (** compute the clusters *)
let compute_clusters exe_env files_changed : Cluster.t list = let compute_clusters exe_env : Cluster.t list =
if !Cluster.trace_clusters then if !Cluster.trace_clusters then
L.err "[compute_clusters] %d changed files@." (Procname.Map.cardinal files_changed); L.err "[compute_clusters] @.";
let file_cg = Cg.create () in let file_cg = Cg.create () in
let global_cg = Exe_env.get_cg exe_env in let global_cg = Exe_env.get_cg exe_env in
let nodes, edges = Cg.get_nodes_and_edges global_cg in let nodes, edges = Cg.get_nodes_and_edges global_cg in
@ -669,16 +548,14 @@ let compute_clusters exe_env files_changed : Cluster.t list =
let num_files = IList.length files in let num_files = IList.length files in
L.err "@.Found %d defined procedures in %d files.@." (IList.length defined_procs) num_files; L.err "@.Found %d defined procedures in %d files.@." (IList.length defined_procs) num_files;
let to_analyze_map = let to_analyze_map =
if !incremental_mode = ANALYZE_ALL then (* get all procedures defined in a file *)
(* get all procedures defined in a file *) let get_defined_procs file_pname = match file_pname_to_cg file_pname with
let get_defined_procs file_pname = match file_pname_to_cg file_pname with | None -> Procname.Set.empty
| None -> Procname.Set.empty | Some cg -> proc_list_to_set (Cg.get_defined_nodes cg) in
| Some cg -> proc_list_to_set (Cg.get_defined_nodes cg) in IList.fold_left
IList.fold_left (fun m file_pname -> Procname.Map.add file_pname (get_defined_procs file_pname) m)
(fun m file_pname -> Procname.Map.add file_pname (get_defined_procs file_pname) m) Procname.Map.empty
Procname.Map.empty files in
files
else compute_to_analyze_map_incremental files_changed global_cg exe_env in
L.err "Analyzing %d files.@.@." (Procname.Map.cardinal to_analyze_map); L.err "Analyzing %d files.@.@." (Procname.Map.cardinal to_analyze_map);
let clusters = create_minimal_clusters file_cg exe_env to_analyze_map in let clusters = create_minimal_clusters file_cg exe_env to_analyze_map in
L.err "Minimal clusters:@."; L.err "Minimal clusters:@.";
@ -706,61 +583,22 @@ let compute_clusters exe_env files_changed : Cluster.t list =
L.log_progress_simple ("\nAnalyzing "^(string_of_int number_of_clusters)^" clusters"); L.log_progress_simple ("\nAnalyzing "^(string_of_int number_of_clusters)^" clusters");
clusters' clusters'
(** compute the set of procedures in [cg] changed since the last analysis *)
let cg_get_changed_procs source_dir cg =
let cfg_fname = DB.source_dir_get_internal_file source_dir ".cfg" in
let cfg_opt = Cfg.load_cfg_from_file cfg_fname in
let pdesc_changed pname =
match cfg_opt with
| Some cfg -> Cfg.pdesc_is_changed cfg pname
| None -> true in
let spec_exists pname =
let spec_fname = Specs.res_dir_specs_filename pname in
Sys.file_exists (DB.filename_to_string spec_fname) in
let cfg_modified_after_specs pname =
let spec_fname = Specs.res_dir_specs_filename pname in
DB.file_modified_time cfg_fname > DB.file_modified_time spec_fname in
let is_changed pname =
not (spec_exists pname) || (cfg_modified_after_specs pname && pdesc_changed pname) in
let defined_nodes = Cg.get_defined_nodes cg in
if !Config.incremental_procs then IList.filter is_changed defined_nodes
else if IList.exists is_changed defined_nodes then defined_nodes
else []
(** Load a .c or .cpp file into an execution environment *) (** Load a .c or .cpp file into an execution environment *)
let load_cg_file (_exe_env: Exe_env.initial) (source_dir : DB.source_dir) exclude_fun = let load_cg_file (_exe_env: Exe_env.initial) (source_dir : DB.source_dir) =
match Exe_env.add_cg_exclude_fun _exe_env source_dir exclude_fun with match Exe_env.add_cg _exe_env source_dir with
| None -> None | None -> None
| Some cg -> | Some cg ->
L.err "loaded %s@." (DB.source_dir_to_string source_dir); L.err "loaded %s@." (DB.source_dir_to_string source_dir);
Some cg Some cg
(** Return a map of (changed file procname) -> (procs in that file that have changed) *) (** Populate the exe_env by loading cg files. *)
let compute_files_changed_map _exe_env (source_dirs : DB.source_dir list) exclude_fun = let populate_exe_env _exe_env (source_dirs : DB.source_dir list) =
let sorted_dirs = IList.sort DB.source_dir_compare source_dirs in let sorted_dirs = IList.sort DB.source_dir_compare source_dirs in
let cg_list = IList.iter
IList.fold_left (fun source_dir ->
(fun cg_list source_dir -> ignore (load_cg_file _exe_env source_dir))
match load_cg_file _exe_env source_dir exclude_fun with sorted_dirs;
| None -> cg_list Exe_env.freeze _exe_env
| Some cg -> (source_dir, cg) :: cg_list)
[]
sorted_dirs in
let exe_env_get_files_changed files_changed_map =
let cg_get_files_changed files_changed_map (source_dir, cg) =
let changed_procs =
if !incremental_mode = ANALYZE_ALL then Cg.get_defined_nodes cg
else cg_get_changed_procs source_dir cg in
if changed_procs <> [] then
let file_pname = ClusterMakefile.source_file_to_pname (Cg.get_source cg) in
Procname.Map.add file_pname (proc_list_to_set changed_procs) files_changed_map
else files_changed_map in
IList.fold_left cg_get_files_changed files_changed_map cg_list in
let exe_env = Exe_env.freeze _exe_env in
let files_changed =
if !incremental_mode = ANALYZE_ALL then Procname.Map.empty
else exe_env_get_files_changed Procname.Map.empty in
files_changed, exe_env
(** Create an exe_env from a cluster. *) (** Create an exe_env from a cluster. *)
let exe_env_from_cluster cluster = let exe_env_from_cluster cluster =
@ -875,7 +713,7 @@ let () =
RegisterCheckers.register (); RegisterCheckers.register ();
Facebook.register_checkers (); Facebook.register_checkers ();
if !allow_specs_cleanup = true && !incremental_mode = ANALYZE_ALL && !cluster_cmdline = None then if !allow_specs_cleanup = true && !cluster_cmdline = None then
DB.Results_dir.clean_specs_dir (); DB.Results_dir.clean_specs_dir ();
let analyzer_out_of, analyzer_err_of = setup_logging () in let analyzer_out_of, analyzer_err_of = setup_logging () in
@ -899,11 +737,11 @@ let () =
else else
begin begin
let _exe_env = Exe_env.create None in let _exe_env = Exe_env.create None in
let files_changed_map, exe_env = let exe_env =
compute_files_changed_map _exe_env source_dirs (compute_exclude_fun ()) in populate_exe_env _exe_env source_dirs in
L.err "Procedures defined in more than one file: %a@." L.err "Procedures defined in more than one file: %a@."
Procname.pp_set (Exe_env.get_procs_defined_in_several_files exe_env); Procname.pp_set (Exe_env.get_procs_defined_in_several_files exe_env);
compute_clusters exe_env files_changed_map compute_clusters exe_env
end in end in

@ -1,12 +0,0 @@
java_test(
name='incremental',
deps=[
'//infer/tests/codetoanalyze/java/incremental/file_unchanged:file_unchanged',
'//infer/tests/codetoanalyze/java/incremental/parent_changed:parent_changed',
'//infer/tests/codetoanalyze/java/incremental/child_changed:child_changed',
'//infer/tests/codetoanalyze/java/incremental/changed_only_mode:changed_only_mode',
],
visibility = [
'PUBLIC'
]
)

@ -1,42 +0,0 @@
v1_files = glob([ '**/*.java.v1'])
v2_files = glob(['**/*.java.v2'])
v3_files = glob(['**/*.java.v3'])
sources = v1_files + v2_files + v3_files
def copy_files_strip_suffix_cmd(sfx, files):
return ' && '.join([' '.join(['cp', f, f.replace(sfx, '')]) for f in files])
out = 'out'
clean_out = ' '.join(['rm', '-rf', out])
clean_java = ' '.join(['rm', '-rf', '*.java'])
clean_cmd = ' && '.join([clean_out, clean_java])
stripped_suffix_files = map(lambda f: f.replace('.v1', ''), v1_files)
to_compile = ' '.join(stripped_suffix_files)
infer_cmd = ' '.join([
'infer',
'--no-progress-bar',
'-i',
'--changed-only',
'--absolute-paths',
'-o', out,
'-a', 'infer',
'--',
'javac',
to_compile
])
v1_copy_cmd = copy_files_strip_suffix_cmd('.v1', v1_files)
v2_copy_cmd = copy_files_strip_suffix_cmd('.v2', v2_files)
v3_copy_cmd = copy_files_strip_suffix_cmd('.v3', v3_files)
stats_copy_cmd = ' '.join(['cp', out + '/stats.json', '$OUT'])
command = ' && '.join([clean_cmd, v1_copy_cmd, infer_cmd, v2_copy_cmd, infer_cmd, v3_copy_cmd, infer_cmd, stats_copy_cmd])
genrule(
name = 'analyze',
srcs = sources,
out = 'stats.json',
cmd = command,
deps = [],
visibility = [
'PUBLIC',
]
)

@ -1,18 +0,0 @@
/*
* Copyright (c) 2015 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package codetoanalyze.java.incremental.changed_only_future;
class Child {
Object bar() {
return new Object();
}
}

@ -1,18 +0,0 @@
/*
* Copyright (c) 2015 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package codetoanalyze.java.incremental.changed_only_future;
class Child {
Object bar() {
return null;
}
}

@ -1,23 +0,0 @@
/*
* Copyright (c) 2015 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package codetoanalyze.java.incremental.changed_only_future;
class Parent {
Object foo() {
Child c = new Child();
return c.bar();
}
void bar() {
Object o1 = new Object();
}
}

@ -1,27 +0,0 @@
/*
* Copyright (c) 2015 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package codetoanalyze.java.incremental.changed_only_future;
class Parent {
Object foo() {
Child c = new Child();
return c.bar();
}
void bar() {
Object o1 = new Object();
}
void baz() {
foo().toString(); // should be an NPE with Child.v2
}
}

@ -1,48 +0,0 @@
v1_files = glob([ '**/*.java.v1'])
v2_files = glob(['**/*.java.v2'])
normal_files = glob(['**/*.java'])
sources = v1_files + v2_files + normal_files
java_library(
name = 'changed_only_mode',
srcs = sources,
deps = [],
visibility = [
'PUBLIC'
]
)
def copy_files_strip_suffix_cmd(sfx, files):
return ' && '.join([' '.join(['cp', f, f.replace(sfx, '')]) for f in files])
out = 'out'
clean_cmd = ' '.join(['rm', '-rf', out])
stripped_suffix_files = map(lambda f: f.replace('.v1', ''), v1_files)
to_compile = ' '.join(set(normal_files + stripped_suffix_files))
infer_cmd = ' '.join([
'infer',
'--no-progress-bar',
'-i',
'--changed-only',
'--absolute-paths',
'-o', out,
'-a', 'infer',
'--',
'javac',
to_compile
])
v1_copy_cmd = copy_files_strip_suffix_cmd('.v1', v1_files)
v2_copy_cmd = copy_files_strip_suffix_cmd('.v2', v2_files)
stats_copy_cmd = ' '.join(['cp', out + '/stats.json', '$OUT'])
command = ' && '.join([clean_cmd, v1_copy_cmd, infer_cmd, v2_copy_cmd, infer_cmd, stats_copy_cmd])
genrule(
name = 'analyze',
srcs = sources,
out = 'stats.json',
cmd = command,
deps = [':changed_only_mode'],
visibility = [
'PUBLIC',
]
)

@ -1,22 +0,0 @@
/*
* Copyright (c) 2015 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package codetoanalyze.java.incremental.changed_only_mode;
class Child {
Object bar() {
return null;
}
Object dontReanalyze(Object o) {
return o;
}
}

@ -1,22 +0,0 @@
/*
* Copyright (c) 2015 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package codetoanalyze.java.incremental.changed_only_mode;
class Child {
Object bar() {
return new Object();
}
Object dontReanalyze(Object o) {
return o;
}
}

@ -1,22 +0,0 @@
/*
* Copyright (c) 2015 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package codetoanalyze.java.incremental.changed_only_mode;
class Child {
Object bar() {
return null;
}
Object dontReanalyze(Object o) {
return o;
}
}

@ -1,24 +0,0 @@
/*
* Copyright (c) 2015 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package codetoanalyze.java.incremental.changed_only_mode;
class Parent {
Object foo() {
Child c = new Child();
Object o = c.bar();
return o;
}
void dontReanalyze() {
Object o1 = new Object();
}
}

@ -1,47 +0,0 @@
v1_files = glob([ '**/*.java.v1'])
v2_files = glob(['**/*.java.v2'])
normal_files = glob(['**/*.java'])
sources = v1_files + v2_files + normal_files
java_library(
name = 'child_changed',
srcs = sources,
deps = [],
visibility = [
'PUBLIC'
]
)
def copy_files_strip_suffix_cmd(sfx, files):
return ' && '.join([' '.join(['cp', f, f.replace(sfx, '')]) for f in files])
out = 'out'
clean_cmd = ' '.join(['rm', '-rf', out])
stripped_suffix_files = map(lambda f: f.replace('.v1', ''), v1_files)
to_compile = ' '.join(normal_files + stripped_suffix_files)
infer_cmd = ' '.join([
'infer',
'--no-progress-bar',
'-i',
'--absolute-paths',
'-o', out,
'-a', 'infer',
'--',
'javac',
to_compile
])
v1_copy_cmd = copy_files_strip_suffix_cmd('.v1', v1_files)
v2_copy_cmd = copy_files_strip_suffix_cmd('.v2', v2_files)
stats_copy_cmd = ' '.join(['cp', out + '/stats.json', '$OUT'])
command = ' && '.join([clean_cmd, v1_copy_cmd, infer_cmd, v2_copy_cmd, infer_cmd, stats_copy_cmd])
genrule(
name = 'analyze',
srcs = sources,
out = 'stats.json',
cmd = command,
deps = [':child_changed'],
visibility = [
'PUBLIC',
]
)

@ -1,22 +0,0 @@
/*
* Copyright (c) 2015 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package codetoanalyze.java.incremental.child_changed;
class Child {
Object bar() {
return null;
}
Object dontReanalyze(Object o) {
return o;
}
}

@ -1,22 +0,0 @@
/*
* Copyright (c) 2015 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package codetoanalyze.java.incremental.child_changed;
class Child {
Object bar() {
return new Object();
}
Object dontReanalyze(Object o) {
return o;
}
}

@ -1,22 +0,0 @@
/*
* Copyright (c) 2015 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package codetoanalyze.java.incremental.child_changed;
class Child {
Object bar() {
return null;
}
Object dontReanalyze(Object o) {
return o;
}
}

@ -1,20 +0,0 @@
/*
* Copyright (c) 2015 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package codetoanalyze.java.incremental.child_changed;
class Grandparent {
void baz() {
Parent p = new Parent();
Object o = p.foo();
o.toString();
}
}

@ -1,24 +0,0 @@
/*
* Copyright (c) 2015 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package codetoanalyze.java.incremental.child_changed;
class Parent {
Object foo() {
Child c = new Child();
Object o = c.bar();
return o;
}
void dontReanalyze() {
Object o1 = new Object();
}
}

@ -1,36 +0,0 @@
sources = glob([ '**/*.java'])
java_library(
name = 'file_unchanged',
srcs = sources,
visibility = [
'PUBLIC'
]
)
out = 'out'
clean_cmd = ' '.join(['rm', '-rf', out])
infer_cmd = ' '.join([
'infer',
'--no-progress-bar',
'-i',
'--absolute-paths',
'-o', out,
'-a', 'infer',
'--',
'javac',
'$SRCS',
])
stats_copy_cmd = ' '.join(['cp', out + '/stats.json', '$OUT'])
command = ' && '.join([clean_cmd, infer_cmd, infer_cmd, stats_copy_cmd])
genrule(
name = 'analyze',
srcs = sources,
out = 'stats.json',
cmd = command,
deps = [':file_unchanged'],
visibility = [
'PUBLIC',
]
)

@ -1,17 +0,0 @@
/*
* Copyright (c) 2015 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package codetoanalyze.java.incremental.file_unchanged;
public class File {
Object o() {
return new Object();
}
}

@ -1,47 +0,0 @@
v1_files = glob([ '**/*.java.v1'])
v2_files = glob(['**/*.java.v2'])
normal_files = glob(['**/*.java'])
sources = v1_files + v2_files + normal_files
java_library(
name = 'parent_changed',
srcs = sources,
deps = [],
visibility = [
'PUBLIC'
]
)
def copy_files_strip_suffix_cmd(sfx, files):
return ' && '.join([' '.join(['cp', f, f.replace(sfx, '')]) for f in files])
out = 'out'
clean_cmd = ' '.join(['rm', '-rf', out])
stripped_suffix_files = map(lambda f: f.replace('.v1', ''), v1_files)
to_compile = ' '.join(normal_files + stripped_suffix_files)
infer_cmd = ' '.join([
'infer',
'--no-progress-bar',
'-i',
'--absolute-paths',
'-o', out,
'-a', 'infer',
'--',
'javac',
to_compile
])
v1_copy_cmd = copy_files_strip_suffix_cmd('.v1', v1_files)
v2_copy_cmd = copy_files_strip_suffix_cmd('.v2', v2_files)
stats_copy_cmd = ' '.join(['cp', out + '/stats.json', '$OUT'])
command = ' && '.join([clean_cmd, v1_copy_cmd, infer_cmd, v2_copy_cmd, infer_cmd, stats_copy_cmd])
genrule(
name = 'analyze',
srcs = sources,
out = 'stats.json',
cmd = command,
deps = [':parent_changed'],
visibility = [
'PUBLIC',
]
)

@ -1,17 +0,0 @@
/*
* Copyright (c) 2015 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package codetoanalyze.java.incremental.parent_changed;
class Child {
Object bar() {
return new Object();
}
}

@ -1,20 +0,0 @@
/*
* Copyright (c) 2015 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package codetoanalyze.java.incremental.parent_changed;
class Parent {
void foo() {
Child c = new Child();
Object o = c.bar();
o.toString();
}
}

@ -1,24 +0,0 @@
/*
* Copyright (c) 2015 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package codetoanalyze.java.incremental.parent_changed;
class Parent {
void foo() {
Child c = new Child();
Object o = c.bar();
o.toString();
}
void bar() {
Object o1 = new Object();
}
}

@ -10,7 +10,6 @@ tests_dependencies = [
'//infer/tests/codetoanalyze/java/checkers:checkers', '//infer/tests/codetoanalyze/java/checkers:checkers',
'//infer/tests/codetoanalyze/java/eradicate:eradicate', '//infer/tests/codetoanalyze/java/eradicate:eradicate',
'//infer/tests/codetoanalyze/java/infer:infer', '//infer/tests/codetoanalyze/java/infer:infer',
'//infer/tests/codetoanalyze/java/incremental:incremental',
'//infer/tests/codetoanalyze/java/tracing:tracing', '//infer/tests/codetoanalyze/java/tracing:tracing',
] ]
@ -98,7 +97,6 @@ java_test(
'//infer/tests/endtoend/java/harness:harness', '//infer/tests/endtoend/java/harness:harness',
'//infer/tests/endtoend/java/tracing:tracing', '//infer/tests/endtoend/java/tracing:tracing',
'//infer/tests/endtoend/java/comparison:comparison', '//infer/tests/endtoend/java/comparison:comparison',
'//infer/tests/endtoend/java/incremental:incremental',
], ],
visibility=integration_tests, visibility=integration_tests,
) )

@ -1,20 +0,0 @@
java_test(
name='incremental',
srcs=glob(['*.java']),
deps=[
'//dependencies/java/guava:guava',
'//dependencies/java/junit:hamcrest',
'//dependencies/java/junit:junit',
'//infer/tests/utils:utils',
],
resources=[
'//infer/tests/codetoanalyze/java/incremental/file_unchanged:analyze',
'//infer/tests/codetoanalyze/java/incremental/parent_changed:analyze',
'//infer/tests/codetoanalyze/java/incremental/child_changed:analyze',
'//infer/tests/codetoanalyze/java/incremental/changed_only_mode:analyze',
'//infer/tests/codetoanalyze/java/incremental/changed_only_future:analyze',
],
visibility=[
'PUBLIC',
],
)

@ -1,62 +0,0 @@
/*
* Copyright (c) 2015 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package endtoend.java.incremental;
import static org.hamcrest.MatcherAssert.assertThat;
import static utils.matchers.NumberOfFilesAnalyzed.numberOfFilesAnalyzed;
import static utils.matchers.NumberOfProceduresAnalyzed.numberOfProceduresAnalyzed;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Paths;
import java.nio.file.Path;
import java.io.*;
import utils.InferException;
import utils.InferStats;
/**
* Make sure the incremental --changed-only mode doesn't corrupt the results for future analyses by
* refusing to analyze callers of changed procedures (and thereby creating stale specs for some poor
* future analysis).
*/
public class ChangedOnlyFuture {
public static final String SOURCE_DIR =
"/infer/tests/codetoanalyze/java/incremental/changed_only_future/";
private static InferStats inferStats;
@BeforeClass
public static void loadResults() throws InterruptedException, IOException {
inferStats = InferStats.loadInferStats(ChangedOnlyModeTest.class, SOURCE_DIR);
}
@Test
public void onlyChangedFileReanalyzedInChangedOnlyMode()
throws IOException, InterruptedException, InferException {
assertThat(
"Only the changed file should be re-analyzed",
inferStats,
numberOfFilesAnalyzed(1));
}
@Test
public void changedAndStaleProcsReanalyze()
throws IOException, InterruptedException, InferException {
assertThat(
"Both the new (changed) procedure and its stale (unchanged) callee should be re-analyzed",
inferStats,
numberOfProceduresAnalyzed(2));
}
}

@ -1,57 +0,0 @@
/*
* Copyright (c) 2015 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package endtoend.java.incremental;
import static org.hamcrest.MatcherAssert.assertThat;
import static utils.matchers.NumberOfFilesAnalyzed.numberOfFilesAnalyzed;
import static utils.matchers.NumberOfProceduresAnalyzed.numberOfProceduresAnalyzed;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Paths;
import java.nio.file.Path;
import java.io.*;
import utils.InferException;
import utils.InferStats;
public class ChangedOnlyModeTest {
public static final String SOURCE_DIR =
"/infer/tests/codetoanalyze/java/incremental/changed_only_mode/";
private static InferStats inferStats;
@BeforeClass
public static void loadResults() throws InterruptedException, IOException {
inferStats = InferStats.loadInferStats(ChangedOnlyModeTest.class, SOURCE_DIR);
}
@Test
public void onlyChangedFileReanalyzedInChangedOnlyMode()
throws IOException, InterruptedException, InferException {
assertThat(
"After changing the child file, parent should not be re-analyzed in changed-only mode",
inferStats,
numberOfFilesAnalyzed(1));
}
@Test
public void onlyChangedProcsReanalyzedInIncrementalMode()
throws IOException, InterruptedException, InferException {
assertThat(
"After changing a procedure, its callers should not be re-analyzed in changed-only mode",
inferStats,
numberOfProceduresAnalyzed(1));
}
}

@ -1,57 +0,0 @@
/*
* Copyright (c) 2015 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package endtoend.java.incremental;
import static org.hamcrest.MatcherAssert.assertThat;
import static utils.matchers.NumberOfFilesAnalyzed.numberOfFilesAnalyzed;
import static utils.matchers.NumberOfProceduresAnalyzed.numberOfProceduresAnalyzed;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Paths;
import java.nio.file.Path;
import java.io.*;
import utils.InferException;
import utils.InferStats;
public class ChildChangedTest {
public static final String SOURCE_DIR =
"/infer/tests/codetoanalyze/java/incremental/child_changed/";
private static InferStats inferStats;
@BeforeClass
public static void loadResults() throws InterruptedException, IOException {
inferStats = InferStats.loadInferStats(ChildChangedTest.class, SOURCE_DIR);
}
@Test
public void changedFilesReanalyzedInIncrementalMode()
throws IOException, InterruptedException, InferException {
assertThat(
"After changing the child file, parent and grandparent should be re-analyzed",
inferStats,
numberOfFilesAnalyzed(3));
}
@Test
public void onlyChangedProcsReanalyzedInIncrementalMode()
throws IOException, InterruptedException, InferException {
assertThat(
"After changing a procedure, only the proc and its callers should be re-analyzed",
inferStats,
numberOfProceduresAnalyzed(3));
}
}

@ -1,57 +0,0 @@
/*
* Copyright (c) 2015 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package endtoend.java.incremental;
import static org.hamcrest.MatcherAssert.assertThat;
import static utils.matchers.NumberOfFilesAnalyzed.numberOfFilesAnalyzed;
import static utils.matchers.NumberOfProceduresAnalyzed.numberOfProceduresAnalyzed;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Paths;
import java.nio.file.Path;
import java.io.*;
import utils.InferException;
import utils.InferStats;
public class FileUnchangedTest {
public static final String SOURCE_DIR =
"/infer/tests/codetoanalyze/java/incremental/file_unchanged/";
private static InferStats inferStats;
@BeforeClass
public static void loadResults() throws InterruptedException, IOException {
inferStats = InferStats.loadInferStats(FileUnchangedTest.class, SOURCE_DIR);
}
@Test
public void unchangedFileNotReanalyzedInIncrementalMode()
throws IOException, InterruptedException, InferException {
assertThat(
"Unchanged file should not be re-analyzed in incremental mode",
inferStats,
numberOfFilesAnalyzed(0));
}
@Test
public void unchangedProcdureNotReanalyzedInIncrementalMode()
throws IOException, InterruptedException, InferException {
assertThat(
"Unchanged procedure should not be re-analyzed in incremental mode",
inferStats,
numberOfProceduresAnalyzed(0));
}
}

@ -1,61 +0,0 @@
/*
* Copyright (c) 2015 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package endtoend.java.incremental;
import static org.hamcrest.MatcherAssert.assertThat;
import static utils.matchers.NumberOfFilesAnalyzed.numberOfFilesAnalyzed;
import static utils.matchers.NumberOfProceduresAnalyzed.numberOfProceduresAnalyzed;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Paths;
import java.nio.file.Path;
import java.io.*;
import utils.InferException;
import utils.InferStats;
public class ParentChangedTest {
public static final String SOURCE_DIR =
"/infer/tests/codetoanalyze/java/incremental/parent_changed/";
private static InferStats inferStats;
@BeforeClass
public static void loadResults() throws InterruptedException, IOException {
inferStats = InferStats.loadInferStats(ParentChangedTest.class, SOURCE_DIR);
}
@Test
public void unchangedFileNotReanalyzedInIncrementalMode()
throws IOException, InterruptedException, InferException {
assertThat(
"After changing only the parent file, the child file should not be re-analyzed",
inferStats,
numberOfFilesAnalyzed(1));
assertThat(
"When adding a new procedure, the old ones should not be re-analyzed",
inferStats,
numberOfProceduresAnalyzed(1));
}
@Test
public void unchangedProcedureNotReanalyzedInIncrementalMode()
throws IOException, InterruptedException, InferException {
assertThat(
"When adding a new procedure, the old ones should not be re-analyzed",
inferStats,
numberOfProceduresAnalyzed(1));
}
}
Loading…
Cancel
Save