Files
callstack__agent-device/scripts/fuzz/envelope.test.ts
Michał Pierzchała 80b4769230 test(fuzz): structured CLI/Maestro generators that reach command validation and assert error codes (#1781 B2) (#1866)
* test(fuzz): structured CLI/Maestro generators that reach command validation and assert error codes (#1781 B2)

* test(fuzz): pin the rediscovered #1433 excess-positional case and keep numeric flag samples inside their range

* style: apply oxfmt to the new fuzz modules

* perf(fuzz): derive the CLI validation surface lazily so unrelated harness paths keep their startup

* test(fuzz): resolve validation generators in the run path so corpus replay keeps its small module graph

* test(fuzz): weight the CLI budget toward command validation, pin the finite classes as seeds, guard lazy surface derivation

* docs(testing): describe the validation lane's layer split, seed-pinned classes, and PR-time gates

* refactor(fuzz): split the validation generator into CLI and Maestro modules, mirrored in tests

* refactor(fuzz): collapse the flag-shaped mutation classes and seed literals, derive class coverage from declarations

* fix(fuzz): hash every case-generation module in configHash, guarded by an import-closure test

* test(fuzz): assert CLI command and flag-key coverage against the registry, and close the six gaps it found
2026-08-20 08:00:08 +02:00

69 lines
3.0 KiB
TypeScript

// The run envelope's provenance guarantees (#1414, #1781 B2).
//
// `configHash` exists so "the same seed means different inputs now" is distinguishable from "the
// parsers changed": a stale corpus that reads as confidence is the failure it prevents. That only
// holds while the hash covers every module deciding what a case contains — and the domain split
// broke it silently, because the modules it hashed stopped being where generation lived. This
// test derives the answer from the import graph instead of trusting a hand-kept list.
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { describe, expect, it } from 'vitest';
import { CASE_GENERATION_INPUTS, NON_GENERATING_MODULES } from './envelope.ts';
const FUZZ_DIR = path.dirname(fileURLToPath(import.meta.url));
/** Roots of case generation: the arbitraries, the seeds, the loop, and the violation rule. */
const ROOTS = ['arbitraries.ts', 'generate.ts', 'targets.ts', 'invariant.ts'] as const;
function localImportsOf(file: string): string[] {
const source = fs.readFileSync(path.join(FUZZ_DIR, file), 'utf8');
return [...source.matchAll(/from '\.\/([\w-]+\.ts)'/g)].map((match) => match[1]!);
}
/**
* Walk stops at a waived module: what a non-generating module imports cannot reach a case either
* (the runner imports the target registry, which would otherwise drag the whole harness in).
*/
function generationClosure(): Set<string> {
const seen = new Set<string>();
const queue = [...ROOTS];
while (queue.length > 0) {
const file = queue.pop()!;
if (seen.has(file)) continue;
seen.add(file);
if (file in NON_GENERATING_MODULES) continue;
queue.push(...localImportsOf(file));
}
return seen;
}
describe('configHash coverage', () => {
it('hashes every module reachable from the generation roots, or waives it with a reason', () => {
const covered = new Set<string>([
...CASE_GENERATION_INPUTS,
...Object.keys(NON_GENERATING_MODULES),
]);
const uncovered = [...generationClosure()].filter((file) => !covered.has(file)).sort();
expect(uncovered).toEqual([]);
});
it('waives nothing it also hashes, nothing unreachable, and explains every waiver', () => {
const closure = generationClosure();
for (const [file, reason] of Object.entries(NON_GENERATING_MODULES)) {
expect(CASE_GENERATION_INPUTS).not.toContain(file);
// A waiver for a module the roots no longer reach is dead configuration, and dead
// configuration is how the next reader learns the wrong thing about what is covered.
expect(closure, `${file} is waived but unreachable`).toContain(file);
expect(reason.trim().length).toBeGreaterThan(10);
}
});
it('lists only files that exist, so a renamed module fails here rather than hashing nothing', () => {
for (const file of [...CASE_GENERATION_INPUTS, ...Object.keys(NON_GENERATING_MODULES)]) {
expect(fs.existsSync(path.join(FUZZ_DIR, file)), file).toBe(true);
}
});
});