mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
a904ef0d5d
The unit-lane corpus replay executed adversarial parser cases on worker threads of the Vitest worker running the test file. A fault in a worker thread ends its whole process, so a case that faulted killed the test runner: `[vitest-pool]: Worker forks emitted error / Worker exited unexpectedly`, with no test, file, or case named. Six of six Coverage deaths before #1994's split were this one file out of ~1100, and the uninstrumented second leg it created then lost the same file six more times in three days. Cases now run in a worker *process*. The two faults a case cannot report about itself are both classified from outside it: a case that never returns is a `hang` (unchanged), and one that ends the process it runs in is a new `crash` failure carrying the exit code or signal and the tail of the worker's stderr — the death certificate the lane used to lose. A sixth self-check target seeds that kind, so a regression in reporting it fails the harness self-check like every other kind.
95 lines
3.6 KiB
TypeScript
95 lines
3.6 KiB
TypeScript
// Broken-on-purpose targets that prove the harness can still fail (#1414).
|
|
//
|
|
// A fuzz lane whose classifier or watchdog regresses goes green forever, which is worse than
|
|
// no lane at all. These targets each violate the invariant one way, so `--self-check` (and the
|
|
// unit test in harness.test.ts) can assert the harness reports every failure kind. They are
|
|
// deliberately kept out of FUZZ_TARGETS: only the registry resolves them by name, so a normal
|
|
// run never sees them and no production module gains a test-only seam.
|
|
|
|
import { AppError } from '@agent-device/kernel/errors';
|
|
import type { FuzzTarget, SelfCheckTargetName } from './target-types.ts';
|
|
import { judgeValidationCase } from './validation-case.ts';
|
|
|
|
const SEEDS = ['self-check'];
|
|
|
|
export const SELF_CHECK_TARGETS: readonly FuzzTarget[] = [
|
|
{
|
|
name: 'self-check-untyped-throw',
|
|
description: 'always throws a bare Error (expects an untyped-throw failure)',
|
|
run: () => {
|
|
throw new Error('self-check untyped throw');
|
|
},
|
|
seeds: SEEDS,
|
|
},
|
|
{
|
|
name: 'self-check-empty-hint',
|
|
description: 'always throws an AppError without a hint (expects an empty-hint failure)',
|
|
run: () => {
|
|
throw new AppError('INVALID_ARGS', 'self-check missing hint', { hint: ' ' });
|
|
},
|
|
seeds: SEEDS,
|
|
},
|
|
{
|
|
name: 'self-check-hang',
|
|
description: 'never returns (expects a hang failure)',
|
|
run: () => {
|
|
for (;;) {
|
|
// Spin forever: only the out-of-process watchdog can end this case.
|
|
}
|
|
},
|
|
seeds: SEEDS,
|
|
},
|
|
{
|
|
// The kind no in-process classifier can produce: the case ends the process it runs in, so
|
|
// only a runner watching from another process reports anything at all. `process.exit` stands
|
|
// in for the real thing (a native fault) because it is deterministic and portable — the
|
|
// runner classifies any death of a worker with a case in flight, whatever ended it.
|
|
name: 'self-check-crash',
|
|
description: 'ends the process mid-case (expects a crash failure)',
|
|
run: () => process.exit(97),
|
|
seeds: SEEDS,
|
|
},
|
|
{
|
|
// A validation judge that stops reporting silent acceptances would leave the #1433 class
|
|
// undetectable again; this target accepts an input its expectation marks invalid.
|
|
name: 'self-check-silent-accept',
|
|
description: 'accepts a case marked invalid (expects a silent-accept failure)',
|
|
run: () => {},
|
|
check: (input) =>
|
|
judgeValidationCase(
|
|
'self-check-silent-accept',
|
|
input,
|
|
{ mutation: 'seeded', expect: { outcome: 'reject', code: 'INVALID_ARGS' } },
|
|
() => {},
|
|
),
|
|
seeds: SEEDS,
|
|
},
|
|
{
|
|
name: 'self-check-wrong-code',
|
|
description: 'rejects with a different code than expected (expects a wrong-code failure)',
|
|
run: () => {
|
|
throw new AppError('COMMAND_FAILED', 'self-check wrong code', { hint: 'seeded' });
|
|
},
|
|
check: (input) =>
|
|
judgeValidationCase(
|
|
'self-check-wrong-code',
|
|
input,
|
|
{ mutation: 'seeded', expect: { outcome: 'reject', code: 'INVALID_ARGS' } },
|
|
() => {
|
|
throw new AppError('COMMAND_FAILED', 'self-check wrong code', { hint: 'seeded' });
|
|
},
|
|
),
|
|
seeds: SEEDS,
|
|
},
|
|
];
|
|
|
|
/** The failure kind each self-check target must produce, keyed by target name. */
|
|
export const SELF_CHECK_EXPECTATIONS = {
|
|
'self-check-untyped-throw': 'untyped-throw',
|
|
'self-check-empty-hint': 'empty-hint',
|
|
'self-check-hang': 'hang',
|
|
'self-check-crash': 'crash',
|
|
'self-check-silent-accept': 'silent-accept',
|
|
'self-check-wrong-code': 'wrong-code',
|
|
} as const satisfies Record<SelfCheckTargetName, string>;
|