Class: Henitai::IncrementalFilter
- Inherits:
-
Object
- Object
- Henitai::IncrementalFilter
- Defined in:
- lib/henitai/incremental_filter.rb,
sig/henitai.rbs
Overview
Marks mutants whose verdict from a prior run is still valid so
--incremental runs skip re-executing them (Gate 3.5, opt-in).
Reuse rules (conservative — any doubt resolves to re-execution):
* Killed: the stored verdict must carry both fingerprints, and both the
subject's source and every recorded covering test file must be
byte-identical to what was recorded. Killed is monotone, so no live
coverage is needed.
* Survived: additionally the LIVE covering set (the full-map
intersection computed via {PerTestCoverage}) must equal the recorded
set in membership, and the run-level dependency fingerprint must be
unchanged — a new or edited test reaching the mutant always forces a
re-run (ADR-11). Without a live per-test map or dependency
fingerprint, survivors always re-execute.
Timeouts, errors, unknown ids and legacy rows always re-execute.
Instance Attribute Summary collapse
-
#dependency_fingerprint ⇒ String?
readonly
Returns the value of attribute dependency_fingerprint.
-
#history_store ⇒ Object
readonly
Returns the value of attribute history_store.
-
#per_test_coverage ⇒ PerTestCoverage?
readonly
Returns the value of attribute per_test_coverage.
Instance Method Summary collapse
-
#ambiguous_stable_ids(mutants) ⇒ Set[String]
MutantIdentity deliberately omits source coordinates (line-drift tolerance), so distinct mutants inside one subject can share a stable id.
-
#apply(mutants) ⇒ Array<Mutant>
The same collection; cache hits get the stored status (:killed or :survived) and from_cache = true.
-
#initialize(history_store:, per_test_coverage: nil, dependency_fingerprint: nil) ⇒ IncrementalFilter
constructor
A new instance of IncrementalFilter.
- #killed_reusable?(mutant, verdict) ⇒ Boolean
- #reusable_status(mutant) ⇒ Symbol?
- #subject_source_unchanged?(mutant, verdict) ⇒ Boolean
-
#survived_reusable?(mutant, verdict) ⇒ Boolean
The filter checks per-test map availability itself instead of trusting the bootstrap's readiness skip: integrations without per-test support or a missing/empty map mean the live covering set is unknowable, and unknowable means re-execute.
Constructor Details
#initialize(history_store:, per_test_coverage: nil, dependency_fingerprint: nil) ⇒ IncrementalFilter
Returns a new instance of IncrementalFilter.
24 25 26 27 28 |
# File 'lib/henitai/incremental_filter.rb', line 24 def initialize(history_store:, per_test_coverage: nil, dependency_fingerprint: nil) @history_store = history_store @per_test_coverage = per_test_coverage @dependency_fingerprint = dependency_fingerprint end |
Instance Attribute Details
#dependency_fingerprint ⇒ String? (readonly)
Returns the value of attribute dependency_fingerprint.
51 52 53 |
# File 'lib/henitai/incremental_filter.rb', line 51 def dependency_fingerprint @dependency_fingerprint end |
#history_store ⇒ Object (readonly)
Returns the value of attribute history_store.
51 52 53 |
# File 'lib/henitai/incremental_filter.rb', line 51 def history_store @history_store end |
#per_test_coverage ⇒ PerTestCoverage? (readonly)
Returns the value of attribute per_test_coverage.
51 52 53 |
# File 'lib/henitai/incremental_filter.rb', line 51 def per_test_coverage @per_test_coverage end |
Instance Method Details
#ambiguous_stable_ids(mutants) ⇒ Set[String]
MutantIdentity deliberately omits source coordinates (line-drift tolerance), so distinct mutants inside one subject can share a stable id. A shared id makes the stored verdict ambiguous — one colliding mutant may have been killed while another errored — so reuse is skipped for every mutant whose id appears more than once in this run.
58 59 60 61 62 |
# File 'lib/henitai/incremental_filter.rb', line 58 def ambiguous_stable_ids(mutants) mutants.group_by(&:stable_id).filter_map do |stable_id, group| stable_id if group.size > 1 end.to_set end |
#apply(mutants) ⇒ Array<Mutant>
Returns the same collection; cache hits get the stored status (:killed or :survived) and from_cache = true.
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/henitai/incremental_filter.rb', line 32 def apply(mutants) collection = Array(mutants) ambiguous = ambiguous_stable_ids(collection) collection.each do |mutant| next unless mutant.pending? next if ambiguous.include?(mutant.stable_id) status = reusable_status(mutant) next unless status mutant.status = status mutant.from_cache = true if mutant.respond_to?(:from_cache=) end mutants end |
#killed_reusable?(mutant, verdict) ⇒ Boolean
76 77 78 79 |
# File 'lib/henitai/incremental_filter.rb', line 76 def killed_reusable?(mutant, verdict) subject_source_unchanged?(mutant, verdict) && VerdictFingerprint.tests_fingerprint_current?(verdict.fetch(:covered_tests_fingerprint)) end |
#reusable_status(mutant) ⇒ Symbol?
64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/henitai/incremental_filter.rb', line 64 def reusable_status(mutant) verdict = history_store.verdict_for(mutant.stable_id) return nil unless verdict case verdict.fetch(:status) when :killed :killed if killed_reusable?(mutant, verdict) when :survived :survived if survived_reusable?(mutant, verdict) end end |
#subject_source_unchanged?(mutant, verdict) ⇒ Boolean
96 97 98 |
# File 'lib/henitai/incremental_filter.rb', line 96 def subject_source_unchanged?(mutant, verdict) VerdictFingerprint.subject_source_hash(mutant) == verdict.fetch(:subject_source_hash) end |
#survived_reusable?(mutant, verdict) ⇒ Boolean
The filter checks per-test map availability itself instead of trusting the bootstrap's readiness skip: integrations without per-test support or a missing/empty map mean the live covering set is unknowable, and unknowable means re-execute.
85 86 87 88 89 90 91 92 93 94 |
# File 'lib/henitai/incremental_filter.rb', line 85 def survived_reusable?(mutant, verdict) return false unless per_test_coverage&.available? return false unless subject_source_unchanged?(mutant, verdict) VerdictFingerprint.survivor_fingerprint_current?( verdict.fetch(:covered_tests_fingerprint), live_paths: per_test_coverage.tests_covering(mutant), dependency_sha: dependency_fingerprint ) end |