evilution 0.30.3 → 0.30.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 43c577be5df2b96507216aaeb8825fe47a86b52d4a7e43684db715763538d9b5
4
- data.tar.gz: 1a17f1e3e393cb527be15d518fc4f193f04a2932fe44dfa17cf2e406f6ac662a
3
+ metadata.gz: 2bd9eaca72cb973c6e11841144b8bae68d2a838aef1ff64c429e18b4090505f1
4
+ data.tar.gz: bbdedbc5ef66ca367deeec89a53399ce83a235616fe2ac179ed98bf880f8be9b
5
5
  SHA512:
6
- metadata.gz: 7dfc0f9ebbcdb8548b582cd74a803119a42b177316c524b31b4d383c64aada5fadd2d65111fc20817f52b38efa4fa57184ead3679cd085df116a8178a8ed00a6
7
- data.tar.gz: aa58a74d4bd33407126dc3c15c0716c06e260ee17475430b5b6405af4cc3c98191607823490bf4f9b72abe87238d1ec7219cec6dbd17e2915011b04d8abfc14e
6
+ metadata.gz: b7cf12d8381e05d66b1a2ffcf1c243e3e18d66f8f073de892d40ff139bb6952214ac5c6bdcff3ab026dd475601629790e7c4d5f86f8315a8baf989bcb5b4aa7b
7
+ data.tar.gz: 21a523a1f2753c5e70b9fb2d53262d28b169661b75c2bd2ca44ce5f9e5c6234591f6d8dcc935fea8c49a79689d7be5a2883cae6ff4517b711379b80b8d8315bf
@@ -385,3 +385,5 @@
385
385
  {"id":"int-434f659f","kind":"field_change","created_at":"2026-05-15T12:15:28.901913417Z","actor":"Denis Kiselev","issue_id":"EV-225l","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Canary PASS 0.30.2 (re-run, earlier skip was wrong). Baseline 285/286 (1 error = port-9736-hardcoded test, infra collateral). stat.rb 95.92%, job.rb 100% (451 muts, 1 equivalent). No evilution bugs. Ran against shared docker redis on 6381 with 3-line test_helper.rb patch (which-guard, backtick self-spawn, port). Artifact .artifacts/resque_resque.yml."}}
386
386
  {"id":"int-d1684261","kind":"field_change","created_at":"2026-05-15T13:01:27.370142624Z","actor":"Denis Kiselev","issue_id":"EV-laxo","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Investigation complete. Root causes found, both confirmed via instrumented iseq probing. pagy 0%: fork isolation clobbers eval'd mutation — spec require()s lazily-loaded target file not in $LOADED_FEATURES, reloads original from disk (EV-vxgl #1253 P1). kaminari 0%: kaminari runs on test-unit gem not Minitest (ActiveSupport::TestCase < Test::Unit::TestCase), test classes never Minitest::Runnable, 0 tests dispatch — plus evilution silently scores 0% instead of erroring (EV-5dxk #1254 P2). Original hypothesis (reset_state runnables clear) was wrong — disproven."}}
387
387
  {"id":"int-011584d4","kind":"field_change","created_at":"2026-05-15T15:55:27.437673072Z","actor":"Denis Kiselev","issue_id":"EV-vxgl","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Fixed PR (merged). MutationApplier#mark_feature_loaded registers realpath in $LOADED_FEATURES post-eval — spec require() no longer reloads original. pagy jobs=4: 0% -> 82.81%. Residual gap vs jobs=1 is separate (EV-wu8w in_process inflation)."}}
388
+ {"id":"int-c44e7652","kind":"field_change","created_at":"2026-05-16T12:45:46.793709029Z","actor":"Denis Kiselev","issue_id":"EV-xfaj","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Fixed in PR #1260 — run_minitest counts test methods from Minitest::Runnable.runnables registry instead of an evictable reporter"}}
389
+ {"id":"int-eea19850","kind":"field_change","created_at":"2026-05-16T14:22:49.671269232Z","actor":"Denis Kiselev","issue_id":"EV-wu8w","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Fixed in PR #1264 — run_minitest reads verdict from evilution's own per-run SummaryReporter attached after plugin init"}}
data/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  Versioning policy: see [docs/versioning.md](docs/versioning.md).
4
4
 
5
+ ## [0.30.4] - 2026-05-16
6
+
7
+ ### Fixed
8
+
9
+ - **Minitest integration no longer errors every mutation when the project test helper calls `Minitest::Reporters.use!`** — a regression shipped in 0.30.3. The EV-5dxk zero-tests guard read `summary.count` from a `SummaryReporter` that evilution adds to its `CompositeReporter`. But when the target's test helper uses the `minitest-reporters` gem (`Minitest::Reporters.use!` — an extremely common setup), that plugin **replaces** the composite's reporters during `Minitest.init_plugins`, evicting evilution's own `SummaryReporter`. `summary.count` then never advanced — it read 0 even when tests ran — so the guard false-fired and every mutation was reported as errored ("no Minitest tests executed"), collapsing the score to a meaningless 0/0. `run_minitest` now derives the dispatched test-method count from the runnable registry (`Minitest::Runnable.runnables`), which is immune to reporter plugins. Surfaced by the EV-7764 pagy stability canary (EV-xfaj, PR #1260, GH #1259)
10
+ - **In-process isolation no longer false-kills genuinely-equivalent mutations and inflates the score** — `run_minitest` returned `passed: reporter.passed?`. When the target test helper calls `Minitest::Reporters.use!`, `init_plugins` replaces the composite's reporters with `minitest-reporters`' `DelegateReporter`, which delegates to a process-global reporter created once by `use!` and never reset between runs. Under in-process isolation one process runs every mutation in sequence, so that reporter's failures accumulate: `reporter.passed?` then reported `false` for every mutation after the first genuine kill, false-killing real survivors. On the pagy `request.rb` canary, in-process scored 98.44% against fork's correct 82.81%. `run_minitest` now attaches evilution's own fresh `SummaryReporter` to the composite **after** plugin init (so init_plugins cannot evict it) and reads the run's verdict from that per-run reporter; in-process and fork now converge (EV-wu8w, PR #1264, GH #1263)
11
+ - **`MinitestCrashDetector` survives reporter-plugin eviction** — the same `Minitest::Reporters.use!` swap that evicted evilution's `SummaryReporter` also detached the `MinitestCrashDetector`, which was attached to the composite before `initialize_minitest_state`. With the detector evicted, `build_minitest_result`'s `detector.only_crashes?` path went dead on `minitest-reporters` projects: a crash-only mutation result lost its `test_crashed` / `error` / `error_class` crash diagnostics and returned a plain killed result. The detector is now attached after plugin init alongside the `SummaryReporter`, keeping it in the live composite (EV-8z2n, PR #1265, GH #1262)
12
+
5
13
  ## [0.30.3] - 2026-05-16
6
14
 
7
15
  ### Fixed
@@ -178,16 +178,47 @@ class Evilution::Integration::Minitest < Evilution::Integration::Base
178
178
  options[:io] = out
179
179
 
180
180
  reporter = ::Minitest::CompositeReporter.new
181
- summary = ::Minitest::SummaryReporter.new(out, options)
182
- reporter << summary
183
- reporter << detector
184
181
 
185
182
  self.class.initialize_minitest_state(reporter, options)
183
+ summary = attach_evilution_reporters(reporter, detector, out, options)
186
184
  reporter.start
187
185
  dispatch_minitest_suites(reporter, options)
188
186
  reporter.report
189
187
 
190
- { passed: reporter.passed?, count: summary.count }
188
+ { passed: summary.passed?, count: minitest_method_count }
189
+ end
190
+
191
+ # Attach evilution's own reporters to the composite AFTER plugin init, and
192
+ # read the run's verdict from the SummaryReporter rather than reporter.passed?.
193
+ #
194
+ # A target test helper that calls Minitest::Reporters.use! makes
195
+ # init_plugins replace the composite's reporters with minitest-reporters'
196
+ # DelegateReporter, which delegates to a process-global reporter created
197
+ # once by use!. That global reporter is never reset between runs, so under
198
+ # in_process isolation — where one process runs every mutation in sequence
199
+ # — its failures accumulate: reporter.passed? would report false for every
200
+ # mutation after the first genuine kill, false-killing real survivors and
201
+ # inflating the score. Anything attached before init_plugins is likewise
202
+ # evicted — that silently disabled the MinitestCrashDetector, so
203
+ # build_minitest_result's only_crashes? path went dead and crashes were
204
+ # downgraded from :error to :killed. Attaching both reporters here, after
205
+ # init_plugins has run, keeps them in the live composite for the current
206
+ # run. Returns the SummaryReporter.
207
+ def attach_evilution_reporters(reporter, detector, out, options)
208
+ summary = ::Minitest::SummaryReporter.new(out, options)
209
+ reporter << summary
210
+ reporter << detector
211
+ summary
212
+ end
213
+
214
+ # Count dispatched test methods from the runnable registry, not a reporter.
215
+ # A project test helper that calls Minitest::Reporters.use! swaps the
216
+ # composite's reporters during init_plugins, evicting evilution's
217
+ # SummaryReporter — a reporter-based count then reads 0 even on a real run.
218
+ # The runnable registry is immune to reporter plugins. Must run after
219
+ # initialize_minitest_state: runnable_methods calls srand(Minitest.seed).
220
+ def minitest_method_count
221
+ ::Minitest::Runnable.runnables.sum { |r| r.runnable_methods.size }
191
222
  end
192
223
 
193
224
  def dispatch_minitest_suites(reporter, options)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Evilution
4
- VERSION = "0.30.3"
4
+ VERSION = "0.30.4"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: evilution
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.30.3
4
+ version: 0.30.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Denis Kiselev