mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
60400d04b7
* feat(mutation): add target-annotation-serde + snapshot-occlusion kernels Both are pure decision kernels the lane's own membership rule covers (target-annotation-serde: parse/validate/normalize the .ad comment-line codec, zero I/O; snapshot-occlusion: pure covered/not-covered decision where a wrong answer silently blocks or mis-allows a tap) but were excluded from KERNEL_MODULES. Fixing the harness's packages/*/src blind spot was required, not optional: test-scope.ts, ownership.ts, and vitest.mutation.config.ts all hardcoded `src/` as the only place a kernel's tests could live. target-annotation-serde's own tests live under packages/ad-script/src/internal/__tests__/, so without this fix the module would score 0% from day one — not from weak tests, but because its test file was silently invisible to the lane. Widened the same three places, plus mutation-affected.yml's path filter and isTestFile/ownedTestFiles in ownership.ts, to also recognize packages/*/src/**/*.test.ts (mirroring vitest.config.ts's own unit-core project include list). Triaged every surviving mutant from the initial run: real coverage gaps got a new/adjusted test (kill-with-test), everything else is documented equivalent with an inline comment at the mutation site explaining the invariant that makes it unobservable (redundant early-returns, JSON.stringify dropping undefined-valued keys, Number.isFinite/isSafeInteger's total-function safety, caller-enforced positiveRect/candidate invariants, etc). Baseline recorded from the actual measured run, not inherited or guessed: 94.03% (315/335) and 89.74% (175/195). * style: run the formatter over the four files the gate flagged
94 lines
3.8 KiB
TypeScript
94 lines
3.8 KiB
TypeScript
// The workflows' YAML cannot read the kernel registry, so these assertions keep
|
|
// the two in step: a module added to KERNEL_MODULES that no weekly shard runs
|
|
// would silently drop out of the sweep, and one no PR path filter selects would
|
|
// silently stop gating once the ratchet graduates.
|
|
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { test } from 'node:test';
|
|
import { KERNEL_MODULES, shardMatrix } from './modules.ts';
|
|
|
|
const repoRoot = path.resolve(import.meta.dirname, '../..');
|
|
|
|
function workflow(name: string): string {
|
|
return fs.readFileSync(path.join(repoRoot, '.github/workflows', name), 'utf8');
|
|
}
|
|
|
|
test('the weekly sweep shards exactly the registry matrix', () => {
|
|
const yaml = workflow('mutation-weekly.yml');
|
|
const jobs = [...yaml.matchAll(/^ {10}- \{ (?<entry>[^}]+) \}$/gm)].map((match) =>
|
|
Object.fromEntries(
|
|
match
|
|
.groups!.entry.split(', ')
|
|
.map((pair) => pair.split(': ') as [string, string])
|
|
.map(([key, value]) => [key, value]),
|
|
),
|
|
);
|
|
assert.deepEqual(
|
|
jobs,
|
|
shardMatrix().map((spec) =>
|
|
spec.shard ? { ...spec } : { name: spec.name, module: spec.module },
|
|
),
|
|
);
|
|
});
|
|
|
|
test('the weekly sweep merges the shards into one ratcheted verdict', () => {
|
|
const yaml = workflow('mutation-weekly.yml');
|
|
assert.match(yaml, /pnpm mutation:check --report-dir/);
|
|
assert.match(yaml, /GITHUB_STEP_SUMMARY|\$GITHUB_STEP_SUMMARY/);
|
|
// A dead shard must not be merged into a verdict that looks like a sweep.
|
|
assert.match(
|
|
yaml,
|
|
new RegExp(`--expect-shards ${shardMatrix().length}\\b`),
|
|
'the weekly ratchet does not require the full shard set',
|
|
);
|
|
});
|
|
|
|
// A shard that outruns the job timeout reports nothing, so the per-shard budget
|
|
// is the acceptance criterion made mechanical.
|
|
test('no mutation shard is allowed to exceed the 30-minute budget', () => {
|
|
for (const name of ['mutation-weekly.yml', 'mutation-affected.yml']) {
|
|
for (const [, minutes] of workflow(name).matchAll(/timeout-minutes: (\d+)/g)) {
|
|
assert.ok(Number(minutes) <= 30, `${name} declares a ${minutes}-minute job`);
|
|
}
|
|
}
|
|
});
|
|
|
|
test('every kernel path a PR can touch selects the affected mutation job', () => {
|
|
// Quote style is the formatter's business (oxfmt formats the workflow tree), so
|
|
// accept either spelling of the same scalar rather than pinning this gate to it.
|
|
const paths = [
|
|
...workflow('mutation-affected.yml').matchAll(/^ {6}- (?<q>['"])(?<glob>[^'"]+)\k<q>$/gm),
|
|
].map((match) => match.groups!.glob);
|
|
for (const module of KERNEL_MODULES) {
|
|
for (const owned of module.owns) {
|
|
const selected = paths.some(
|
|
(glob) =>
|
|
glob === owned ||
|
|
glob === `${owned}**` ||
|
|
(glob.endsWith('/**') && owned.startsWith(glob.slice(0, -2))),
|
|
);
|
|
assert.ok(selected, `no path filter selects ${owned} (module ${module.id})`);
|
|
}
|
|
}
|
|
// Ownership is derived, so any test in src/ or a workspace package's src/
|
|
// can own a kernel; the filter must let all of them through and leave the
|
|
// decision to the `select` job. A narrower filter is exactly the omission
|
|
// the derivation exists to prevent.
|
|
assert.ok(
|
|
paths.includes('src/**/*.test.ts'),
|
|
'the PR lane must trigger on every src test, since test ownership is derived',
|
|
);
|
|
assert.ok(
|
|
paths.includes('packages/*/src/**/*.test.ts'),
|
|
'the PR lane must trigger on every packages/*/src test too — target-annotation-serde is owned by one',
|
|
);
|
|
assert.match(workflow('mutation-affected.yml'), /mutation:affected --list-affected/);
|
|
// The lane's own sources fail open into it too: a ratchet or baseline edit must
|
|
// prove itself against real mutants, not against a stale report.
|
|
for (const own of ['scripts/mutation/**', 'stryker.config.json', 'mutation-baselines/**']) {
|
|
assert.ok(paths.includes(own), `missing path filter ${own}`);
|
|
}
|
|
});
|