Class: Rigor::Protection::MutationScanner
- Inherits:
-
Object
- Object
- Rigor::Protection::MutationScanner
- Defined in:
- lib/rigor/protection/mutation_scanner.rb
Overview
ADR-63 Tier 2 — the mutation effectiveness tier (the truth tier behind Tier 1's static Inference::ProtectionScanner proxy). For one file it answers the question Tier 1 only bounds: when a type-visible bug is introduced at a dispatch site, does Rigor actually catch it?
Mechanism (the ADR-62 warm loop, narrowed to per-file measurement): generate the type-visible mutations (Mutator), keep only those whose receiver Rigor holds a concrete type for (the type-aware filter — the FP-safe meaning-maker; an unresolved receiver is kept), then for each ask the kill oracle whether the mutant is caught. The oracle is the ADR-69 seam: #scan_file uses the DiagnosticOracle (a new Rigor diagnostic = a kill); #scan_file_fused additionally consults a TestSuiteOracle on the type-survivors (ADR-70 — the dynamic protection axis).
The expensive builds (RBS environment + the whole-project pre-pass scan) are paid ONCE by the caller and
threaded into the DiagnosticOracle; each mutant reuses them through Runner.new(prebuilt:)#run_source
(in-memory overlay, no disk write).
Defined Under Namespace
Classes: FileResult, FusedFileResult, FusedSite, SurvivingSite
Instance Method Summary collapse
-
#initialize(configuration:, environment:, project_scan:, limit: nil, seed: 1, oracle: nil, site_selector: :biteable, base_scope: nil, discovery_seed: nil) ⇒ MutationScanner
constructor
rubocop:disable Metrics/ParameterLists -- every one is an independently-defaulted collaborator or knob; bundling them into an options object would only move the list behind a name.
- #scan_file(path, source: nil) ⇒ FileResult
-
#scan_file_fused(path, test_oracle:, source: nil) ⇒ FusedFileResult
ADR-70 — the fused static∪dynamic measurement.
Constructor Details
#initialize(configuration:, environment:, project_scan:, limit: nil, seed: 1, oracle: nil, site_selector: :biteable, base_scope: nil, discovery_seed: nil) ⇒ MutationScanner
rubocop:disable Metrics/ParameterLists -- every one is an independently-defaulted collaborator or knob; bundling them into an options object would only move the list behind a name.
95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/rigor/protection/mutation_scanner.rb', line 95 def initialize(configuration:, environment:, project_scan:, limit: nil, seed: 1, oracle: nil, site_selector: :biteable, base_scope: nil, discovery_seed: nil) # rubocop:enable Metrics/ParameterLists @environment = environment @limit = limit @seed = seed @site_selector = site_selector @base_scope = base_scope @oracle = oracle || DiagnosticOracle.new( configuration: configuration, environment: environment, project_scan: project_scan, discovery_seed: discovery_seed ) end |
Instance Method Details
#scan_file(path, source: nil) ⇒ FileResult
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/rigor/protection/mutation_scanner.rb', line 112 def scan_file(path, source: nil) source ||= File.read(path, encoding: Encoding::UTF_8) kept = kept_mutations(source, path) return FileResult.new(path: path, killed: 0, survived: 0, sites: []) if kept.empty? baseline = @oracle.baseline(source: source, path: path) killed = 0 harness_errors = 0 sites = [] kept.each do |mut| case classify(source, path, mut, baseline) when :killed then killed += 1 when :survived then sites << surviving_site(mut) when :harness_error then harness_errors += 1 # :invalid — a parse-broken mutant; not a measurement, skip it. end end FileResult.new(path: path, killed: killed, survived: sites.size, sites: sites, harness_errors: harness_errors) end |
#scan_file_fused(path, test_oracle:, source: nil) ⇒ FusedFileResult
ADR-70 — the fused static∪dynamic measurement. Runs the type pass (the DiagnosticOracle); for every
mutant the type checker did not kill, asks test_oracle whether the project's test suite catches it.
The expensive suite run is paid only for type-survivors (the gradual short-circuit), so the cost is
proportional to the protection hole.
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/rigor/protection/mutation_scanner.rb', line 138 def scan_file_fused(path, test_oracle:, source: nil) source ||= File.read(path, encoding: Encoding::UTF_8) kept = kept_mutations(source, path) return FusedFileResult.new(path: path, type_killed: 0, test_killed: 0, sites: []) if kept.empty? baseline = @oracle.baseline(source: source, path: path) type_killed = 0 test_killed = 0 harness_errors = 0 sites = [] kept.each do |mut| case classify(source, path, mut, baseline) when :killed then type_killed += 1 when :survived if test_oracle.killed?(path: path, original: source, mutant_source: mut.apply(source)) test_killed += 1 else sites << fused_site(mut, :none) end when :harness_error then harness_errors += 1 # :invalid — a parse-broken mutant; not a measurement, skip it. end end FusedFileResult.new(path: path, type_killed: type_killed, test_killed: test_killed, sites: sites, harness_errors: harness_errors) end |