henitai 0.4.0 → 0.5.1
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 +97 -1
- data/README.md +1 -1
- data/lib/henitai/cli.rb +1 -1
- data/lib/henitai/configuration.rb +9 -2
- data/lib/henitai/dirty_source_detector.rb +53 -0
- data/lib/henitai/equivalence_detector/operand_predicates.rb +49 -0
- data/lib/henitai/equivalence_detector.rb +6 -23
- data/lib/henitai/excluded_test_filter.rb +47 -0
- data/lib/henitai/execution_engine.rb +5 -11
- data/lib/henitai/inherited_fd_registry.rb +66 -0
- data/lib/henitai/integration/base.rb +7 -2
- data/lib/henitai/integration/child_bootstrap.rb +27 -0
- data/lib/henitai/integration/child_debug_log.rb +135 -0
- data/lib/henitai/integration/child_runtime_control.rb +6 -18
- data/lib/henitai/integration/loaded_features.rb +38 -0
- data/lib/henitai/integration/mutant_run_support.rb +5 -5
- data/lib/henitai/integration/rspec_child_runner.rb +16 -15
- data/lib/henitai/integration/rspec_process_runner.rb +7 -2
- data/lib/henitai/integration.rb +10 -7
- data/lib/henitai/mutation_skip_directives.rb +7 -1
- data/lib/henitai/operators/return_value.rb +1 -1
- data/lib/henitai/orphan_watchdog.rb +93 -0
- data/lib/henitai/process_liveness.rb +41 -0
- data/lib/henitai/reports_directory_lock.rb +12 -11
- data/lib/henitai/result.rb +30 -3
- data/lib/henitai/runner.rb +41 -123
- data/lib/henitai/runner_dependencies.rb +75 -0
- data/lib/henitai/slot_scheduler/drain_verdict.rb +29 -0
- data/lib/henitai/slot_scheduler/draining.rb +7 -17
- data/lib/henitai/slot_scheduler/retry_policy.rb +21 -0
- data/lib/henitai/slot_scheduler/slot_deadline.rb +37 -0
- data/lib/henitai/slot_scheduler/slot_table.rb +75 -0
- data/lib/henitai/slot_scheduler/test_file_selection.rb +40 -0
- data/lib/henitai/slot_scheduler.rb +68 -80
- data/lib/henitai/source_file_selection.rb +76 -0
- data/lib/henitai/subject_selection.rb +33 -0
- data/lib/henitai/survivor_rerun_strategy.rb +7 -19
- data/lib/henitai/version.rb +1 -1
- data/lib/henitai.rb +8 -0
- data/sig/henitai.rbs +92 -38
- metadata +39 -11
- data/lib/henitai/integration/child_debug_support.rb +0 -119
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "slot_scheduler/process_control"
|
|
4
4
|
require_relative "slot_scheduler/draining"
|
|
5
|
+
require_relative "slot_scheduler/drain_verdict"
|
|
6
|
+
require_relative "slot_scheduler/retry_policy"
|
|
7
|
+
require_relative "slot_scheduler/slot_deadline"
|
|
8
|
+
require_relative "slot_scheduler/slot_table"
|
|
9
|
+
require_relative "slot_scheduler/test_file_selection"
|
|
5
10
|
|
|
6
11
|
module Henitai
|
|
7
12
|
# Owns the process-slot table for a single parallel mutation run.
|
|
@@ -40,18 +45,23 @@ module Henitai
|
|
|
40
45
|
# @return [Array<ScenarioExecutionResult>] verdicts accumulated so far.
|
|
41
46
|
attr_reader :flaky_retry_count, :results
|
|
42
47
|
|
|
43
|
-
|
|
48
|
+
# +slot_table+ is injectable so a spec can seed mid-run state, or assert
|
|
49
|
+
# against the table through its own public interface, without reaching a
|
|
50
|
+
# private reader here.
|
|
51
|
+
# rubocop:disable Metrics/ParameterLists -- every one of these is a distinct
|
|
52
|
+
# collaborator supplied by ProcessWorkerRunner; bundling them into a context
|
|
53
|
+
# object would only move the list.
|
|
54
|
+
def initialize(integration:, config:, progress_reporter:, options:, host:, slot_table: SlotTable.new)
|
|
55
|
+
# rubocop:enable Metrics/ParameterLists
|
|
44
56
|
@integration = integration
|
|
45
57
|
@config = config
|
|
46
58
|
@progress_reporter = progress_reporter
|
|
47
59
|
@options = options
|
|
48
60
|
@host = host
|
|
61
|
+
@slot_table = slot_table
|
|
49
62
|
@pending = []
|
|
50
|
-
@slots = {}
|
|
51
|
-
@pid_to_slot = {}
|
|
52
63
|
@results = []
|
|
53
64
|
@flaky_retry_count = 0
|
|
54
|
-
@next_slot_id = 0
|
|
55
65
|
end
|
|
56
66
|
|
|
57
67
|
# Queues the mutants to be scheduled into worker slots.
|
|
@@ -60,11 +70,11 @@ module Henitai
|
|
|
60
70
|
end
|
|
61
71
|
|
|
62
72
|
def done?
|
|
63
|
-
pending.empty? &&
|
|
73
|
+
pending.empty? && slot_table.empty?
|
|
64
74
|
end
|
|
65
75
|
|
|
66
76
|
def fill_idle_slots
|
|
67
|
-
while
|
|
77
|
+
while slot_table.size < worker_count && !pending.empty?
|
|
68
78
|
mutant = pending.shift
|
|
69
79
|
spawn_into_slot(mutant)
|
|
70
80
|
end
|
|
@@ -83,8 +93,8 @@ module Henitai
|
|
|
83
93
|
|
|
84
94
|
def next_event_timeout
|
|
85
95
|
now = monotonic_time
|
|
86
|
-
slot_timeouts =
|
|
87
|
-
|
|
96
|
+
slot_timeouts = slot_table.each_value.filter_map do |slot|
|
|
97
|
+
slot_deadline.remaining(slot, now)
|
|
88
98
|
end
|
|
89
99
|
|
|
90
100
|
slot_timeouts.min
|
|
@@ -92,7 +102,7 @@ module Henitai
|
|
|
92
102
|
|
|
93
103
|
private
|
|
94
104
|
|
|
95
|
-
attr_reader :pending, :
|
|
105
|
+
attr_reader :pending, :slot_table, :integration, :config,
|
|
96
106
|
:progress_reporter, :options, :host
|
|
97
107
|
|
|
98
108
|
def worker_count = host.worker_count
|
|
@@ -101,12 +111,12 @@ module Henitai
|
|
|
101
111
|
def shutdown? = host.shutdown_requested?
|
|
102
112
|
|
|
103
113
|
def spawn_into_slot(mutant)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
mutant
|
|
107
|
-
return record_no_coverage(mutant) if
|
|
114
|
+
selection = test_file_selection
|
|
115
|
+
test_files = selection.for(mutant)
|
|
116
|
+
annotate_selection(mutant, test_files)
|
|
117
|
+
return record_no_coverage(mutant) if selection.resolved_empty?(test_files)
|
|
108
118
|
|
|
109
|
-
worker_index = next_free_worker_index
|
|
119
|
+
worker_index = slot_table.next_free_worker_index(worker_count)
|
|
110
120
|
with_worker_slot(worker_index) do
|
|
111
121
|
handle = integration.spawn_mutant(mutant: mutant, test_files: test_files)
|
|
112
122
|
register_slot(handle, mutant, worker_index, slot_timeout(mutant, test_files))
|
|
@@ -115,11 +125,18 @@ module Henitai
|
|
|
115
125
|
record_spawn_failure(mutant, e)
|
|
116
126
|
end
|
|
117
127
|
|
|
128
|
+
# Reported back onto the mutant so the report can show which tests were
|
|
129
|
+
# considered, whether or not any of them ran. Guarded by respond_to? because
|
|
130
|
+
# specs pass Struct stand-ins without these members.
|
|
131
|
+
def annotate_selection(mutant, test_files)
|
|
132
|
+
mutant.covered_by = test_files if mutant.respond_to?(:covered_by=)
|
|
133
|
+
mutant.tests_completed = test_files.size if mutant.respond_to?(:tests_completed=)
|
|
134
|
+
end
|
|
135
|
+
|
|
118
136
|
def register_slot(handle, mutant, worker_index, timeout)
|
|
119
|
-
|
|
120
|
-
slot
|
|
121
|
-
|
|
122
|
-
pid_to_slot[handle.pid] = slot_id
|
|
137
|
+
slot = build_slot(slot_table.next_slot_id!, mutant, handle, worker_index, timeout)
|
|
138
|
+
slot_table.add(slot)
|
|
139
|
+
slot_table.register_pid(handle.pid, slot.slot_id)
|
|
123
140
|
Integration::SchedulerDiagnostics.child_started(handle.pid)
|
|
124
141
|
end
|
|
125
142
|
|
|
@@ -137,20 +154,11 @@ module Henitai
|
|
|
137
154
|
resolver ? resolver.call(mutant, test_files) : config.timeout
|
|
138
155
|
end
|
|
139
156
|
|
|
140
|
-
# Smallest index in 0...worker_count not held by a live slot, so
|
|
141
|
-
# concurrently-running children always see distinct values and freed
|
|
142
|
-
# indices are reused. Slot ids themselves grow monotonically and are
|
|
143
|
-
# unsuitable as a resource token.
|
|
144
|
-
def next_free_worker_index
|
|
145
|
-
used = slots.each_value.map(&:worker_index)
|
|
146
|
-
(0...worker_count).find { |index| !used.include?(index) } || used.size
|
|
147
|
-
end
|
|
148
|
-
|
|
149
157
|
def complete_slot(pid, wait_result)
|
|
150
|
-
slot_id =
|
|
158
|
+
slot_id = slot_table.release_pid(pid)
|
|
151
159
|
return unless slot_id
|
|
152
160
|
|
|
153
|
-
slot =
|
|
161
|
+
slot = slot_table.fetch(slot_id)
|
|
154
162
|
return unless slot
|
|
155
163
|
|
|
156
164
|
Integration::SchedulerDiagnostics.child_ended(pid)
|
|
@@ -159,29 +167,46 @@ module Henitai
|
|
|
159
167
|
end
|
|
160
168
|
|
|
161
169
|
def dispatch_slot_result(slot, result)
|
|
162
|
-
if
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
170
|
+
return retry_slot(slot) if retry_policy.retry?(slot: slot, result: result, shutdown: shutdown?)
|
|
171
|
+
|
|
172
|
+
finalize_slot(slot, result)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def finalize_slot(slot, result)
|
|
176
|
+
slot_table.delete(slot.slot_id)
|
|
177
|
+
slot.mutant.status = result.status
|
|
178
|
+
results << result
|
|
179
|
+
progress_reporter&.progress(slot.mutant, scenario_result: result)
|
|
180
|
+
result.release_output! if result.respond_to?(:release_output!)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Memoized lazily, not built in #initialize: specs (and the no-op scheduler
|
|
184
|
+
# the runner builds when nothing is pending) pass `config: nil`, and this
|
|
185
|
+
# is only reached once a slot has actually finished.
|
|
186
|
+
def retry_policy
|
|
187
|
+
@retry_policy ||= RetryPolicy.new(max_retries: config.max_flaky_retries)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def drain_verdict
|
|
191
|
+
@drain_verdict ||= DrainVerdict.new(integration: integration)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def slot_deadline
|
|
195
|
+
@slot_deadline ||= SlotDeadline.new(drain_window: PROCESS_DRAIN_WINDOW)
|
|
171
196
|
end
|
|
172
197
|
|
|
173
|
-
def
|
|
174
|
-
|
|
198
|
+
def test_file_selection
|
|
199
|
+
@test_file_selection ||= TestFileSelection.new(options: options, integration: integration)
|
|
175
200
|
end
|
|
176
201
|
|
|
177
202
|
def retry_slot(slot)
|
|
178
|
-
test_files =
|
|
203
|
+
test_files = test_file_selection.for(slot.mutant)
|
|
179
204
|
with_worker_slot(slot.worker_index) do
|
|
180
205
|
handle = integration.spawn_mutant(mutant: slot.mutant, test_files: test_files)
|
|
181
206
|
finish_retry(slot, handle)
|
|
182
207
|
end
|
|
183
208
|
rescue StandardError => e
|
|
184
|
-
|
|
209
|
+
slot_table.delete(slot.slot_id)
|
|
185
210
|
record_spawn_failure(slot.mutant, e)
|
|
186
211
|
end
|
|
187
212
|
|
|
@@ -189,7 +214,7 @@ module Henitai
|
|
|
189
214
|
@flaky_retry_count += 1 if slot.retry_count.zero?
|
|
190
215
|
slot.retry_count += 1
|
|
191
216
|
reset_slot_for_retry(slot, handle)
|
|
192
|
-
|
|
217
|
+
slot_table.register_pid(handle.pid, slot.slot_id)
|
|
193
218
|
Integration::SchedulerDiagnostics.child_started(handle.pid)
|
|
194
219
|
end
|
|
195
220
|
|
|
@@ -220,43 +245,6 @@ module Henitai
|
|
|
220
245
|
progress_reporter&.progress(mutant, scenario_result: nil)
|
|
221
246
|
end
|
|
222
247
|
|
|
223
|
-
def resolved_selection_empty?(test_files)
|
|
224
|
-
options.key?(:test_file_resolver) && test_files.empty?
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
def remaining_slot_timeout(slot, now)
|
|
228
|
-
# Invariant: drain_draining_slots runs (and removes draining slots) before
|
|
229
|
-
# the event wait, so next_event_timeout never observes a draining slot
|
|
230
|
-
# whose SIGTERM has not been sent. Guard term_sent_at_monotonic defensively
|
|
231
|
-
# against a future ordering change: an unsignalled draining slot is due now.
|
|
232
|
-
return 0.0 if slot.draining && slot.term_sent_at_monotonic.nil?
|
|
233
|
-
|
|
234
|
-
deadline =
|
|
235
|
-
if slot.draining
|
|
236
|
-
slot.term_sent_at_monotonic + PROCESS_DRAIN_WINDOW
|
|
237
|
-
else
|
|
238
|
-
slot.started_at_monotonic + slot.timeout
|
|
239
|
-
end
|
|
240
|
-
remaining = deadline - now
|
|
241
|
-
remaining.positive? ? remaining : 0.0
|
|
242
|
-
end
|
|
243
|
-
|
|
244
|
-
def resolve_test_files(mutant)
|
|
245
|
-
if @options.key?(:test_file_resolver)
|
|
246
|
-
@options[:test_file_resolver].call(mutant)
|
|
247
|
-
elsif @options.key?(:test_files)
|
|
248
|
-
@options[:test_files]
|
|
249
|
-
else
|
|
250
|
-
integration.select_tests(mutant.subject)
|
|
251
|
-
end
|
|
252
|
-
end
|
|
253
|
-
|
|
254
|
-
def next_slot_id!
|
|
255
|
-
id = @next_slot_id
|
|
256
|
-
@next_slot_id += 1
|
|
257
|
-
id
|
|
258
|
-
end
|
|
259
|
-
|
|
260
248
|
def with_worker_slot(worker_index)
|
|
261
249
|
previous = ENV.fetch(WORKER_SLOT_ENV, nil)
|
|
262
250
|
ENV[WORKER_SLOT_ENV] = worker_index.to_s
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Henitai
|
|
4
|
+
# Resolves which source files a run mutates: everything under `includes:`,
|
|
5
|
+
# minus anything matched by `excludes:`, optionally narrowed to what changed
|
|
6
|
+
# since a git ref.
|
|
7
|
+
#
|
|
8
|
+
# Excludes are absolute, not a tiebreak: an excluded file (a standalone entry
|
|
9
|
+
# point that cannot be mutation-tested in-process, say) stays excluded even
|
|
10
|
+
# when it is the only thing that changed. Both stages are filters over the
|
|
11
|
+
# same list, so they commute — excludes run first only to keep the
|
|
12
|
+
# per-test-coverage lookups in `filter_changed` off files that are already
|
|
13
|
+
# out of scope.
|
|
14
|
+
class SourceFileSelection
|
|
15
|
+
def initialize(config:, since:, git_diff_analyzer:, per_test_coverage:)
|
|
16
|
+
@config = config
|
|
17
|
+
@since = since
|
|
18
|
+
@git_diff_analyzer = git_diff_analyzer
|
|
19
|
+
@per_test_coverage = per_test_coverage
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def call
|
|
23
|
+
filter_changed(reject_excluded(included_source_files))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def included_source_files
|
|
27
|
+
Array(@config.includes).flat_map do |include_path|
|
|
28
|
+
Dir.glob(File.join(include_path, "**", "*.rb"))
|
|
29
|
+
end.uniq
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Drops files matched by any `excludes:` glob. Compared as expanded paths so
|
|
33
|
+
# a relative glob and an absolute candidate still match.
|
|
34
|
+
def reject_excluded(files)
|
|
35
|
+
excluded = excluded_source_files
|
|
36
|
+
return files if excluded.empty?
|
|
37
|
+
|
|
38
|
+
files.reject { |path| excluded.include?(normalize_path(path)) }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def filter_changed(files)
|
|
42
|
+
return files unless @since
|
|
43
|
+
|
|
44
|
+
changed = changed_paths_since.map { |path| normalize_path(path) }
|
|
45
|
+
changed += covered_sources_for_changed_tests(changed)
|
|
46
|
+
files.select { |path| changed.include?(normalize_path(path)) }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def excluded_source_files
|
|
52
|
+
Array(@config.excludes)
|
|
53
|
+
.flat_map { |pattern| Dir.glob(pattern) }
|
|
54
|
+
.map { |path| normalize_path(path) }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Committed changes since the ref plus the current working tree (tracked
|
|
58
|
+
# dirty and untracked files): the working tree is what gets tested, so it is
|
|
59
|
+
# always part of "changed since REF".
|
|
60
|
+
def changed_paths_since
|
|
61
|
+
@git_diff_analyzer.changed_files(from: @since, to: "HEAD") +
|
|
62
|
+
@git_diff_analyzer.working_tree_changed_files
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Changed test files select the source files they cover, so an edited test
|
|
66
|
+
# re-tests the subjects it can kill. Uses the per-test map from the previous
|
|
67
|
+
# run — Gate 1 runs before this run's bootstrap finishes.
|
|
68
|
+
def covered_sources_for_changed_tests(changed_paths)
|
|
69
|
+
changed_paths
|
|
70
|
+
.flat_map { |path| @per_test_coverage.source_files_covered_by(path) }
|
|
71
|
+
.map { |path| normalize_path(path) }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def normalize_path(path) = File.expand_path(path)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Henitai
|
|
4
|
+
# Turns a list of source files into the subjects a run mutates, applying any
|
|
5
|
+
# CLI subject patterns.
|
|
6
|
+
#
|
|
7
|
+
# With no patterns every resolved subject is kept. With patterns, each is
|
|
8
|
+
# applied independently and the results concatenated, so overlapping patterns
|
|
9
|
+
# can name the same subject twice — hence the de-duplication on
|
|
10
|
+
# `[expression, source_file]` rather than on object identity. Expression alone
|
|
11
|
+
# is not enough: the same expression can legitimately appear in two files.
|
|
12
|
+
class SubjectSelection
|
|
13
|
+
def initialize(subject_resolver:, patterns:)
|
|
14
|
+
@subject_resolver = subject_resolver
|
|
15
|
+
@patterns = patterns
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def resolve(source_files)
|
|
19
|
+
subjects = @subject_resolver.resolve_from_files(source_files)
|
|
20
|
+
return subjects if pattern_expressions.empty?
|
|
21
|
+
|
|
22
|
+
unique(pattern_expressions.flat_map { |expression| @subject_resolver.apply_pattern(subjects, expression) })
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def unique(subjects)
|
|
26
|
+
subjects.uniq { |subject| [subject.expression, subject.source_file] }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def pattern_expressions = Array(@patterns).map(&:expression)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "dirty_source_detector"
|
|
4
|
+
|
|
3
5
|
module Henitai
|
|
4
6
|
# Survivor-rerun fast path for {Runner}.
|
|
5
7
|
#
|
|
@@ -154,27 +156,13 @@ module Henitai
|
|
|
154
156
|
end
|
|
155
157
|
|
|
156
158
|
def dirty_source_files?(dirty_worktree_files, git_sha: nil)
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
all_changed = dirty_worktree_files + committed_changed_files(git_sha)
|
|
160
|
-
include_roots = Array(@config.includes).map { |path| normalize_path(path) }
|
|
161
|
-
all_changed.any? { |path| in_include_root?(normalize_path(path), include_roots) }
|
|
162
|
-
rescue StandardError
|
|
163
|
-
true
|
|
159
|
+
dirty_source_detector.dirty?(dirty_worktree_files, git_sha: git_sha)
|
|
164
160
|
end
|
|
165
161
|
|
|
166
|
-
def
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
def in_include_root?(path, include_roots)
|
|
173
|
-
include_roots.any? { |root| path == root || path.start_with?("#{root}/") }
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
def normalize_path(path)
|
|
177
|
-
File.expand_path(path)
|
|
162
|
+
def dirty_source_detector
|
|
163
|
+
@dirty_source_detector ||= DirtySourceDetector.new(
|
|
164
|
+
includes: @config.includes, git_diff_analyzer: @git_diff_analyzer
|
|
165
|
+
)
|
|
178
166
|
end
|
|
179
167
|
|
|
180
168
|
def warn_survivor_drift(selector)
|
data/lib/henitai/version.rb
CHANGED
data/lib/henitai.rb
CHANGED
|
@@ -58,6 +58,9 @@ module Henitai
|
|
|
58
58
|
autoload :SurvivorTestFilter, "henitai/survivor_test_filter"
|
|
59
59
|
autoload :SurvivorActivationCache, "henitai/survivor_activation_cache"
|
|
60
60
|
autoload :SurvivorRerunStrategy, "henitai/survivor_rerun_strategy"
|
|
61
|
+
autoload :DirtySourceDetector, "henitai/dirty_source_detector"
|
|
62
|
+
autoload :SourceFileSelection, "henitai/source_file_selection"
|
|
63
|
+
autoload :SubjectSelection, "henitai/subject_selection"
|
|
61
64
|
autoload :ScenarioExecutionResult, "henitai/scenario_execution_result"
|
|
62
65
|
autoload :CoverageFormatter, "henitai/coverage_formatter"
|
|
63
66
|
autoload :MinitestCoverageReporter, "henitai/minitest_coverage_reporter"
|
|
@@ -65,12 +68,17 @@ module Henitai
|
|
|
65
68
|
autoload :SyntaxValidator, "henitai/syntax_validator"
|
|
66
69
|
autoload :SamplingStrategy, "henitai/sampling_strategy"
|
|
67
70
|
autoload :TestPrioritizer, "henitai/test_prioritizer"
|
|
71
|
+
autoload :ExcludedTestFilter, "henitai/excluded_test_filter"
|
|
68
72
|
autoload :TimeoutCalibrator, "henitai/timeout_calibrator"
|
|
69
73
|
autoload :ExecutionEngine, "henitai/execution_engine"
|
|
70
74
|
autoload :ProcessWorkerRunner, "henitai/process_worker_runner"
|
|
71
75
|
autoload :SlotScheduler, "henitai/slot_scheduler"
|
|
72
76
|
autoload :ProcessWakeup, "henitai/process_wakeup"
|
|
77
|
+
autoload :ProcessLiveness, "henitai/process_liveness"
|
|
78
|
+
autoload :InheritedFdRegistry, "henitai/inherited_fd_registry"
|
|
79
|
+
autoload :OrphanWatchdog, "henitai/orphan_watchdog"
|
|
73
80
|
autoload :Runner, "henitai/runner"
|
|
81
|
+
autoload :RunnerDependencies, "henitai/runner_dependencies"
|
|
74
82
|
autoload :Reporter, "henitai/reporter"
|
|
75
83
|
autoload :Integration, "henitai/integration"
|
|
76
84
|
autoload :Result, "henitai/result"
|
data/sig/henitai.rbs
CHANGED
|
@@ -76,36 +76,56 @@ module Henitai
|
|
|
76
76
|
module Operators
|
|
77
77
|
end
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
class Integration::LoadedFeatures
|
|
80
|
+
def include?: (String) -> bool
|
|
81
|
+
def map: (Array[String]) -> Array[[String, bool]]
|
|
82
|
+
|
|
80
83
|
private
|
|
81
84
|
|
|
82
|
-
def
|
|
83
|
-
def
|
|
84
|
-
|
|
85
|
+
def candidates_for: (String) -> Array[String]
|
|
86
|
+
def normalize: (String) -> String
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
class Integration::ChildDebugLog
|
|
90
|
+
PREFIX: String
|
|
91
|
+
|
|
92
|
+
def initialize: (?io: IO?, ?loaded_features: Integration::LoadedFeatures) -> void
|
|
93
|
+
def enabled?: () -> bool
|
|
94
|
+
def write: (String) -> void
|
|
95
|
+
def rspec_trace: (
|
|
85
96
|
test_files: Array[String],
|
|
86
97
|
rspec_options: Array[String],
|
|
87
98
|
rspec_argv: Array[String]
|
|
88
99
|
) -> void
|
|
89
|
-
def
|
|
90
|
-
def
|
|
91
|
-
def
|
|
92
|
-
def
|
|
93
|
-
def
|
|
94
|
-
def
|
|
95
|
-
def
|
|
96
|
-
def
|
|
100
|
+
def rspec_exit: (untyped) -> void
|
|
101
|
+
def example_count: (String) -> void
|
|
102
|
+
def activation_start: (String) -> void
|
|
103
|
+
def activation_end: (untyped, test_files: Array[String]) -> void
|
|
104
|
+
def mutant_meta: (untyped) -> void
|
|
105
|
+
def activation_check: () -> void
|
|
106
|
+
def timeout_signal_sent: (Integer) -> void
|
|
107
|
+
def thread_dump: (String) -> void
|
|
97
108
|
def rspec_world_example_count: () -> Integer?
|
|
98
|
-
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
def dump_thread: (Thread, Integer) -> void
|
|
113
|
+
def value_of: (untyped, Symbol) -> untyped
|
|
114
|
+
def location_of: (untyped) -> String?
|
|
115
|
+
def subject_expression_of: (untyped) -> String?
|
|
116
|
+
def io: () -> IO
|
|
99
117
|
end
|
|
100
118
|
|
|
101
119
|
module Integration::ChildRuntimeControl
|
|
120
|
+
def child_debug_log: () -> Integration::ChildDebugLog
|
|
121
|
+
|
|
102
122
|
private
|
|
103
123
|
|
|
104
124
|
def suppress_simplecov!: () -> void
|
|
105
125
|
def suppress_coverage!: () -> void
|
|
106
126
|
def debug_child_timeout_dump: (Integer) -> void
|
|
107
127
|
def install_debug_timeout_trap: () -> void
|
|
108
|
-
def
|
|
128
|
+
def pause: (Float) -> void
|
|
109
129
|
end
|
|
110
130
|
|
|
111
131
|
module Integration::RspecChildRunner
|
|
@@ -371,7 +391,6 @@ module Henitai
|
|
|
371
391
|
def self.for: (String) -> untyped
|
|
372
392
|
|
|
373
393
|
class Base
|
|
374
|
-
include ChildDebugSupport
|
|
375
394
|
include ChildRuntimeControl
|
|
376
395
|
|
|
377
396
|
def select_tests: (Subject) -> Array[String]
|
|
@@ -383,6 +402,7 @@ module Henitai
|
|
|
383
402
|
def wait_with_timeout: (Integer, Float) -> untyped
|
|
384
403
|
def reap_child: (Integer) -> void
|
|
385
404
|
def cleanup_process_group: (Integer) -> void
|
|
405
|
+
def child_debug_log: () -> ChildDebugLog
|
|
386
406
|
|
|
387
407
|
private
|
|
388
408
|
|
|
@@ -392,18 +412,6 @@ module Henitai
|
|
|
392
412
|
def cleanup_child_process: (Integer) -> void
|
|
393
413
|
def suppress_simplecov!: () -> void
|
|
394
414
|
def suppress_coverage!: () -> void
|
|
395
|
-
def debug_child?: () -> bool
|
|
396
|
-
def debug_child_puts: (String) -> void
|
|
397
|
-
def debug_child_rspec_trace: (test_files: Array[String], rspec_options: Array[String], rspec_argv: Array[String]) -> void
|
|
398
|
-
def debug_child_rspec_exit: (untyped status) -> void
|
|
399
|
-
def debug_child_activation_start: (String mutant_id) -> void
|
|
400
|
-
def debug_child_activation_end: (untyped activation_result, test_files: Array[String]) -> void
|
|
401
|
-
def debug_child_mutant_meta: (Mutant) -> void
|
|
402
|
-
def debug_child_activation_check: () -> void
|
|
403
|
-
def debug_child_example_count: (String) -> void
|
|
404
|
-
def loaded_feature_map: (Array[String]) -> Array[[String, bool]]
|
|
405
|
-
def loaded_feature?: (String) -> bool
|
|
406
|
-
def rspec_world_example_count: () -> Integer?
|
|
407
415
|
def with_non_interactive_stdin: () { () -> untyped } -> untyped
|
|
408
416
|
def subprocess_env: () -> Hash[String, String]
|
|
409
417
|
def scenario_log_support: () -> ScenarioLogSupport
|
|
@@ -973,6 +981,8 @@ module Henitai
|
|
|
973
981
|
|
|
974
982
|
SCHEMA_VERSION: String
|
|
975
983
|
DEFAULT_THRESHOLDS: Hash[Symbol, Integer]
|
|
984
|
+
DEFAULT_COVERAGE_CRITERIA: Hash[Symbol, bool]
|
|
985
|
+
CRITERION_STATUSES: Hash[Symbol, Symbol]
|
|
976
986
|
|
|
977
987
|
attr_reader mutants: Array[Mutant]
|
|
978
988
|
attr_reader started_at: Time
|
|
@@ -982,8 +992,9 @@ module Henitai
|
|
|
982
992
|
attr_reader session_id: String
|
|
983
993
|
attr_reader git_sha: String?
|
|
984
994
|
attr_reader since: String?
|
|
995
|
+
attr_reader coverage_criteria: Hash[Symbol, bool]
|
|
985
996
|
|
|
986
|
-
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?, ?source_provider: ^(String) -> String, ?authoritative: bool, ?since: String?) -> void
|
|
997
|
+
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?, ?source_provider: ^(String) -> String, ?authoritative: bool, ?since: String?, ?coverage_criteria: Hash[Symbol, bool]?) -> void
|
|
987
998
|
def partial_rerun?: () -> bool
|
|
988
999
|
def authoritative?: () -> bool
|
|
989
1000
|
def killed: () -> Integer
|
|
@@ -1000,6 +1011,7 @@ module Henitai
|
|
|
1000
1011
|
private
|
|
1001
1012
|
|
|
1002
1013
|
def detected_in: (Array[Mutant]) -> Integer
|
|
1014
|
+
def detected_statuses: () -> Array[Symbol]
|
|
1003
1015
|
def mutation_score_for: (Array[Mutant]) -> Float?
|
|
1004
1016
|
def mutation_score_indicator_for: (Array[Mutant]) -> Float?
|
|
1005
1017
|
def base_schema: () -> Hash[Symbol, untyped]
|
|
@@ -1063,12 +1075,59 @@ module Henitai
|
|
|
1063
1075
|
def self.write: (String, Hash[String, Hash[String, untyped]]) -> void
|
|
1064
1076
|
end
|
|
1065
1077
|
|
|
1078
|
+
class RunnerDependencies
|
|
1079
|
+
def initialize: (config: untyped) -> void
|
|
1080
|
+
def subject_resolver: () -> untyped
|
|
1081
|
+
def git_diff_analyzer: () -> untyped
|
|
1082
|
+
def mutant_generator: () -> untyped
|
|
1083
|
+
def static_filter: () -> untyped
|
|
1084
|
+
def execution_engine: () -> untyped
|
|
1085
|
+
def coverage_bootstrapper: () -> CoverageBootstrapper
|
|
1086
|
+
def integration: () -> untyped
|
|
1087
|
+
def operators: () -> Array[Operator]
|
|
1088
|
+
def per_test_coverage: () -> PerTestCoverage
|
|
1089
|
+
def history_store: () -> untyped
|
|
1090
|
+
def progress_reporter: (full_run: bool) -> untyped
|
|
1091
|
+
def source_provider: () -> ^(String) -> String
|
|
1092
|
+
end
|
|
1093
|
+
|
|
1094
|
+
class SourceFileSelection
|
|
1095
|
+
def initialize: (
|
|
1096
|
+
config: untyped,
|
|
1097
|
+
since: String?,
|
|
1098
|
+
git_diff_analyzer: untyped,
|
|
1099
|
+
per_test_coverage: untyped
|
|
1100
|
+
) -> void
|
|
1101
|
+
def call: () -> Array[String]
|
|
1102
|
+
def included_source_files: () -> Array[String]
|
|
1103
|
+
def reject_excluded: (Array[String]) -> Array[String]
|
|
1104
|
+
def filter_changed: (Array[String]) -> Array[String]
|
|
1105
|
+
|
|
1106
|
+
private
|
|
1107
|
+
|
|
1108
|
+
def excluded_source_files: () -> Array[String]
|
|
1109
|
+
def changed_paths_since: () -> Array[String]
|
|
1110
|
+
def covered_sources_for_changed_tests: (Array[String]) -> Array[String]
|
|
1111
|
+
def normalize_path: (String) -> String
|
|
1112
|
+
end
|
|
1113
|
+
|
|
1114
|
+
class SubjectSelection
|
|
1115
|
+
def initialize: (subject_resolver: untyped, patterns: Array[untyped]?) -> void
|
|
1116
|
+
def resolve: (Array[String]) -> Array[Subject]
|
|
1117
|
+
def unique: (Array[Subject]) -> Array[Subject]
|
|
1118
|
+
|
|
1119
|
+
private
|
|
1120
|
+
|
|
1121
|
+
def pattern_expressions: () -> Array[String]
|
|
1122
|
+
end
|
|
1123
|
+
|
|
1066
1124
|
class Runner
|
|
1067
1125
|
attr_reader config: Configuration
|
|
1068
1126
|
attr_reader result: untyped
|
|
1069
1127
|
|
|
1070
|
-
def initialize: (?config: Configuration, ?subjects: Array[Subject], ?since: String, ?survivors_from: String?, ?mode: Hash[Symbol, bool]) -> void
|
|
1128
|
+
def initialize: (?config: Configuration, ?subjects: Array[Subject], ?since: String, ?survivors_from: String?, ?mode: Hash[Symbol, bool], ?deps: RunnerDependencies?) -> void
|
|
1071
1129
|
def run: () -> Result
|
|
1130
|
+
def deps: () -> RunnerDependencies
|
|
1072
1131
|
def resolve_subjects: (?Array[String]) -> untyped
|
|
1073
1132
|
def generate_mutants: (untyped) -> untyped
|
|
1074
1133
|
def filter_mutants: (untyped) -> untyped
|
|
@@ -1087,15 +1146,8 @@ module Henitai
|
|
|
1087
1146
|
def per_test_coverage: () -> PerTestCoverage
|
|
1088
1147
|
def history_store_path: () -> String
|
|
1089
1148
|
def source_files: () -> Array[String]
|
|
1090
|
-
def
|
|
1091
|
-
def
|
|
1092
|
-
def excluded_source_files: () -> Array[String]
|
|
1093
|
-
def filter_changed: (Array[String]) -> Array[String]
|
|
1094
|
-
def changed_paths_since: () -> Array[String]
|
|
1095
|
-
def covered_sources_for_changed_tests: (Array[String]) -> Array[String]
|
|
1096
|
-
def pattern_subjects: () -> Array[Subject]
|
|
1097
|
-
def unique_subjects: (Array[Subject]) -> Array[Subject]
|
|
1098
|
-
def normalize_path: (String) -> String
|
|
1149
|
+
def source_file_selection: () -> SourceFileSelection
|
|
1150
|
+
def subject_selection: () -> SubjectSelection
|
|
1099
1151
|
|
|
1100
1152
|
private
|
|
1101
1153
|
|
|
@@ -1111,6 +1163,8 @@ module Henitai
|
|
|
1111
1163
|
def mutants_for: (Array[Subject], Array[String]) -> Array[Mutant]
|
|
1112
1164
|
def with_reports_dir: () { () -> untyped } -> untyped
|
|
1113
1165
|
def result_thresholds: () -> Hash[Symbol, Integer]?
|
|
1166
|
+
def result_coverage_criteria: () -> Hash[Symbol, bool]?
|
|
1167
|
+
def optional_config: (Symbol) -> untyped
|
|
1114
1168
|
def survivor_rerun?: () -> bool
|
|
1115
1169
|
def full_run?: () -> bool
|
|
1116
1170
|
def survivor_strategy: () -> SurvivorRerunStrategy
|