mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
006c4cadc9
* test: nightly parser fuzz lane with typed-AppError invariant (#1414) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(fuzz): run envelope, artifact promotion, and harness self-check tests (#1414) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(fuzz): shared scheduled-lane envelope on every terminal path, watchdog after ready (#1414) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(fuzz): envelope for malformed options; add scheduled-lane health consumer (#1414, #1430) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(lanes): actions:read scope, terminal error envelope, first-due grace (#1414, #1430) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(lanes): anchor first-run grace to schedule registration, use exec helper in tests (#1414, #1430) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(lanes): portable POSIX pickaxe pattern for schedule registration (#1414, #1430) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * refactor(fuzz): fast-check generators over the shared hazard list, drop the bundled lane-health work (#1414) - Strip scripts/scheduled-lane/* and scheduled-lane-health.yml: that watcher is #1430's own deliverable and collides with PR #1439's implementation of the same lane. What this lane owes (a per-run envelope) moves into scripts/fuzz/envelope.ts. - Rebase onto #1437 and rebuild the generator layer on fast-check: cases come from arbitraries sharing SELECTOR_VALUE_HAZARDS with the property suite, and counterexamples are shrunk, so a failure names a minimal input plus fast-check's seed/path instead of a 20k-char random string. - Route harness.test.ts into the serialized subprocess-stub project. - Drop the AGENT_DEVICE_FUZZ_STARTUP_DELAY_MS test seam: the ready handshake is now proven by a case budget far below real worker startup. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(fuzz): replay the regression corpus through the worker watchdog (#1414) A promoted hang case used to wedge the unit job until the CI timeout, because corpus replay called checkCase in-process. It now goes through the same worker-backed watchdog the nightly lane uses, so such a case fails against a 5s per-case budget; the file moves to the serialized subprocess-stub project with the rest of the worker-driven fuzz tests. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(fuzz): let the watchdog outlive vitest's default case timeout (#1414) A wedged parser was surfacing as a bare 'Test timed out in 5000ms' instead of the named hang: failure that says which input wedged, because the file's vitest timeout was shorter than the watchdog budget times the number of replayed cases. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(fuzz): complete drift provenance in the lane envelope (#1414) configHash now covers every input that decides what a seed generates (generate.ts and the shared property arbitraries, not just the arbitraries/targets/invariant), and tool records fast-check's installed version. A generation-loop edit or a fast-check upgrade previously changed the case set while the envelope looked unchanged. A test recomputes the hash with each input omitted so a future omission fails. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Michał Pierzchała <thymikee@gmail.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
111 lines
4.1 KiB
TypeScript
111 lines
4.1 KiB
TypeScript
// Parser fuzz targets (#1414).
|
|
//
|
|
// Every target takes one string case and calls a real parser. The harness owns the
|
|
// invariant (typed AppError with a non-empty hint, no hang); a target only says how a
|
|
// case string reaches its parser, and supplies the seed inputs the mutator chews on.
|
|
//
|
|
// Keep targets pure and synchronous: the hang watchdog (scripts/fuzz/worker.ts) can only
|
|
// attribute a stall to a case if the case runs to completion in one tick.
|
|
|
|
import { parseArgs } from '../../src/cli/parser/args.ts';
|
|
import { parseSelectorChain } from '../../src/selectors/parse.ts';
|
|
import { parseReplayScriptDetailed } from '../../src/replay/script.ts';
|
|
import { readCliBatchStepsJson } from '../../src/cli/batch-steps.ts';
|
|
import { parseMaestroProgram } from '../../src/compat/maestro/program-ir-parser.ts';
|
|
import type { FuzzTarget } from './target-types.ts';
|
|
|
|
// argv is carried as one string so a case (and its corpus entry, artifact, and repro
|
|
// command) stays a single copy-pasteable value. Splitting on spaces is deliberate: the
|
|
// fuzzer wants odd tokens, not a faithful shell grammar.
|
|
function toArgv(input: string): string[] {
|
|
return input.split(' ').filter((token) => token.length > 0);
|
|
}
|
|
|
|
export const FUZZ_TARGETS: readonly FuzzTarget[] = [
|
|
{
|
|
name: 'cli-args',
|
|
description: 'parseArgs (strict flags)',
|
|
run: (input) => void parseArgs(toArgv(input), { strictFlags: true }),
|
|
seeds: [
|
|
'click --selector text=Login',
|
|
'open com.example.app --platform ios --json',
|
|
'snapshot --depth 3 --format text',
|
|
'fill @e1 --text hello --submit',
|
|
'batch --steps [] --timeout 1000',
|
|
'devices --platform android --json --debug',
|
|
'wait --selector role=button --timeout-ms 500',
|
|
'test replays -- --raw --passthrough',
|
|
'click --selector',
|
|
'--json',
|
|
'',
|
|
],
|
|
},
|
|
{
|
|
name: 'selector',
|
|
description: 'parseSelectorChain',
|
|
run: (input) => void parseSelectorChain(input),
|
|
seeds: [
|
|
'text=Login',
|
|
'label="Sign in" && role=button',
|
|
'text=Save || label=Done',
|
|
'id=com.example:id/button[2]',
|
|
'role=button and is=enabled',
|
|
'text~=partial',
|
|
'label="quoted \\"inner\\" value"',
|
|
'@e1',
|
|
'text=🚀',
|
|
'text=',
|
|
'',
|
|
],
|
|
},
|
|
{
|
|
name: 'replay-script',
|
|
description: 'parseReplayScriptDetailed (.ad scripts)',
|
|
run: (input) => void parseReplayScriptDetailed(input),
|
|
seeds: [
|
|
'open com.example.app\nclick text=Login\nclose\n',
|
|
'# context platform=ios target=mobile\nopen com.example.app\n',
|
|
'# context timeoutMs=1000 retries=2\nsnapshot\n',
|
|
'# target-v1 role=button label=Login\nclick @e1\n',
|
|
'fill @e1 --text "hello world"\nassert text=Welcome\n',
|
|
'swipe 10 20 30 40\nwait 250\n',
|
|
'env FOO=bar\nclick text=${FOO}\n',
|
|
'screenshot --quality low\n',
|
|
'# target-v1 role=button\n\nclick @e1\n',
|
|
'',
|
|
],
|
|
},
|
|
{
|
|
name: 'batch-steps',
|
|
description: 'readCliBatchStepsJson (batch --steps)',
|
|
run: (input) => void readCliBatchStepsJson(input),
|
|
seeds: [
|
|
'[{"command":"snapshot","input":{}}]',
|
|
'[{"command":"click","input":{"selector":"text=Login"}}]',
|
|
'[{"command":"open","positionals":["com.example.app"],"flags":{"json":true}}]',
|
|
'[{"command":"snapshot","input":{}},{"command":"close","input":{}}]',
|
|
'[{"command":"wait","input":{"timeoutMs":250}}]',
|
|
'[]',
|
|
'{}',
|
|
'not json',
|
|
'',
|
|
],
|
|
},
|
|
{
|
|
name: 'maestro',
|
|
description: 'parseMaestroProgram (Maestro compat)',
|
|
run: (input) => void parseMaestroProgram(input, { sourcePath: 'fuzz.yaml' }),
|
|
seeds: [
|
|
'appId: com.example.app\n---\n- launchApp\n- tapOn: "Login"\n',
|
|
'appId: com.example.app\n---\n- tapOn:\n id: "login"\n',
|
|
'appId: com.example.app\n---\n- inputText: "hello"\n- assertVisible: "Welcome"\n',
|
|
'appId: com.example.app\n---\n- swipe:\n direction: UP\n',
|
|
'appId: com.example.app\n---\n- runFlow: other.yaml\n',
|
|
'appId: com.example.app\n---\n- repeat:\n times: 2\n commands:\n - back\n',
|
|
'- launchApp\n',
|
|
'appId: com.example.app\n---\n',
|
|
'',
|
|
],
|
|
},
|
|
];
|