From 5aeb6b7190f2aafa2d6cab0a3b95151e743b49c4 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Wed, 28 Oct 2026 20:08:48 +0100 Subject: [PATCH] =?UTF-8?q?Answer=20architecture=20round=201:=200=20?= =?UTF-8?q?=F0=9F=94=B4,=202=20=F0=9F=9F=A1=20(0=20misread);=20a=20Runtime?= =?UTF-8?q?Error=20fails=20the=20run=20in=20a=206=20GB=20container,=20Stry?= =?UTF-8?q?ker=20bumps=20wait=20for=20a=20hand=20run,=20its=20package=20is?= =?UTF-8?q?=20a=20decision,=20item=2076=20names=20the=20test-file=20lists?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 1 + README.md | 3 ++- docker-runner.sh | 2 +- docs/decisions.md | 8 ++++++++ mutation-tests/check-runtime-errors.mjs | 9 +++++++++ mutation-tests/run.sh | 2 +- mutation-tests/stryker.config.mjs | 5 +++-- renovate.json | 5 +++++ todo.md | 4 ++++ 9 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 mutation-tests/check-runtime-errors.mjs diff --git a/AGENTS.md b/AGENTS.md index 77bf5bf..99e6917 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -43,6 +43,7 @@ In `docs/decisions.md`: - No runtime dependencies - Standards ship as data - fast-check +- Stryker in its own package - Any ES2022 engine - ESM only - One built entrypoint diff --git a/README.md b/README.md index d9a3796..827d869 100644 --- a/README.md +++ b/README.md @@ -733,7 +733,8 @@ Both commands need only Docker, git and bash 5. - `./ci.sh` runs the gate: every check CI runs. - `./mutation-tests/run.sh []` mutation-tests the source lines changed since ``, - `origin/main` by default, running each mutant against only the test files that reach it. + `origin/main` by default, running each mutant, in a 6 GB container, against only the test files + that reach it. ## Links diff --git a/docker-runner.sh b/docker-runner.sh index 2a0f236..ed74022 100644 --- a/docker-runner.sh +++ b/docker-runner.sh @@ -9,7 +9,7 @@ node_image=node:26.12.1-alpine3.24 in_image() { local image=$1 entrypoint=$2 shift 2 - docker run --rm -u "$(id -u):$(id -g)" -e HOME=/tmp ${PROPERTY_RUNS+-e PROPERTY_RUNS} ${in_image_network:+--network "$in_image_network"} -v "$PWD:/app" -w /app --entrypoint "$entrypoint" "$image" "$@" + docker run --rm -u "$(id -u):$(id -g)" -e HOME=/tmp ${PROPERTY_RUNS+-e PROPERTY_RUNS} ${in_image_memory:+--memory "$in_image_memory" --memory-swap "$in_image_memory"} ${in_image_network:+--network "$in_image_network"} -v "$PWD:/app" -w /app --entrypoint "$entrypoint" "$image" "$@" } # Markers on stderr so a captured leg's value stays clean; leg_* because bash scopes local into the leg's own call. diff --git a/docs/decisions.md b/docs/decisions.md index 38538fa..85b1ef3 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -487,6 +487,14 @@ hand otherwise. `fast-check` earns its place as a devDependency shrinking a failing generated document to the nodes that break it. +## Stryker in its own package + +2026-10-28, item 152. Code health: the code is easy to change safely. Valid while the gate runs no +mutation tests. + +Mutation testing with Stryker lives in `mutation-tests/`'s own package, so the gate's `npm ci` never +installs it. CI does not run it, so Renovate does not automerge its bumps. + ## Any ES2022 engine 2026-09-01, the maintainer. Goal 8. Valid while ES2022 is the floor browsers and servers share. diff --git a/mutation-tests/check-runtime-errors.mjs b/mutation-tests/check-runtime-errors.mjs new file mode 100644 index 0000000..d99aefe --- /dev/null +++ b/mutation-tests/check-runtime-errors.mjs @@ -0,0 +1,9 @@ +import { readFileSync } from 'node:fs' +import config from './stryker.config.mjs' + +const mutants = Object.values(JSON.parse(readFileSync(config.jsonReporter.fileName, 'utf8')).files).flatMap((file) => file.mutants) +const errors = mutants.filter((mutant) => mutant.status === 'RuntimeError').length +if (errors > 0) { + console.error(`${errors} of ${mutants.length} mutants ended as RuntimeError, so each may hide a survivor`) + process.exit(1) +} diff --git a/mutation-tests/run.sh b/mutation-tests/run.sh index 204c043..aa14dfd 100755 --- a/mutation-tests/run.sh +++ b/mutation-tests/run.sh @@ -19,4 +19,4 @@ if [ -z "$mutate" ]; then fi leg "install ($node_image)" in_image "$node_image" sh -c 'npm ci --no-audit --no-fund && npm ci --no-audit --no-fund --prefix mutation-tests' -leg "mutation tests ($node_image)" in_image "$node_image" mutation-tests/node_modules/.bin/stryker run mutation-tests/stryker.config.mjs --mutate "$mutate" +in_image_memory=6g leg "mutation tests ($node_image)" in_image "$node_image" sh -c 'mutation-tests/node_modules/.bin/stryker run mutation-tests/stryker.config.mjs --mutate "$1" && node mutation-tests/check-runtime-errors.mjs' sh "$mutate" diff --git a/mutation-tests/stryker.config.mjs b/mutation-tests/stryker.config.mjs index 6372fa3..a00dab2 100644 --- a/mutation-tests/stryker.config.mjs +++ b/mutation-tests/stryker.config.mjs @@ -1,9 +1,10 @@ export default { - // A test file peaks near 2.6 GB: past four workers a 6 GB container kills some, and a killed worker's mutant reads as an error, hiding a survivor. + // A test file peaks near 2.6 GB: past four workers run.sh's 6 GB container kills some, and a killed worker's mutant reads as a RuntimeError, which fails the run. concurrency: 4, coverageAnalysis: 'perTest', ignorePatterns: ['/browser-tests', '/converter-comparison', '/dist', '/mutation-tests', '/package-tests'], - reporters: ['clear-text', 'progress-append-only'], + jsonReporter: { fileName: '/tmp/mutation-report.json' }, + reporters: ['clear-text', 'json', 'progress-append-only'], tap: { testFiles: ['src/**/*.test.ts'] }, tempDirName: '/tmp/stryker', testRunner: 'tap', diff --git a/renovate.json b/renovate.json index 1d36104..0a3ce2d 100644 --- a/renovate.json +++ b/renovate.json @@ -51,6 +51,11 @@ ], "extends": ["config:recommended"], "packageRules": [ + { + "automerge": false, + "description": "CI does not run Stryker, so its bumps wait for a mutation run by hand", + "matchFileNames": ["mutation-tests/package.json"] + }, { "allowedVersions": "<19", "description": "engines.node states >=18, so the image proving it stays on 18", diff --git a/todo.md b/todo.md index 2016215..90e1f8f 100644 --- a/todo.md +++ b/todo.md @@ -436,6 +436,10 @@ assumes they agree. A helper added to one list lints test code under the shipped shipped code from coverage, silently. Derive two from the third, or assert in a test that the three agree. Found by the technical-principles audit, 2026-10-04. +`mutation-tests/run.sh`'s pathspec and `mutation-tests/stryker.config.mjs`'s `testFiles` restate +which files are tests and test helpers, so a helper missing from them reads as survivors. Found by +the architecture review of item 152, 2026-10-28. + ### 56. Give each piece of `blocks.ts`'s block-walk state and `inline-content.ts`'s `Scan` one owner that returns what it changes. Technical principle "One owner per value": the `Walk` record passes through functions that mutate it