Skip to content

Commit f49d30f

Browse files
aarneCopilotCopilot
authored
New test harness (#127)
* New test harness * AI is fucking useless here * Testing with AI is pure agony * Migrate bridge e2e tests from forEachEngine to regressionTest harness (#126) * Initial plan * Migrate fallback-bug.test.ts from forEachEngine to regressionTest Co-authored-by: aarne <82001+aarne@users.noreply.github.com> * Migrate chained.test.ts from forEachEngine to regressionTest Co-authored-by: aarne <82001+aarne@users.noreply.github.com> * Migrate string-interpolation.test.ts and interpolation-universal.test.ts to regressionTest Co-authored-by: aarne <82001+aarne@users.noreply.github.com> * Migrate prototype-pollution.test.ts from forEachEngine Co-authored-by: aarne <82001+aarne@users.noreply.github.com> * Migrate ternary.test.ts from forEachEngine to regressionTest (partial - 2 trace assertions need engine-specific handling) Co-authored-by: aarne <82001+aarne@users.noreply.github.com> * Fix ternary refFallback trace assertion: runtime lazy vs compiler eager Co-authored-by: aarne <82001+aarne@users.noreply.github.com> * refactor: migrate expressions.test.ts from forEachEngine to regressionTest Migrated the following test groups to the regressionTest harness: - expressions: execution (arithmetic, comparisons, tool source, chained) - expressions: operator precedence - boolean logic: not prefix - parenthesized arithmetic - safe flag propagation in expressions (with allowDowngrade) - safe navigation with sync tool (with allowDowngrade) Tests that cannot be migrated due to compiler limitations remain as forEachEngine blocks: - String literal comparison (serializer strips quotes) - Array mapping with expressions (serializer bug) - catch error fallback (compiler doesn't downgrade) - and/or boolean logic (compiler generates broken __and/__or) - Parenthesized boolean expressions (contain and/or) - condAnd/condOr without safe navigation - Safe flag on right operand of and/or Side-effect tests (short-circuit behavior) moved to test/legacy/expressions.test.ts per migration guidelines. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor: migrate force-wire tests to regressionTest harness - Tests 1,2,4,5 migrated to regressionTest with test.multitool - Tests 3 (timing) and 6 (runtime-skip) moved to legacy/force-wire.test.ts - All 6 original test scenarios preserved Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Move remaining forEachEngine tests to legacy/ with README.md of action items Co-authored-by: aarne <82001+aarne@users.noreply.github.com> * Migrate infinite-loop-protection.test.ts to regressionTest (partial) Co-authored-by: aarne <82001+aarne@users.noreply.github.com> * Fix infinite-loop-protection.test.ts: fix traversal coverage and graphql replay failures Co-authored-by: aarne <82001+aarne@users.noreply.github.com> * Fix serializer: support expressions, ternary, coalesce, string interpolation in tool self-wires Co-authored-by: aarne <82001+aarne@users.noreply.github.com> * test: migrate tool-self-wires-runtime to regressionTest harness Move test/legacy/tool-self-wires-runtime.test.ts to test/tool-self-wires-runtime.test.ts using the regressionTest pattern with test.multitool from bridge-tools.ts. All 9 test cases are consolidated into a single regressionTest block with shared const declarations and per-scenario assertions. The compiler handles all cases natively so allowDowngrade is not needed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: migrate native-batching tests to regressionTest harness - Migrate all 3 tests from forEachEngine to regressionTest pattern - Fix serializer bug: loop-scoped tool handles now round-trip correctly - Add element property to HandleBinding type for tool handles - Mark element-scoped tools in parser (processLocalToolBindings) - Serializer skips element handles in header, emits inside array blocks - Serializer correctly identifies element-scoped tool wires - Delete legacy/native-batching.test.ts Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update legacy README with migration patterns and remove migrated files Co-authored-by: aarne <82001+aarne@users.noreply.github.com> * New tsc setup * Fix build * feat: add bridge-types dependency to bridge-compiler and update pnpm-lock * feat: refactor multitool functions for improved error handling and cleanup * Fix graphql control flog bug * Control flow tests are migrated * Test structure * feat: more compiler coverage * feat: enhance error handling and add new regression tests for expressions * fix: fuzzer * Tests * Some progress * Move back to legacy * fix tests * fix: update tools type to Record<string, unknown> in buildAotFn and compileAndRun * Hallukad jalle * Test stability * Broke a bunch of things * Fixed some stuff * Broke some stuff/ fixed some more stuff * Fixed more stuff * Broke some stuff again * Did not really mnage to fix all * Half fixes * Now to graphql * graphql tessts --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: aarne <82001+aarne@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Aarne Laur <aarne.laur@gmail.com> --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: aarne <82001+aarne@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 6b32109 commit f49d30f

122 files changed

Lines changed: 17551 additions & 13855 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/benchmark.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
run: pnpm install
2828

2929
- name: Run benchmarks
30-
run: cd packages/bridge && CI=true node --experimental-transform-types --conditions source bench/engine.bench.ts > bench-results.json 2>/dev/null
30+
run: cd packages/bridge && CI=true node --experimental-transform-types bench/engine.bench.ts > bench-results.json 2>/dev/null
3131

3232
- name: Upload benchmark results
3333
uses: actions/upload-artifact@v4
@@ -75,7 +75,7 @@ jobs:
7575
run: pnpm install
7676

7777
- name: Run benchmarks
78-
run: cd packages/bridge && CI=true node --experimental-transform-types --conditions source bench/engine.bench.ts > bench-results.json 2>/dev/null
78+
run: cd packages/bridge && CI=true node --experimental-transform-types bench/engine.bench.ts > bench-results.json 2>/dev/null
7979

8080
- uses: bencherdev/bencher@main
8181

AGENTS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ playground/ Browser playground (Vite + React)
6363

6464
**Run a single test file:**
6565
```bash
66-
node --experimental-transform-types --conditions source --test test/<filename>.test.ts
66+
node --experimental-transform-types --test test/<filename>.test.ts
6767
```
6868

6969
Tests are **co-located with each package**. The main test suites:
@@ -77,7 +77,7 @@ Tests are **co-located with each package**. The main test suites:
7777

7878
- **ESM** (`"type": "module"`) with `.ts` import extensions (handled by `rewriteRelativeImportExtensions`)
7979
- **Strict mode**`noUnusedLocals`, `noUnusedParameters`, `noImplicitReturns`, `noFallthroughCasesInSwitch`
80-
- **Dev running:** `--experimental-transform-types --conditions source`
80+
- **Dev running:** `--experimental-transform-types`
8181
- **Path mappings:** `tsconfig.base.json` maps `@stackables/*` for cross-package imports
8282

8383
## Deep-dive docs

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,11 @@ const schema = bridgeTransform(createSchema({ typeDefs }), instructions, {
119119
```
120120

121121
**[Read the Tools & Extensions Guide](https://bridge.sdk42.com/advanced/custom-tools/)**
122+
123+
## Testing Prompt
124+
125+
The reason we write tests is to catch bugs so we can fix them — not to document broken behavior and ship it.
126+
127+
We never hide problems or avoid broken scenarios to make tests pass.
128+
129+
It is always better to not ship and have broken tests than to break our users trust.

docs/fuzz-testing.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,9 @@ When a fuzz run finds a new issue:
108108
pnpm test
109109

110110
# Single fuzz file
111-
node --experimental-transform-types --conditions source --test packages/bridge-compiler/test/fuzz-runtime-parity.test.ts
112-
node --experimental-transform-types --conditions source --test packages/bridge/test/fuzz-parser.test.ts
113-
node --experimental-transform-types --conditions source --test packages/bridge-stdlib/test/fuzz-stdlib.test.ts
111+
node --experimental-transform-types --test packages/bridge-compiler/test/fuzz-runtime-parity.test.ts
112+
node --experimental-transform-types --test packages/bridge/test/fuzz-parser.test.ts
113+
node --experimental-transform-types --test packages/bridge-stdlib/test/fuzz-stdlib.test.ts
114114

115115
# Reproduce a specific failing seed
116116
# Add { seed: -1234567, path: "0", endOnFailure: true } to fc.assert options

docs/profiling.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@ Use the focused profiling target instead:
470470
```bash
471471
# Runs a single scenario in a tight loop — cleaner profiles
472472
BRIDGE_PROFILE_FILTER="flat array 1000" BRIDGE_PROFILE_ITERATIONS=10000 \
473-
node --experimental-transform-types --conditions source \
473+
node --experimental-transform-types \
474474
--cpu-prof --cpu-prof-dir profiles --cpu-prof-interval 50 \
475475
scripts/profile-target.mjs
476476
```

docs/test-migration-playbook.md

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
# Test Migration Playbook: Legacy → regressionTest
2+
3+
Migrate `packages/bridge/test/legacy/*.test.ts` to the `regressionTest` framework.
4+
5+
## Prerequisites
6+
7+
- Read `packages/bridge/test/utils/regression.ts` (the framework — DO NOT EDIT)
8+
- Read `packages/bridge/test/utils/bridge-tools.ts` (test multitools)
9+
- Study `packages/bridge/test/coalesce-cost.test.ts` as the gold-standard example
10+
11+
## Step-by-step process
12+
13+
### 1. Categorise every test in the legacy file
14+
15+
Read the file and sort each test into one of these buckets:
16+
17+
| Bucket | Action |
18+
| ------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
19+
| **Parser-only** (parses AST, checks wire structure) | DELETE — regressionTest's `parse → serialise → parse` covers this automatically |
20+
| **Serializer roundtrip** (parse → serialize → parse) | DELETE — regressionTest does this automatically |
21+
| **Runtime execution** (runs bridge, asserts data/errors) | MIGRATE to `regressionTest` scenarios |
22+
| **Non-runtime tests** (class constructors, pure unit tests) | MOVE to the corresponding package test dir (e.g. `bridge-core/test/`, `bridge-parser/test/`) |
23+
| **Tests requiring custom execution** (AbortSignal, custom contexts) | Keep using `forEachEngine` in the new file |
24+
25+
### 2. Design bridges for regressionTest
26+
27+
Group related runtime-execution tests into **logical regressionTest blocks**. Each block has:
28+
29+
```typescript
30+
regressionTest("descriptive name", {
31+
bridge: `
32+
version 1.5
33+
bridge Operation.field {
34+
with test.multitool as a
35+
with input as i
36+
with output as o
37+
// ... wires
38+
}
39+
`,
40+
tools, // import { tools } from "./utils/bridge-tools.ts"
41+
scenarios: {
42+
"Operation.field": {
43+
"scenario name": { input: {...}, assertData: {...}, assertTraces: N },
44+
},
45+
},
46+
});
47+
```
48+
49+
**Design rules:**
50+
51+
- One regressionTest can have **multiple bridges** (multiple operations in scenarios)
52+
- Group by **feature/behavior** (e.g. "throw control flow", "continue/break in arrays")
53+
- Each bridge needs enough scenarios to achieve **traversal coverage** (all non-error paths hit)
54+
- Keep bridge definitions minimal — test one concept per wire
55+
56+
### 3. Use test.multitool everywhere possible
57+
58+
The multitool (`with test.multitool as a`) is a passthrough: input → output (minus `_`-prefixed keys).
59+
60+
**Capabilities:**
61+
62+
- `_error`: `input: { a: { _error: "boom" } }` → tool throws `Error("boom")`
63+
- `_delay`: `input: { a: { _delay: 100, name: "A" } }` → delays 100ms, returns `{ name: "A" }`
64+
- All other `_` keys are stripped from output
65+
- Correctly handles nested objects and arrays
66+
67+
**Wiring pattern:**
68+
69+
```
70+
a <- i.a // sends i.a as input to tool, tool returns cleaned copy
71+
o.x <- a.y // reads .y from tool output
72+
```
73+
74+
**Only use custom tool definitions when:**
75+
76+
- You need a tool that transforms data (not passthrough)
77+
- You need AbortSignal handling on the tool side
78+
- You need `ctx.signal` inspection
79+
80+
### 4. Write scenarios
81+
82+
Each scenario needs:
83+
84+
| Field | Required | Description |
85+
| ---------------- | -------- | ----------------------------------------------------------------------- |
86+
| `input` | Yes | Input object passed to bridge |
87+
| `assertTraces` | Yes | Number of tool calls (or function for custom check) |
88+
| `assertData` | No | Expected output data (object or function) |
89+
| `assertError` | No | Expected error (regex or function) — mutually exclusive with assertData |
90+
| `fields` | No | Restrict which output fields are resolved |
91+
| `context` | No | Context values (for `with context as ctx`) |
92+
| `tools` | No | Per-scenario tool overrides |
93+
| `allowDowngrade` | No | Set `true` if compiler can't handle this bridge feature |
94+
| `assertGraphql` | No | GraphQL-specific expectations (object or function) |
95+
| `assertLogs` | No | Log assertions |
96+
97+
**assertData shorthand:** For simple cases, use object literal:
98+
99+
```typescript
100+
assertData: { name: "Alice", age: 30 }
101+
```
102+
103+
**assertError with regex:** Matches against `${error.name} ${error.message}`:
104+
105+
```typescript
106+
assertError: /BridgeRuntimeError/; // matches error name
107+
assertError: /name is required/; // matches error message
108+
assertError: /BridgePanicError.*fatal/; // matches both
109+
```
110+
111+
**assertError with function** (for instanceof checks):
112+
113+
```typescript
114+
assertError: (err: any) => {
115+
assert.ok(err instanceof BridgePanicError);
116+
assert.equal(err.message, "fatal");
117+
};
118+
```
119+
120+
**fields for isolating wires:** When one wire throws but others don't, use `fields` to test them separately:
121+
122+
```typescript
123+
"error on fieldA only": {
124+
input: { ... },
125+
fields: ["fieldA"], // only resolve this field
126+
assertError: /message/,
127+
assertTraces: 0,
128+
},
129+
```
130+
131+
### 5. Handle traversal coverage
132+
133+
The framework automatically checks that all non-error traversal paths are covered. Common uncovered paths:
134+
135+
- **empty-array**: Add a scenario with an empty array: `input: { a: { items: [] } }`
136+
- **Fallback paths**: Add a scenario where each fallback fires
137+
- **Short-circuit paths**: Add scenarios for each branch of ||/?? chains
138+
139+
If traversal coverage fails, the error message tells you exactly which paths are missing.
140+
141+
### 6. Handle compiler downgrade
142+
143+
The compiled engine doesn't support all features. When the compiler downgrades, add `allowDowngrade: true` to the scenario. Common triggers:
144+
145+
- `?.` (safe execution modifier) without `catch`
146+
- Some complex expressions
147+
- Certain nested array patterns
148+
149+
**Important:** `allowDowngrade` applies per-scenario, but the bridge is shared. If ANY wire in the bridge triggers downgrade, ALL scenarios need `allowDowngrade: true`.
150+
151+
### 7. Handle errors in GraphQL
152+
153+
as graphql has partial errors then we need to assert it separately
154+
155+
```typescript
156+
assertGraphql: {
157+
fieldA: /error message/i, // expect GraphQL error for this field
158+
fieldB: "fallback-value", // expect this value
159+
}
160+
```
161+
162+
### 8. Move non-runtime tests
163+
164+
Tests that don't invoke the bridge execution engine belong in the corresponding package:
165+
166+
| Test type | Target |
167+
| ------------------------ | -------------------------------------------------- |
168+
| Error class constructors | `packages/bridge-core/test/execution-tree.test.ts` |
169+
| Parser AST structure | `packages/bridge-parser/test/` |
170+
| Serializer output format | `packages/bridge-parser/test/` |
171+
| Type definitions | `packages/bridge-types/test/` |
172+
173+
### 9. Final verification
174+
175+
```bash
176+
pnpm build # 0 type errors
177+
pnpm lint # 0 lint errors
178+
pnpm test # 0 failures
179+
```
180+
181+
Run the specific test file first for fast iteration:
182+
183+
```bash
184+
node --experimental-transform-types --test packages/bridge/test/<new-file>.test.ts
185+
```
186+
187+
## Migration checklist template
188+
189+
For each legacy test file:
190+
191+
- [ ] Read and categorise all tests
192+
- [ ] Delete parser-only and roundtrip tests (covered by regressionTest)
193+
- [ ] Design bridges using test.multitool
194+
- [ ] Write scenarios with correct assertions
195+
- [ ] Ensure traversal coverage (add empty-array, fallback scenarios)
196+
- [ ] Add `allowDowngrade: true` where compiler downgrades
197+
- [ ] Handle GraphQL replay bugs with `assertGraphql: () => {}`
198+
- [ ] Move non-runtime tests to corresponding package
199+
- [ ] Keep tests needing custom execution (AbortSignal) using `forEachEngine`
200+
- [ ] Verify: `pnpm build && pnpm lint && pnpm test`
201+
- [ ] Don't delete the legacy file until confirmation
202+
203+
## Files remaining to migrate
204+
205+
```
206+
packages/bridge/test/legacy/ # check for remaining legacy tests
207+
packages/bridge/test/expressions.test.ts # if still using forEachEngine
208+
packages/bridge/test/infinite-loop-protection.test.ts # if still using forEachEngine
209+
```

examples/builtin-tools/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
"private": true,
44
"type": "module",
55
"scripts": {
6-
"start": "node --experimental-transform-types --conditions source server.ts",
7-
"e2e": "node --experimental-transform-types --conditions source --test e2e.test.ts"
6+
"start": "node --experimental-transform-types server.ts",
7+
"e2e": "node --experimental-transform-types --test e2e.test.ts"
88
},
99
"dependencies": {
1010
"@stackables/bridge": "workspace:*",

examples/composed-gateway/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
"private": true,
44
"type": "module",
55
"scripts": {
6-
"start": "node --experimental-transform-types --conditions source server.ts",
7-
"e2e": "node --experimental-transform-types --conditions source --test e2e.test.ts"
6+
"start": "node --experimental-transform-types server.ts",
7+
"e2e": "node --experimental-transform-types --test e2e.test.ts"
88
},
99
"dependencies": {
1010
"@stackables/bridge": "workspace:*",

examples/travel-api/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
"private": true,
44
"type": "module",
55
"scripts": {
6-
"start": "node --experimental-transform-types --conditions source server.ts",
7-
"e2e": "node --experimental-transform-types --conditions source --test e2e.test.ts"
6+
"start": "node --experimental-transform-types server.ts",
7+
"e2e": "node --experimental-transform-types --test e2e.test.ts"
88
},
99
"dependencies": {
1010
"@stackables/bridge": "workspace:*",

examples/weather-api/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
"private": true,
44
"type": "module",
55
"scripts": {
6-
"start": "node --experimental-transform-types --conditions source server.ts",
7-
"e2e": "node --experimental-transform-types --conditions source --test e2e.test.ts"
6+
"start": "node --experimental-transform-types server.ts",
7+
"e2e": "node --experimental-transform-types --test e2e.test.ts"
88
},
99
"dependencies": {
1010
"@stackables/bridge": "workspace:*",

0 commit comments

Comments
 (0)