[quality] Mutation testing discards the coverage of every script tests/helpers.mjs spawns
Maintainers usually reply within 1 day
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 25/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Stale
- Tech stack
- javascript, node.js
Research direction
Start with tests/helpers.mjs and the existing runScriptWithFixtures spawn, then inspect how the tap runner collects Stryker coverage. The issue describes a preload that transfers child coverage and hit counts back to the test process; verify it only runs when globalThis.__stryker__ exists and preserves normal spawns. Confirm with the reported scripts/validate-awards.mjs mutation run: all four mutants should be covered and killed.
Written by the indexing model from the issue text.
Description
Finding
runScriptWithFixtures in tests/helpers.mjs mirrors the repo into a temp
sandbox and spawns the script: spawnSync('node', [join(work, 'scripts', scriptName), ...args]).
Stryker's instrumented source records which mutants ran on
globalThis.__stryker__.mutantCoverage, and the tap runner reads that object
out of the test process only (@stryker-mutator/tap-runner/dist/src/setup/hook.cjs
dumps global[namespace] at exit). A spawned script gets a fresh global, so
its counters die with the child. Stryker never sees a mutant in a top-level
scripts/*.mjs file run, so it classifies every one of them as "no coverage"
and never schedules it.
Mutant activation already crosses the boundary — the instrumented prelude
reads __STRYKER_ACTIVE_MUTANT__ from the inherited environment. Only the
counters are lost.
Evidence
Measured on 8abcb3f, npx stryker run --mutate 'scripts/*.mjs' (29 files,
4135 mutants, 29m06s, StrykerJS 10, coverageAnalysis: 'perTest', tap runner):
File | total | covered | # killed | # timeout | # survived | # no cov |
All files | 12.34 | 89.38 | 480 | 8 | 58 | 3408 |
3408 of 4135 mutants (82%) were never tested. The split is exactly the
spawn boundary — the five scripts whose tests import them scored normally,
the 24 whose tests go through runScriptWithFixtures scored 0.00 with zero
covered mutants:
| imported by its test | score | spawned via the helper | mutants, all "no coverage" |
|---|---|---|---|
check-duplication.mjs |
91.19 | validate-members.mjs |
406 |
audit-gate.mjs |
40.26 | validate-enduser-members.mjs |
401 |
netlify-build.mjs |
74.36 | generate-members.mjs |
348 |
pr-queue-hygiene.mjs |
57.55 | collect-metrics.mjs |
287 |
fetch-community-people.mjs |
222 | ||
| …20 more |
Unit line coverage is 100% on every one of these files
(npm run test:unit:coverage:check, same revision), so the unit coverprofile
cannot surface this: the suites genuinely exercise the scripts, Stryker just
cannot observe that they did.
Recommendation
Bridge the counters across the spawn, in the harness that owns the spawn:
- Add a
node -rpreload that writes the child's
globalThis.__stryker__.mutantCoverage(andhitCount) to a file at exit,
and armshitLimit/hitCountfromSTRYKER_HIT_LIMITfirst so an
infinite-loop mutant kills the one child instead of spinning until Stryker
times out the whole test file. - In
runScriptWithFixtures, attach that preload only when
globalThis.__stryker__exists — outside a mutation run the spawn must stay
byte-for-byte as it is — and merge the dumped counters back afterwards. The
tap runner never setscurrentTestId, so the child'sstaticbucket and
the test process's describe the same test file and add together directly.
Verified against this change: scripts/validate-awards.mjs goes from
0 covered / 4 no coverage / 0.00 to 4 killed / 100.00.
This issue covers the harness bridge only. It does not add scripts/*.mjs
to the mutate set in stryker.config.mjs — that is a separate call with its
own backlog and threshold consequences, and it is filed separately.
Priority
- Impact: high — it silently voids mutation testing for every data validator
and collector the CI gate runs, and no existing gate can detect it. - Effort: medium
🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5n31 | SHA: 8abcb3f
— hive: agent=quality backend=copilot model=claude-opus-5 copilot=1.0.88
- Dominant language
- JavaScript
- Stars
- 0
- Forks
- 3
- Avg merge
- 7h 35m
- Merged PRs (30d)
- 579
Getting set up
Starts the project's dev container in your browser, under your own GitHub account.
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from cncf/endusers
-
agent/sec-check security stage/010-actionable
Difficulty 3/5 1-2 days Newbie friendliness 35/100
Maintainers usually reply within 1 day
-
agent/guide documentation stage/045-needs-decision
Difficulty 3/5 1-2 days Newbie friendliness 30/100
cncf/endusers#1924 · 3 comments ·
Maintainers usually reply within 1 day
-
agent/guide documentation stage/045-needs-decision
Difficulty 3/5 1-2 days Newbie friendliness 38/100
cncf/endusers#1923 · 1 comment ·
Maintainers usually reply within 1 day
-
agent/quality stage/010-actionable testing
Difficulty 3/5 1-2 days Newbie friendliness 62/100
Maintainers usually reply within 1 day
-
a11y: search button accessible name does not contain its visible label (WCAG 2.5.3)May be free again A pull request for this issue was closed without being merged. Openblocked bug hive/claimed-by-scanner needs-human
Difficulty 4/5 3-5 days Newbie friendliness 39/100
cncf/endusers#1618 · 8 comments ·
Maintainers usually reply within 1 day
Similar issues
-
Difficulty 1/5 Under an hour Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
type/bug
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
flarum/framework#5301 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
AOSSIE-Org/BabyNest#284 ·
Maintainers usually reply within 1 day
-
status:untriaged
Difficulty 1/5 Under an hour Newbie friendliness 86/100
midnightntwrk/servicedesk#275 ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 85/100
seek4science/seek#2762 ·
Maintainers usually reply within 5 days