mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
80b4769230
* 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
69 lines
3.0 KiB
TypeScript
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);
|
|
}
|
|
});
|
|
});
|