henitai 0.1.10 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +68 -0
- data/README.md +18 -4
- data/lib/henitai/cli.rb +81 -3
- data/lib/henitai/configuration.rb +24 -11
- data/lib/henitai/coverage_bootstrapper.rb +24 -24
- data/lib/henitai/execution_engine.rb +3 -9
- data/lib/henitai/git_diff_analyzer.rb +34 -0
- data/lib/henitai/integration/rspec_process_runner.rb +66 -13
- data/lib/henitai/integration.rb +386 -38
- data/lib/henitai/mutant/activator.rb +14 -2
- data/lib/henitai/mutant.rb +13 -2
- data/lib/henitai/mutant_generator.rb +21 -2
- data/lib/henitai/mutant_history_store.rb +7 -22
- data/lib/henitai/mutant_identity.rb +34 -0
- data/lib/henitai/parallel_execution_runner.rb +29 -11
- data/lib/henitai/process_wakeup.rb +49 -0
- data/lib/henitai/process_worker_runner.rb +434 -0
- data/lib/henitai/reporter.rb +76 -3
- data/lib/henitai/result.rb +39 -8
- data/lib/henitai/runner.rb +203 -14
- data/lib/henitai/scenario_execution_result.rb +16 -3
- data/lib/henitai/static_filter.rb +10 -3
- data/lib/henitai/survivor_activation_cache.rb +81 -0
- data/lib/henitai/survivor_loader.rb +140 -0
- data/lib/henitai/survivor_selector.rb +36 -0
- data/lib/henitai/survivor_test_filter.rb +72 -0
- data/lib/henitai/version.rb +1 -1
- data/lib/henitai.rb +8 -0
- data/sig/henitai.rbs +200 -9
- metadata +22 -1
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Henitai
|
|
6
|
+
# Reads a Stryker-compatible mutation report and extracts survivor data.
|
|
7
|
+
#
|
|
8
|
+
# Returns a +Report+ value object carrying:
|
|
9
|
+
# - +survivor_ids+ — stable IDs of survived mutants
|
|
10
|
+
# - +coverage_map+ — stableId → [test_files] from prior coveredBy data
|
|
11
|
+
# - +git_sha+ — git HEAD at the time the report was written (may be nil)
|
|
12
|
+
#
|
|
13
|
+
# Scope validation is intentionally shallow: checks schemaVersion presence
|
|
14
|
+
# and at least one file path overlap with config.includes.
|
|
15
|
+
class SurvivorLoader
|
|
16
|
+
# Value object returned by #load.
|
|
17
|
+
Report = Struct.new(:survivor_ids, :coverage_map, :git_sha)
|
|
18
|
+
|
|
19
|
+
class FileNotFoundError < StandardError; end
|
|
20
|
+
class InvalidReportError < StandardError; end
|
|
21
|
+
class ScopeMismatchError < StandardError; end
|
|
22
|
+
|
|
23
|
+
# @param path [String] path to a Stryker-compatible JSON report
|
|
24
|
+
# @param include_paths [Array<String>] from config.includes; used for scope validation
|
|
25
|
+
def initialize(path, include_paths: [])
|
|
26
|
+
@path = path
|
|
27
|
+
@include_paths = include_paths
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @return [Report]
|
|
31
|
+
def load
|
|
32
|
+
raw = read_file
|
|
33
|
+
report = parse_json(raw)
|
|
34
|
+
validate_scope(report)
|
|
35
|
+
build_report(report)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def build_report(report)
|
|
41
|
+
entries = known_entries(report)
|
|
42
|
+
Report.new(
|
|
43
|
+
survivor_ids: extract_survivor_ids(entries),
|
|
44
|
+
coverage_map: extract_coverage_map(entries),
|
|
45
|
+
git_sha: report["gitSha"]
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Returns mutant entries that have a stableId, warning about those that don't.
|
|
50
|
+
def known_entries(report)
|
|
51
|
+
all_mutants(report).select do |entry|
|
|
52
|
+
if entry["stableId"]
|
|
53
|
+
true
|
|
54
|
+
else
|
|
55
|
+
warn "henitai: survivor report entry missing stableId — skipping"
|
|
56
|
+
false
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def extract_survivor_ids(entries)
|
|
62
|
+
entries.filter_map { |e| e["stableId"] if e["status"] == "Survived" }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def extract_coverage_map(entries)
|
|
66
|
+
entries.each_with_object({}) do |entry, map|
|
|
67
|
+
covered = Array(entry["coveredBy"]).compact
|
|
68
|
+
map[entry["stableId"]] = covered unless covered.empty?
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def read_file
|
|
73
|
+
File.read(@path)
|
|
74
|
+
rescue Errno::ENOENT
|
|
75
|
+
raise FileNotFoundError, "Survivor report not found: #{@path}"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def parse_json(raw)
|
|
79
|
+
JSON.parse(raw)
|
|
80
|
+
rescue JSON::ParserError => e
|
|
81
|
+
raise InvalidReportError, "Invalid JSON in survivor report #{@path}: #{e.message}"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def validate_scope(report)
|
|
85
|
+
validate_schema_version!(report)
|
|
86
|
+
return if @include_paths.empty?
|
|
87
|
+
|
|
88
|
+
report_files = normalized_report_files(report)
|
|
89
|
+
include_dirs_raw = normalized_include_dirs_raw
|
|
90
|
+
include_dirs_abs = normalized_include_dirs_abs(include_dirs_raw)
|
|
91
|
+
|
|
92
|
+
return if any_report_file_overlaps?(report_files, include_dirs_raw, include_dirs_abs)
|
|
93
|
+
|
|
94
|
+
raise ScopeMismatchError,
|
|
95
|
+
"Survivor report #{@path} has no file overlap with configured includes — " \
|
|
96
|
+
"did you pass a report from a different project?"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def validate_schema_version!(report)
|
|
100
|
+
return if report.key?("schemaVersion")
|
|
101
|
+
|
|
102
|
+
raise InvalidReportError,
|
|
103
|
+
"Survivor report #{@path} is missing schemaVersion — is this a Henitai report?"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def normalized_report_files(report)
|
|
107
|
+
(report.fetch("files", {}) || {}).keys.map { |p| strip_trailing_slash(p.to_s) }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def normalized_include_dirs_raw
|
|
111
|
+
@include_paths.map { |p| strip_trailing_slash(p.to_s) }.uniq
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def normalized_include_dirs_abs(dirs_raw)
|
|
115
|
+
dirs_raw.map { |p| File.expand_path(p) }.uniq
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def strip_trailing_slash(path)
|
|
119
|
+
path.sub(%r{/\z}, "")
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def any_report_file_overlaps?(report_files, include_dirs_raw, include_dirs_abs)
|
|
123
|
+
report_files.any? do |file|
|
|
124
|
+
include_dirs_raw.any? { |inc| path_prefix_match?(file, inc) } ||
|
|
125
|
+
include_dirs_abs.any? { |inc_abs| path_prefix_match?(File.expand_path(file), inc_abs) }
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def path_prefix_match?(path, dir)
|
|
130
|
+
return false if path.empty? || dir.empty?
|
|
131
|
+
|
|
132
|
+
path == dir || path.start_with?(dir + File::SEPARATOR)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def all_mutants(report)
|
|
136
|
+
files = report.fetch("files", {}) || {}
|
|
137
|
+
files.values.compact.flat_map { |file_data| file_data.fetch("mutants", []) }
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Henitai
|
|
4
|
+
# Filters a mutant list to those that match a set of prior survivor stable IDs.
|
|
5
|
+
#
|
|
6
|
+
# After calling #select, #unmatched_ids reports which survivor IDs had no
|
|
7
|
+
# corresponding mutant in the current generation. A high unmatched ratio
|
|
8
|
+
# indicates that the source has drifted and a full run is recommended.
|
|
9
|
+
class SurvivorSelector
|
|
10
|
+
DRIFT_THRESHOLD = 0.5
|
|
11
|
+
class SelectionError < StandardError; end
|
|
12
|
+
|
|
13
|
+
def initialize(survivor_ids:)
|
|
14
|
+
@survivor_ids = survivor_ids.to_set
|
|
15
|
+
@unmatched_ids = nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def select(mutants)
|
|
19
|
+
current_index = mutants.to_h { |m| [m.stable_id, m] }
|
|
20
|
+
matched_ids, @unmatched_ids = @survivor_ids.partition { |id| current_index.key?(id) }
|
|
21
|
+
matched_ids.filter_map { |id| current_index[id] }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def unmatched_ids
|
|
25
|
+
raise SelectionError, "Call #select before accessing #unmatched_ids" if @unmatched_ids.nil?
|
|
26
|
+
|
|
27
|
+
@unmatched_ids
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def drift_warning?
|
|
31
|
+
return false if @survivor_ids.empty?
|
|
32
|
+
|
|
33
|
+
unmatched_ids.size.to_f / @survivor_ids.size > DRIFT_THRESHOLD
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Henitai
|
|
4
|
+
# Splits a matched survivor set into stable and pending subsets by consulting
|
|
5
|
+
# a git diff against the covering tests from the prior report.
|
|
6
|
+
#
|
|
7
|
+
# A survivor is **stable** (can skip re-execution) when:
|
|
8
|
+
# - it has covering test data in the prior report, AND
|
|
9
|
+
# - none of those test files appear in the diff between the prior run's
|
|
10
|
+
# git_sha and the current HEAD.
|
|
11
|
+
#
|
|
12
|
+
# A survivor is **pending** (must execute) when:
|
|
13
|
+
# - git_sha is nil (no anchor → conservative), OR
|
|
14
|
+
# - its coveredBy data is absent or empty, OR
|
|
15
|
+
# - at least one covering test file changed.
|
|
16
|
+
#
|
|
17
|
+
# On any git error the filter conservatively treats all survivors as pending.
|
|
18
|
+
class SurvivorTestFilter
|
|
19
|
+
# @param coverage_map [Hash<String, Array<String>>] stableId → [test_files]
|
|
20
|
+
# @param git_sha [String, nil] git SHA from the prior report
|
|
21
|
+
# @param dirty_source_files [Boolean] true when the current worktree has
|
|
22
|
+
# dirty source files and the survivor shortcut must be disabled
|
|
23
|
+
# @param worktree_changed_files [Array<String>] dirty tracked/untracked files
|
|
24
|
+
# @param diff_analyzer [GitDiffAnalyzer]
|
|
25
|
+
def initialize(
|
|
26
|
+
coverage_map:,
|
|
27
|
+
git_sha:,
|
|
28
|
+
dirty_source_files: false,
|
|
29
|
+
worktree_changed_files: [],
|
|
30
|
+
diff_analyzer: GitDiffAnalyzer.new
|
|
31
|
+
)
|
|
32
|
+
@coverage_map = coverage_map
|
|
33
|
+
@git_sha = git_sha
|
|
34
|
+
@dirty_source_files = dirty_source_files
|
|
35
|
+
@worktree_changed_files = Array(worktree_changed_files)
|
|
36
|
+
@diff_analyzer = diff_analyzer
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @param mutants [Array<Mutant>]
|
|
40
|
+
# @return [Hash<Symbol, Array<Mutant>>] { stable: [...], pending: [...] }
|
|
41
|
+
def apply(mutants)
|
|
42
|
+
return { stable: [], pending: mutants } if @dirty_source_files
|
|
43
|
+
return { stable: [], pending: mutants } if @git_sha.nil?
|
|
44
|
+
|
|
45
|
+
changed = changed_test_files
|
|
46
|
+
return { stable: [], pending: mutants } if changed.nil?
|
|
47
|
+
|
|
48
|
+
mutants.each_with_object({ stable: [], pending: [] }) do |mutant, result|
|
|
49
|
+
bucket = stable_survivor?(mutant, changed) ? :stable : :pending
|
|
50
|
+
result[bucket] << mutant
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def stable_survivor?(mutant, changed)
|
|
57
|
+
covering = @coverage_map[mutant.stable_id]
|
|
58
|
+
return false if covering.nil? || covering.empty?
|
|
59
|
+
|
|
60
|
+
covering.none? { |test_file| changed.include?(test_file) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Returns a Set of changed test file paths, or nil on any git error
|
|
64
|
+
# (nil triggers conservative fallback in #apply — all survivors pending).
|
|
65
|
+
def changed_test_files
|
|
66
|
+
committed = @diff_analyzer.changed_files(from: @git_sha, to: "HEAD")
|
|
67
|
+
(committed + @worktree_changed_files).to_set
|
|
68
|
+
rescue StandardError
|
|
69
|
+
nil
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
data/lib/henitai/version.rb
CHANGED
data/lib/henitai.rb
CHANGED
|
@@ -26,6 +26,7 @@ module Henitai
|
|
|
26
26
|
autoload :PerTestCoverageSelector, "henitai/per_test_coverage_selector"
|
|
27
27
|
autoload :Subject, "henitai/subject"
|
|
28
28
|
autoload :Mutant, "henitai/mutant"
|
|
29
|
+
autoload :MutantIdentity, "henitai/mutant_identity"
|
|
29
30
|
autoload :Operator, "henitai/operator"
|
|
30
31
|
autoload :Operators, "henitai/operators"
|
|
31
32
|
autoload :SourceParser, "henitai/source_parser"
|
|
@@ -39,6 +40,10 @@ module Henitai
|
|
|
39
40
|
autoload :EquivalenceDetector, "henitai/equivalence_detector"
|
|
40
41
|
autoload :StaticFilter, "henitai/static_filter"
|
|
41
42
|
autoload :StillbornFilter, "henitai/stillborn_filter"
|
|
43
|
+
autoload :SurvivorLoader, "henitai/survivor_loader"
|
|
44
|
+
autoload :SurvivorSelector, "henitai/survivor_selector"
|
|
45
|
+
autoload :SurvivorTestFilter, "henitai/survivor_test_filter"
|
|
46
|
+
autoload :SurvivorActivationCache, "henitai/survivor_activation_cache"
|
|
42
47
|
autoload :ScenarioExecutionResult, "henitai/scenario_execution_result"
|
|
43
48
|
autoload :CoverageFormatter, "henitai/coverage_formatter"
|
|
44
49
|
autoload :MinitestCoverageReporter, "henitai/minitest_coverage_reporter"
|
|
@@ -47,6 +52,9 @@ module Henitai
|
|
|
47
52
|
autoload :SamplingStrategy, "henitai/sampling_strategy"
|
|
48
53
|
autoload :TestPrioritizer, "henitai/test_prioritizer"
|
|
49
54
|
autoload :ExecutionEngine, "henitai/execution_engine"
|
|
55
|
+
autoload :ParallelExecutionRunner, "henitai/parallel_execution_runner"
|
|
56
|
+
autoload :ProcessWorkerRunner, "henitai/process_worker_runner"
|
|
57
|
+
autoload :ProcessWakeup, "henitai/process_wakeup"
|
|
50
58
|
autoload :Runner, "henitai/runner"
|
|
51
59
|
autoload :Reporter, "henitai/reporter"
|
|
52
60
|
autoload :Integration, "henitai/integration"
|
data/sig/henitai.rbs
CHANGED
|
@@ -73,6 +73,38 @@ module Henitai
|
|
|
73
73
|
module Operators
|
|
74
74
|
end
|
|
75
75
|
|
|
76
|
+
module Integration::ChildDebugSupport
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def run_rspec_runner: (Array[String]) -> untyped
|
|
80
|
+
def build_rspec_runner: () -> untyped
|
|
81
|
+
def configure_rspec_runner: (untyped) -> void
|
|
82
|
+
def load_rspec_spec_files: (Array[String]) -> void
|
|
83
|
+
def run_rspec_specs: (untyped) -> untyped
|
|
84
|
+
def debug_child_timeout_dump: (Integer) -> void
|
|
85
|
+
def install_debug_timeout_trap: () -> void
|
|
86
|
+
def debug_child_thread_dump: (String) -> void
|
|
87
|
+
def debug_child_puts: (String) -> void
|
|
88
|
+
def debug_child?: () -> bool
|
|
89
|
+
def debug_child_rspec_trace: (
|
|
90
|
+
test_files: Array[String],
|
|
91
|
+
rspec_options: Array[String],
|
|
92
|
+
rspec_argv: Array[String]
|
|
93
|
+
) -> void
|
|
94
|
+
def debug_child_rspec_exit: (untyped) -> void
|
|
95
|
+
def debug_child_example_count: (String) -> void
|
|
96
|
+
def debug_child_activation_start: (String) -> void
|
|
97
|
+
def debug_child_activation_end: (untyped, test_files: Array[String]) -> void
|
|
98
|
+
def debug_child_mutant_meta: (Mutant) -> void
|
|
99
|
+
def debug_child_activation_check: () -> void
|
|
100
|
+
def suppress_simplecov!: () -> void
|
|
101
|
+
def suppress_coverage!: () -> void
|
|
102
|
+
def loaded_feature_map: (Array[String]) -> Array[[String, bool]]
|
|
103
|
+
def loaded_feature?: (String) -> bool
|
|
104
|
+
def rspec_world_example_count: () -> Integer?
|
|
105
|
+
def pause: (Float) -> void
|
|
106
|
+
end
|
|
107
|
+
|
|
76
108
|
class Configuration
|
|
77
109
|
DEFAULT_TIMEOUT: Float
|
|
78
110
|
DEFAULT_OPERATORS: Symbol
|
|
@@ -106,11 +138,14 @@ module Henitai
|
|
|
106
138
|
private
|
|
107
139
|
|
|
108
140
|
def load_raw_configuration: (String) -> Hash[Symbol, untyped]
|
|
141
|
+
def detect_integration: () -> String
|
|
109
142
|
def apply_defaults: (Hash[Symbol, untyped]) -> void
|
|
110
143
|
def apply_general_defaults: (Hash[Symbol, untyped]) -> void
|
|
111
144
|
def apply_mutation_defaults: (Hash[Symbol, untyped]) -> void
|
|
112
145
|
def apply_analysis_defaults: (Hash[Symbol, untyped]) -> void
|
|
113
146
|
def merge_defaults: (Hash[Symbol, untyped], Hash[Symbol, untyped]?) -> Hash[Symbol, untyped]
|
|
147
|
+
def resolve_integration_default: (untyped) -> untyped
|
|
148
|
+
def default_dashboard: (Hash[Symbol, untyped]?) -> Hash[Symbol, untyped]
|
|
114
149
|
def symbolize_keys: (untyped) -> untyped
|
|
115
150
|
end
|
|
116
151
|
|
|
@@ -171,8 +206,11 @@ module Henitai
|
|
|
171
206
|
attr_accessor duration: Float?
|
|
172
207
|
attr_accessor covered_by: Array[String]?
|
|
173
208
|
attr_accessor tests_completed: Integer?
|
|
209
|
+
attr_reader precomputed_stable_id: String?
|
|
210
|
+
attr_reader precomputed_activation_source: String?
|
|
174
211
|
|
|
175
|
-
def initialize: (subject: Subject, operator: String, nodes: Hash[Symbol, untyped], description: String, location: Hash[Symbol, untyped]) -> void
|
|
212
|
+
def initialize: (subject: Subject, operator: String, nodes: Hash[Symbol, untyped], description: String, location: Hash[Symbol, untyped], ?precomputed_stable_id: String?, ?precomputed_activation_source: String?) -> void
|
|
213
|
+
def stable_id: () -> String
|
|
176
214
|
def killed?: () -> bool
|
|
177
215
|
def survived?: () -> bool
|
|
178
216
|
def pending?: () -> bool
|
|
@@ -256,9 +294,12 @@ module Henitai
|
|
|
256
294
|
def self.for: (String) -> untyped
|
|
257
295
|
|
|
258
296
|
class Base
|
|
297
|
+
include ChildDebugSupport
|
|
298
|
+
|
|
259
299
|
def select_tests: (Subject) -> Array[String]
|
|
260
300
|
def test_files: () -> Array[String]
|
|
261
301
|
def run_mutant: (mutant: Mutant, test_files: Array[String], timeout: Float) -> ScenarioExecutionResult
|
|
302
|
+
def spawn_mutant: (mutant: Mutant, test_files: Array[String]) -> ChildHandle
|
|
262
303
|
def run_suite: (Array[String], ?timeout: Float) -> ScenarioExecutionResult
|
|
263
304
|
def per_test_coverage_supported?: () -> bool
|
|
264
305
|
def wait_with_timeout: (Integer, Float) -> untyped
|
|
@@ -268,9 +309,26 @@ module Henitai
|
|
|
268
309
|
private
|
|
269
310
|
|
|
270
311
|
def pause: (Float) -> void
|
|
312
|
+
def wait_nonblocking: (Integer) -> untyped
|
|
271
313
|
def handle_timeout: (Integer) -> Symbol
|
|
272
314
|
def cleanup_child_process: (Integer) -> void
|
|
273
|
-
def
|
|
315
|
+
def suppress_simplecov!: () -> void
|
|
316
|
+
def suppress_coverage!: () -> void
|
|
317
|
+
def run_tests: (Array[String]) -> Integer
|
|
318
|
+
def debug_child?: () -> bool
|
|
319
|
+
def debug_child_puts: (String) -> void
|
|
320
|
+
def debug_child_rspec_trace: (test_files: Array[String], rspec_options: Array[String], rspec_argv: Array[String]) -> void
|
|
321
|
+
def debug_child_rspec_exit: (untyped status) -> void
|
|
322
|
+
def debug_child_activation_start: (String mutant_id) -> void
|
|
323
|
+
def debug_child_activation_end: (untyped activation_result, test_files: Array[String]) -> void
|
|
324
|
+
def debug_child_mutant_meta: (Mutant) -> void
|
|
325
|
+
def debug_child_activation_check: () -> void
|
|
326
|
+
def debug_child_example_count: (String) -> void
|
|
327
|
+
def loaded_feature_map: (Array[String]) -> Array[[String, bool]]
|
|
328
|
+
def loaded_feature?: (String) -> bool
|
|
329
|
+
def rspec_world_example_count: () -> Integer?
|
|
330
|
+
def run_child_activation_and_tests: (mutant: Mutant, test_files: Array[String], log_paths: Hash[Symbol, String]) -> Integer
|
|
331
|
+
def with_non_interactive_stdin: () { () -> untyped } -> untyped
|
|
274
332
|
def subprocess_env: () -> Hash[String, String]
|
|
275
333
|
def scenario_log_support: () -> ScenarioLogSupport
|
|
276
334
|
def with_subprocess_env: () { () -> untyped } -> untyped
|
|
@@ -300,9 +358,25 @@ module Henitai
|
|
|
300
358
|
def stderr_stream: () -> IO
|
|
301
359
|
end
|
|
302
360
|
|
|
361
|
+
module SchedulerDiagnostics
|
|
362
|
+
def self.enabled?: () -> bool
|
|
363
|
+
def self.child_started: (Integer?) -> void
|
|
364
|
+
def self.child_ended: (Integer?) -> void
|
|
365
|
+
def self.summary: () -> Hash[Symbol, untyped]
|
|
366
|
+
def self.reset!: () -> void
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
class ChildHandle
|
|
370
|
+
attr_accessor pid: Integer?
|
|
371
|
+
attr_accessor log_paths: Hash[Symbol, String]
|
|
372
|
+
|
|
373
|
+
def initialize: (pid: Integer?, log_paths: Hash[Symbol, String]) -> void
|
|
374
|
+
end
|
|
375
|
+
|
|
303
376
|
class RspecProcessRunner
|
|
304
377
|
def run_mutant: (Rspec, mutant: Mutant, test_files: Array[String], timeout: Float) -> ScenarioExecutionResult
|
|
305
378
|
def run_suite: (Rspec, Array[String], timeout: Float) -> ScenarioExecutionResult
|
|
379
|
+
def spawn_mutant: (Rspec, mutant: Mutant, test_files: Array[String], log_paths: Hash[Symbol, String]) -> ChildHandle
|
|
306
380
|
end
|
|
307
381
|
|
|
308
382
|
class Rspec < Base
|
|
@@ -312,13 +386,21 @@ module Henitai
|
|
|
312
386
|
def select_tests: (Subject) -> Array[String]
|
|
313
387
|
def test_files: () -> Array[String]
|
|
314
388
|
def run_mutant: (mutant: Mutant, test_files: Array[String], timeout: Float) -> ScenarioExecutionResult
|
|
389
|
+
def spawn_mutant: (mutant: Mutant, test_files: Array[String]) -> ChildHandle
|
|
315
390
|
def run_suite: (Array[String], ?timeout: Float) -> ScenarioExecutionResult
|
|
316
391
|
def per_test_coverage_supported?: () -> bool
|
|
317
392
|
|
|
318
393
|
private
|
|
319
394
|
|
|
395
|
+
def mutant_log_name: (Mutant) -> String
|
|
320
396
|
def run_in_child: (mutant: Mutant, test_files: Array[String], log_paths: Hash[Symbol, String]) -> Integer
|
|
397
|
+
def suppress_simplecov!: () -> void
|
|
398
|
+
def suppress_coverage!: () -> void
|
|
399
|
+
def install_debug_timeout_trap: () -> void
|
|
400
|
+
def debug_child_thread_dump: (String) -> void
|
|
401
|
+
def debug_child_example_count: (String) -> void
|
|
321
402
|
def suite_command: (Array[String]) -> Array[String]
|
|
403
|
+
def rspec_suite_runner_script: () -> String
|
|
322
404
|
def build_result: (untyped, Hash[Symbol, String]) -> ScenarioExecutionResult
|
|
323
405
|
def scenario_status: (untyped) -> Symbol
|
|
324
406
|
def exit_status_for: (untyped) -> Integer?
|
|
@@ -339,17 +421,29 @@ module Henitai
|
|
|
339
421
|
def requires_source_file_transitively?: (String, String, ?Array[String]) -> bool
|
|
340
422
|
def required_files: (String) -> Array[String]
|
|
341
423
|
def resolve_required_file: (String, String, String) -> String?
|
|
424
|
+
def loaded_feature_map: (Array[String]) -> Array[[String, bool]]
|
|
425
|
+
def loaded_feature?: (String) -> bool
|
|
342
426
|
def relative_candidates: (String, String) -> Array[String]
|
|
343
427
|
def require_candidates: (String, String) -> Array[String]
|
|
344
428
|
def expand_candidates: (String, String) -> Array[String]
|
|
345
429
|
end
|
|
346
430
|
|
|
431
|
+
module CoverageRuntimeSuppressors
|
|
432
|
+
def self.suppress_simplecov!: () -> void
|
|
433
|
+
def self.suppress_coverage!: () -> void
|
|
434
|
+
end
|
|
435
|
+
|
|
347
436
|
module SimpleCovStartSuppressor
|
|
348
437
|
def start: (*untyped) -> nil
|
|
349
438
|
end
|
|
350
439
|
|
|
440
|
+
module CoverageStartSuppressor
|
|
441
|
+
def start: (*untyped) -> nil
|
|
442
|
+
end
|
|
443
|
+
|
|
351
444
|
class Minitest < Rspec
|
|
352
445
|
def run_mutant: (mutant: Mutant, test_files: Array[String], timeout: Float) -> ScenarioExecutionResult
|
|
446
|
+
def spawn_mutant: (mutant: Mutant, test_files: Array[String]) -> ChildHandle
|
|
353
447
|
def run_suite: (Array[String], ?timeout: Float) -> ScenarioExecutionResult
|
|
354
448
|
|
|
355
449
|
private
|
|
@@ -358,6 +452,7 @@ module Henitai
|
|
|
358
452
|
def run_tests: (Array[String]) -> Integer
|
|
359
453
|
def preload_environment: () -> void
|
|
360
454
|
def setup_load_path: () -> void
|
|
455
|
+
def suppress_minitest_autorun!: () -> nil
|
|
361
456
|
def suppress_simplecov!: () -> void
|
|
362
457
|
def subprocess_env: () -> Hash[String, String]
|
|
363
458
|
def cleanup_suite_process: (Integer?, untyped) -> void
|
|
@@ -372,15 +467,21 @@ module Henitai
|
|
|
372
467
|
|
|
373
468
|
class GitDiffAnalyzer
|
|
374
469
|
def changed_files: (from: String, to: String, ?dir: String) -> Array[String]
|
|
470
|
+
def working_tree_changed_files: (?dir: String) -> Array[String]
|
|
471
|
+
def head_sha: (?dir: String) -> String?
|
|
375
472
|
end
|
|
376
473
|
|
|
377
474
|
class MutantGenerator
|
|
378
475
|
def generate: (Array[Subject], Array[untyped], ?config: untyped) -> Array[Mutant]
|
|
379
476
|
end
|
|
380
477
|
|
|
478
|
+
module MutantIdentity
|
|
479
|
+
def self.stable_id: (Mutant) -> String
|
|
480
|
+
end
|
|
481
|
+
|
|
381
482
|
class MutantHistoryStore
|
|
382
483
|
def initialize: (path: String) -> void
|
|
383
|
-
def record: (
|
|
484
|
+
def record: (untyped, version: String, ?recorded_at: Time) -> void
|
|
384
485
|
def trend_report: () -> Hash[Symbol, untyped]
|
|
385
486
|
end
|
|
386
487
|
|
|
@@ -420,6 +521,16 @@ module Henitai
|
|
|
420
521
|
def run: (Array[Mutant], untyped, untyped, untyped, ?Hash[Symbol, untyped]) -> void
|
|
421
522
|
end
|
|
422
523
|
|
|
524
|
+
class ProcessWorkerRunner
|
|
525
|
+
SCHEDULER_POLL_INTERVAL: Float
|
|
526
|
+
PROCESS_DRAIN_WINDOW: Float
|
|
527
|
+
|
|
528
|
+
Slot: untyped
|
|
529
|
+
|
|
530
|
+
def initialize: (worker_count: Integer) -> void
|
|
531
|
+
def run: (Array[Mutant], untyped, untyped, untyped?, ?Hash[Symbol, untyped]) -> Array[ScenarioExecutionResult]
|
|
532
|
+
end
|
|
533
|
+
|
|
423
534
|
class StaticFilter
|
|
424
535
|
def initialize: (?coverage_report_reader: CoverageReportReader) -> void
|
|
425
536
|
def apply: (Array[Mutant], untyped) -> Array[Mutant]
|
|
@@ -441,9 +552,7 @@ module Henitai
|
|
|
441
552
|
def parallel_execution?: (untyped, Array[Mutant]) -> bool
|
|
442
553
|
def worker_count: (untyped) -> Integer
|
|
443
554
|
def run_linear: (Array[Mutant], untyped, untyped, untyped?, untyped) -> void
|
|
444
|
-
def run_parallel: (Array[Mutant], untyped, untyped, untyped
|
|
445
|
-
def pipe_stdin?: () -> bool
|
|
446
|
-
def start_stdin_watcher: () { () -> void } -> Thread
|
|
555
|
+
def run_parallel: (Array[Mutant], untyped, untyped, untyped?) -> void
|
|
447
556
|
def process_mutant: (Mutant, untyped, untyped, untyped?, ?untyped) -> void
|
|
448
557
|
def prioritized_tests_for: (Mutant, untyped, untyped) -> Array[String]
|
|
449
558
|
def test_prioritizer: () -> TestPrioritizer
|
|
@@ -457,6 +566,7 @@ module Henitai
|
|
|
457
566
|
SERIALIZER_METHODS: Hash[Symbol, Symbol]
|
|
458
567
|
|
|
459
568
|
def self.activate!: (untyped) -> Symbol?
|
|
569
|
+
def self.activation_source_for: (Mutant) -> String?
|
|
460
570
|
def initialize: () -> void
|
|
461
571
|
def activate!: (untyped) -> Symbol?
|
|
462
572
|
|
|
@@ -512,7 +622,10 @@ module Henitai
|
|
|
512
622
|
private
|
|
513
623
|
|
|
514
624
|
def report_lines: (Result) -> Array[String]
|
|
515
|
-
def summary_lines: (
|
|
625
|
+
def summary_lines: (untyped) -> Array[String]
|
|
626
|
+
def full_summary_lines: (untyped) -> Array[String]
|
|
627
|
+
def partial_summary_lines: (untyped) -> Array[String]
|
|
628
|
+
def append_survivor_stats: (Array[String], untyped) -> void
|
|
516
629
|
def survived_detail_lines: (Result) -> Array[String]
|
|
517
630
|
def survived_mutant_lines: (Mutant) -> Array[String]
|
|
518
631
|
def survived_mutant_header: (Mutant) -> String
|
|
@@ -536,6 +649,13 @@ module Henitai
|
|
|
536
649
|
|
|
537
650
|
private
|
|
538
651
|
|
|
652
|
+
def write_canonical: (Hash[Symbol, untyped]) -> void
|
|
653
|
+
def write_session_snapshot: (Hash[Symbol, untyped]) -> void
|
|
654
|
+
def write_activation_recipes: (untyped) -> void
|
|
655
|
+
def survived_mutants_for: (untyped) -> Array[Mutant]
|
|
656
|
+
def session_snapshot_path: (String) -> String
|
|
657
|
+
def session_recipe_path: (String) -> String
|
|
658
|
+
def canonical_path: () -> String
|
|
539
659
|
def report_path: () -> String
|
|
540
660
|
def write_history_report: () -> void
|
|
541
661
|
def history_store_path: () -> String
|
|
@@ -595,8 +715,12 @@ module Henitai
|
|
|
595
715
|
attr_reader started_at: Time
|
|
596
716
|
attr_reader finished_at: Time
|
|
597
717
|
attr_reader thresholds: Hash[Symbol, Integer]
|
|
718
|
+
attr_reader survivor_stats: Hash[Symbol, untyped]?
|
|
719
|
+
attr_reader session_id: String
|
|
720
|
+
attr_reader git_sha: String?
|
|
598
721
|
|
|
599
|
-
def initialize: (mutants: Array[Mutant], started_at: Time, finished_at: Time, ?thresholds: Hash[Symbol, Integer]?) -> void
|
|
722
|
+
def initialize: (mutants: Array[Mutant], started_at: Time, finished_at: Time, ?thresholds: Hash[Symbol, Integer]?, ?partial_rerun: bool, ?survivor_stats: Hash[Symbol, untyped]?, ?session_id: String, ?git_sha: String?) -> void
|
|
723
|
+
def partial_rerun?: () -> bool
|
|
600
724
|
def killed: () -> Integer
|
|
601
725
|
def survived: () -> Integer
|
|
602
726
|
def equivalent: () -> Integer
|
|
@@ -609,6 +733,8 @@ module Henitai
|
|
|
609
733
|
|
|
610
734
|
private
|
|
611
735
|
|
|
736
|
+
def base_schema: () -> Hash[Symbol, untyped]
|
|
737
|
+
def unmatched_survivor_ids: () -> Array[String]
|
|
612
738
|
def build_files_section: () -> Hash[Symbol, untyped]
|
|
613
739
|
def mutant_to_schema: (Mutant) -> Hash[Symbol, untyped]
|
|
614
740
|
def coverage_schema: (Mutant) -> Hash[Symbol, untyped]
|
|
@@ -620,11 +746,56 @@ module Henitai
|
|
|
620
746
|
def stryker_status: (Symbol) -> String
|
|
621
747
|
end
|
|
622
748
|
|
|
749
|
+
class SurvivorLoader
|
|
750
|
+
FileNotFoundError: singleton(StandardError)
|
|
751
|
+
InvalidReportError: singleton(StandardError)
|
|
752
|
+
ScopeMismatchError: singleton(StandardError)
|
|
753
|
+
|
|
754
|
+
class Report
|
|
755
|
+
attr_reader survivor_ids: Array[String]
|
|
756
|
+
attr_reader coverage_map: Hash[String, Array[String]]
|
|
757
|
+
attr_reader git_sha: String?
|
|
758
|
+
|
|
759
|
+
def initialize: (survivor_ids: Array[String], coverage_map: Hash[String, Array[String]], git_sha: String?) -> void
|
|
760
|
+
end
|
|
761
|
+
|
|
762
|
+
def initialize: (String, ?include_paths: Array[String]) -> void
|
|
763
|
+
def load: () -> Report
|
|
764
|
+
end
|
|
765
|
+
|
|
766
|
+
class SurvivorTestFilter
|
|
767
|
+
def initialize: (
|
|
768
|
+
coverage_map: Hash[String, Array[String]],
|
|
769
|
+
git_sha: String?,
|
|
770
|
+
?dirty_source_files: bool,
|
|
771
|
+
?worktree_changed_files: Array[String],
|
|
772
|
+
?diff_analyzer: GitDiffAnalyzer
|
|
773
|
+
) -> void
|
|
774
|
+
def apply: (Array[Mutant]) -> Hash[Symbol, Array[Mutant]]
|
|
775
|
+
end
|
|
776
|
+
|
|
777
|
+
class SurvivorSelector
|
|
778
|
+
DRIFT_THRESHOLD: Float
|
|
779
|
+
|
|
780
|
+
def initialize: (survivor_ids: Array[String]) -> void
|
|
781
|
+
def select: (Array[Mutant]) -> Array[Mutant]
|
|
782
|
+
def unmatched_ids: () -> Array[String]
|
|
783
|
+
def drift_warning?: () -> bool
|
|
784
|
+
end
|
|
785
|
+
|
|
786
|
+
class SurvivorActivationCache
|
|
787
|
+
FILENAME: String
|
|
788
|
+
|
|
789
|
+
def self.compute: (Array[Mutant]) -> Hash[String, Hash[String, untyped]]
|
|
790
|
+
def self.load: (String) -> Hash[String, Hash[String, untyped]]?
|
|
791
|
+
def self.write: (String, Hash[String, Hash[String, untyped]]) -> void
|
|
792
|
+
end
|
|
793
|
+
|
|
623
794
|
class Runner
|
|
624
795
|
attr_reader config: Configuration
|
|
625
796
|
attr_reader result: untyped
|
|
626
797
|
|
|
627
|
-
def initialize: (?config: Configuration, ?subjects: Array[Subject], ?since: String) -> void
|
|
798
|
+
def initialize: (?config: Configuration, ?subjects: Array[Subject], ?since: String, ?survivors_from: String?) -> void
|
|
628
799
|
def run: () -> Result
|
|
629
800
|
def resolve_subjects: (?Array[String]) -> untyped
|
|
630
801
|
def generate_mutants: (untyped) -> untyped
|
|
@@ -656,6 +827,26 @@ module Henitai
|
|
|
656
827
|
def mutants_for: (Array[Subject], Array[String]) -> Array[Mutant]
|
|
657
828
|
def with_reports_dir: () { () -> untyped } -> untyped
|
|
658
829
|
def result_thresholds: () -> Hash[Symbol, Integer]?
|
|
830
|
+
def survivor_rerun?: () -> bool
|
|
831
|
+
def apply_survivor_selection: (Array[Mutant]) -> Array[Mutant]
|
|
832
|
+
def try_recipe_run: () -> Array[Mutant]?
|
|
833
|
+
def load_survivor_report: () -> SurvivorLoader::Report
|
|
834
|
+
def run_from_recipes: (SurvivorLoader::Report, Array[String]?) -> Array[Mutant]?
|
|
835
|
+
def recipe_fast_path_safe?: (SurvivorLoader::Report, Array[String]?) -> bool
|
|
836
|
+
def recipe_selector_and_stubs: (Array[String], Hash[String, Hash[String, untyped]]) -> [SurvivorSelector, Array[Mutant]]
|
|
837
|
+
def load_activation_recipes: (Array[String]) -> Hash[String, Hash[String, untyped]]?
|
|
838
|
+
def build_stub_mutant: (String, Hash[String, untyped]) -> Mutant
|
|
839
|
+
def stub_subject_from_recipe: (Hash[String, untyped]) -> Subject
|
|
840
|
+
def recipe_location: (Hash[String, untyped]?) -> Hash[Symbol, untyped]
|
|
841
|
+
def finalize_survivor_split: (SurvivorSelector, Array[Mutant], Hash[Symbol, Array[Mutant]]) -> Array[Mutant]
|
|
842
|
+
def dirty_worktree_changed_files: () -> Array[String]?
|
|
843
|
+
def dirty_source_files?: (Array[String]?, ?git_sha: String?) -> bool
|
|
844
|
+
def committed_changed_files: (String?) -> Array[String]
|
|
845
|
+
def in_include_root?: (String, Array[String]) -> bool
|
|
846
|
+
def warn_survivor_drift: (SurvivorSelector) -> void
|
|
847
|
+
def build_survivor_stats: (SurvivorSelector, Array[Mutant], Hash[Symbol, Array[Mutant]]) -> Hash[Symbol, untyped]
|
|
848
|
+
def test_filter: (SurvivorLoader::Report, ?dirty_source_files: bool) -> SurvivorTestFilter
|
|
849
|
+
def safe_head_sha: () -> String?
|
|
659
850
|
end
|
|
660
851
|
|
|
661
852
|
class CoverageBootstrapper
|