Reference document for fuzz/property-based testing coverage and workflow in the Bridge codebase.
Bridge uses fast-check (^4.5.3) for property-based testing alongside the standard node:test + node:assert framework. Fuzz tests live co-located with each package and run as part of the normal pnpm test suite.
| File | Package | Purpose |
|---|---|---|
test/fuzz-compile.test.ts |
bridge-compiler |
JS syntax validity, determinism, flat-path AOT/runtime parity |
test/fuzz-runtime-parity.test.ts |
bridge-compiler |
Deep-path parity, array mapping parity, loop-tool parity and fallback, tool-call timeout parity |
test/fuzz-regressions.todo.test.ts |
bridge-compiler |
Backlog of known fuzz-discovered divergences as test.todo entries |
test/fuzz-stdlib.test.ts |
bridge-stdlib |
Array and string tool crash-safety |
test/fuzz-parser.test.ts |
bridge |
Parser crash-safety, serializer round-trip, formatter stability, loop-scoped tool syntax |
- JS syntax validity of AOT compiler output
- Compiler determinism
- AOT/runtime parity on flat single-segment paths (
fc.jsonValue()inputs) - AOT/runtime parity on deep multi-segment paths with chaotic inputs (
NaN,Infinity,-0,undefined, deeply nested objects) - AOT/runtime parity on array-mapping bridges (
[] as el { ... }) with chaotic element data - AOT/runtime parity on compiler-compatible loop-scoped tool bridges
- Fallback parity for compiler-incompatible loop-scoped tool bridges that use nested loop-local tools, memoized handles, or shadowed loop-local tool aliases
- AOT/runtime parity on tool-call timeout (
BridgeTimeoutErrorclass and message match) - Parser round-trip: text → parse → serialize → reparse → execute parity
parseBridgenever throws unstructured errors on random inputparseBridgeDiagnosticsnever throws (LSP/IDE safety)prettyPrintToSourceidempotence and output parseability (bridge, tool, const blocks)prettyPrintToSourcestability for loop-scopedwith ... memoizedeclarations inside array mappings, plusserializeBridgeround-trip coverage for non-shadowed loop-scoped handlesarr.filter,arr.find,arr.first,arr.toArraycrash-safety on any input typestr.toLowerCase,str.toUpperCase,str.trim,str.lengthcrash-safety on any input type
Symbol,BigInt, circular-ref handling across all stdlib toolsparseBridgeDiagnosticscompleteness: valid input should produce zero error-severity diagnostics- Randomized fallback/nullish edge cases for every compiler-incompatible shape beyond loop-scoped tool handles
| Test | Runs |
|---|---|
| Deep-path AOT/runtime parity | 3,000 |
| Array mapping parity | 1,000 |
| Loop-scoped tool AOT/runtime parity | 400 |
| Loop-scoped tool fallback parity | 300 |
| Tool-call timeout parity | 500 |
parseBridge never panics |
5,000 |
parseBridgeDiagnostics never throws |
5,000 |
| Serializer round-trip | 2,000 |
prettyPrintToSource idempotence (basic) |
2,000 |
prettyPrintToSource parseability (basic) |
2,000 |
prettyPrintToSource idempotence (extended blocks) |
1,000 |
prettyPrintToSource parseability (extended blocks) |
1,000 |
| Loop-scoped tool round-trip / formatter properties | 1,000 |
| stdlib tool crash-safety (per tool) | 2,000 |
Text-first over AST-first. Generating valid .bridge text strings and parsing them is preferred over building Bridge AST objects directly with fc.letrec. Text-first generation avoids exponential shrinking blowup: fast-check shrinks by removing tokens from a string, not by exploring recursive AST tree variants. This is especially important for array mapping and nested-block tests.
Depth limits with fc.letrec. When recursive arbitraries are necessary (e.g. chaosValueArb for deep input objects), always pass depthFactor or cap with maxLength/maxKeys at every level. Without this, the shrinking phase can explore exponentially many candidates and halt CI.
Safety margins for timing tests. Timer-based parity tests skip inputs in the "grey zone" where |toolDelay - toolTimeout| < 20ms to avoid flakiness on slow CI runners.
Forbidden path segments. All generated identifier arbitraries filter out __proto__, prototype, and constructor to stay within the valid domain for path traversal.
When a fuzz run finds a new issue:
-
Capture evidence immediately — seed, failure path, counterexample input, whether it is
AOT != runtime, a parser crash, or a runtime panic. -
Add a
test.todoentry inpackages/bridge-compiler/test/fuzz-regressions.todo.test.ts:test.todo("class label — short description (seed=123456)");
-
Open a tracking note — link to the todo, add impact, expected vs actual behaviour, suspected component.
-
Create a deterministic reproducer — prefer a minimal hand-authored bridge + input over rerunning fuzz with a seed. Add it to
codegen.test.tsor a dedicated regression file as a normaltest(...). -
Fix at root cause — keep fixes small and targeted.
-
Promote and clean up — ensure reproducer passes, remove the
test.todoentry, keep the fuzz property in place.
# All tests (includes fuzz)
pnpm test
# Single fuzz file
node --experimental-transform-types --conditions source --test packages/bridge-compiler/test/fuzz-runtime-parity.test.ts
node --experimental-transform-types --conditions source --test packages/bridge/test/fuzz-parser.test.ts
node --experimental-transform-types --conditions source --test packages/bridge-stdlib/test/fuzz-stdlib.test.ts
# Reproduce a specific failing seed
# Add { seed: -1234567, path: "0", endOnFailure: true } to fc.assert options- Test framework:
node:test+node:assert(no Jest/Vitest) - Fuzz library:
fast-check^4.5.3 — devDependency ofbridge-compiler,bridge-stdlib,bridge - Parser fuzz tests live in
packages/bridge/test/ - Stdlib fuzz tests live in
packages/bridge-stdlib/test/ - Compiler parity fuzz tests live in
packages/bridge-compiler/test/