Repository navigation
feat(repo): wire dry-run module evidence into closure analysis - #223
Merged
Merged
Conversation
systemfsoftware-maker
added this pull request to stack #224
October 7, 2026 06:51
The vitest runner collects Vitest's per-worker module execution record at the end of each dry-run test file and returns it as testFileModules, keyed by the absolute test file path with node: builtins dropped. The complete DryRunResult now carries the optional field so an incremental run can close a test file's closure from the modules it actually evaluated Verdict-Semantics: unchanged
ImportClosureInput gains an optional observedModules record, and a computed dynamic import no longer leaves a test file's closure open once the runner reports the modules that test file actually evaluated. Observed modules that name existing files join the closure as extra roots scanned and hashed like statically reached files; a key that is not an absolute path to an existing file, a parse error, or an unresolved static specifier still opens it. Nothing supplies the evidence yet, so the layer is inert in production Verdict-Semantics: unchanged
The dry run records the modules each test file evaluated, mapped from the sandbox back to the project, and a reused dry-run coverage carries the record forward. Every import-closure analysis reads it, so an incremental run closes a test file whose only openness is a computed dynamic import. Evidence is taken only from modules that name an existing project file, dropping the runner's generated sandbox setup module which would otherwise keep every closure open Verdict-Semantics: unchanged
systemfsoftware-maker
force-pushed
the
stryker/closure-evidence-wire
branch
from
October 7, 2026 07:16
4650cf0 to
acb5586
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Layer 3 of 3. This wires the dry run's
testFileModulesinto every closure analysis and persists it in the incremental report. With it, the last 3 open stryker-js test closures close (src/conclude-run.ts,src/mutation-reporting.service.ts,src/run/instrument.ts). Those were open only because they reach the computedimport(moduleName)insrc/drivers/config.ts.Wiring
dryRunCoverage.testFileModulesstores original project paths, mapped from the sandbox the same way asglobalTestInputs. A reused dry run carries it forward unchanged.data:URLs, store realpaths) pass through verbatim, so adata:target still opens the closure.closureAnalysisOfcalls in the dry-run cell: the prior coverage before the reuse decision, and the fresh evidence after a run. It also goes into the incremental-reuse analysis and the plan path.fsreads: a mutant that changes a computed specifier may load a module the dry run never saw.Proof (real dry runs with the layer-1 runner, not hand-fed evidence)
tests/incremental-reuse.integration.test.ts. A test file statically importssrc/left.mjsand, in its body, runsawait load(new URL('../src/plugin.mjs', import.meta.url).href).src/plugin.mjsre-runs left's covered mutants.src/unrelated.mjskeeps them.data:target keeps the closure open, so the unrelated edit re-runs them.import(name). With evidence, an uncalled import loads nothing, so it closes. The scenario now imports adata:URL at runtime, which keeps the soundness case meaningful.Failing-before: "A runtime module a test file loads keeps the closure closed around an unreached edit". With layer 2's engine sources in a scratch worktree, it got
kept: false, reran: trueagainst expectedkept: true, reran: false(1 failed, 13 passed). 14 of 14 pass here.Projected effect (no mutation run; main CI measures after release)
Evidence was collected exactly as the runner collects it, reading the worker module map in each test file's
afterAllduring a plain vitest run of the package's mutation test set: 184 files, 1081 tests. Closures were computed with this branch'sanalyzeImportClosure. Mutants come from the merged report of main run 37574914218 (2624 reusable mutants), and the projection covers one-file edits of each of the 243 source files under main's keys (#216 plus #219).The median holds at 258 because #219's whole-suite key re-runs every static mutant whenever any test reaches the edited file. That is most files; 42 of 243 edits now keep everything. With no open closures left, the held reach key (
stryker/static-reach-closure) projects 165 / 211 / 431 at 12.0 min.Validation
pnpm check:ci: 111 of 112. The only failure isstryker-js#api:check, whose single diff is the machine-dependentNode_2 as Nodealias. The committed report keeps the rendering that CI passes on main.Verdict-Semantics: unchanged, and both changeset checks exit 0.minor.