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
52 lines
2.4 KiB
TypeScript
52 lines
2.4 KiB
TypeScript
// The contract every validation generator owes, as a reusable suite (#1781 B2).
|
|
//
|
|
// Both generators must be replayable, must hold every expectation they plant against the real
|
|
// parser, and must keep firing every class they declare. That is one contract, so it is asserted
|
|
// from one place — the domain-specific reach and layer assertions stay in each generator's own
|
|
// test file, which is where the knowledge that differs lives.
|
|
|
|
import type fc from 'fast-check';
|
|
import { sample } from 'fast-check';
|
|
import { describe, expect, it } from 'vitest';
|
|
import { describeFailure } from './invariant.ts';
|
|
import { getFuzzTarget } from './registry.ts';
|
|
import type { ParserTargetName } from './target-types.ts';
|
|
import { decodeValidationCase } from './validation-case.ts';
|
|
|
|
export function describeGeneratorContract(input: {
|
|
targetName: ParserTargetName;
|
|
arbitrary: fc.Arbitrary<string>;
|
|
/** The classes the generator declares; coverage is checked against it, not a snapshot. */
|
|
declaredClasses: readonly string[];
|
|
sample: readonly string[];
|
|
}): void {
|
|
const { targetName, arbitrary, declaredClasses, sample: cases } = input;
|
|
const target = getFuzzTarget(targetName);
|
|
|
|
describe(`${targetName} generator`, () => {
|
|
it('is deterministic for a seed, so a reported counterexample replays', () => {
|
|
const again = sample(arbitrary, { numRuns: 32, seed: 7 });
|
|
expect(again).toEqual(sample(arbitrary, { numRuns: 32, seed: 7 }));
|
|
expect(again).not.toEqual(sample(arbitrary, { numRuns: 32, seed: 8 }));
|
|
});
|
|
|
|
it('produces decodable envelopes whose expectations hold on a healthy tree', () => {
|
|
const failures = [];
|
|
for (const entry of cases) {
|
|
expect(decodeValidationCase(entry)).not.toBeNull();
|
|
const failure = target.check!(entry);
|
|
if (failure) failures.push(describeFailure(failure));
|
|
}
|
|
expect(failures).toEqual([]);
|
|
});
|
|
|
|
// Checked against the generator's own declaration rather than a pinned snapshot: a class that
|
|
// stops firing is dead coverage, and a snapshot of the class list only restates the source.
|
|
it('fires every class it declares, and still generates valid accept cases', () => {
|
|
const fired = new Set(cases.map((entry) => decodeValidationCase(entry)!.mutation));
|
|
expect([...declaredClasses].sort().filter((name) => !fired.has(name))).toEqual([]);
|
|
expect(fired).toContain('valid');
|
|
});
|
|
});
|
|
}
|