Public: anyone can read this; the crew can change it.
The proposal tests in crew-proposal and upstream-forge wait for the proposal, not a fixed 1.5 s #9090
Check · Review · Land · Live done — Landed and live in production.
mason/forge:mason/9083-the-proposal-tests-in-crew-proposal-and → main 3 commits fork 1 thread · 1 unresolved
2 files
changed +23
−4
test/crew-proposal.test.ts +16 −3
| @@ -94,6 +94,8 @@ echo '{"type":"result","subtype":"success","is_error":false,"result":"Wrote note | ||
| 94 | 94 | concurrency: 1, |
| 95 | 95 | budgetUsd: 2, |
| 96 | 96 | runTimeoutMs: 60_000, |
| 97 | // A queued run starts on the next tick, not up to 5 s later. | |
| 98 | tickMs: 50, | |
| 97 | 99 | }); |
| 98 | 100 | const a = await createUser(app.db, { |
| 99 | 101 | origin: "seed", |
| @@ -266,8 +268,15 @@ echo '{"type":"result","subtype":"success","is_error":false,"result":"Wrote note | ||
| 266 | 268 | await Bun.sleep(20); |
| 267 | 269 | } |
| 268 | 270 | await press("v"); |
| 269 | await Bun.sleep(1500); | |
| 270 | await settleApp(tui); | |
| 271 | // A writer's `v` opens the conflict blocks: wait for them to be drawn. | |
| 272 | // A reader's `v` is ignored and draws nothing to wait for; what a | |
| 273 | // reader may not do is asserted on the keys that follow. | |
| 274 | const writer = user.id === alice.id; | |
| 275 | while (writer && !tui.screen.toText().includes("conflict block in")) { | |
| 276 | if (Date.now() > end) throw new Error(`v never opened the conflict blocks:\n${tui.screen.toText()}`); | |
| 277 | await settleApp(tui); | |
| 278 | await Bun.sleep(20); | |
| 279 | } | |
| 271 | 280 | for (const key of keys) await press(key); |
| 272 | 281 | while (until !== undefined && !until(tui.screen.toText())) { |
| 273 | 282 | if (Date.now() > end + 30_000) throw new Error(tui.screen.toText()); |
| @@ -376,7 +385,11 @@ echo '{"type":"result","subtype":"success","is_error":false,"result":"Wrote note | ||
| 376 | 385 | issueId: pull.id, |
| 377 | 386 | reason: "comment", |
| 378 | 387 | }); |
| 379 | app.db.query("UPDATE run SET state = 'running' WHERE id = ?1").run(run.id); | |
| 388 | // A live run holds its lease; without a heartbeat the crew's next tick | |
| 389 | // takes it for one a restart interrupted and queues it again for real. | |
| 390 | app.db | |
| 391 | .query("UPDATE run SET state = 'running', heartbeat_at = strftime('%Y-%m-%dT%H:%M:%fZ','now') WHERE id = ?1") | |
| 392 | .run(run.id); | |
| 380 | 393 | const before = agentRuns(); |
| 381 | 394 | const refused = await proposeResolution(app, alice, repo, current()); |
| 382 | 395 | expect(refused.ok).toBe(false); |
test/upstream-forge.test.ts +7 −1
| @@ -266,7 +266,13 @@ describe("a child forge proposes to its parent forge", () => { | ||
| 266 | 266 | const screen = await chainScreen(child, kid, ctx); |
| 267 | 267 | expect(screen.keys.map((k: any) => k.keys?.[0] ?? k[0])).toContain("p"); |
| 268 | 268 | screen.key({ key: "p" } as any); |
| 269 | await Bun.sleep(1500); | |
| 269 | // Wait for what the lines below read: the press's toast and its event. | |
| 270 | const deadline = Date.now() + 10_000; | |
| 271 | while (toasts.length === 0 || heard.length === 0) { | |
| 272 | if (Date.now() > deadline) | |
| 273 | throw new Error(`after p: ${toasts.length === 0 ? "no toast" : "no forge.proposal event"} within 10 s (toasts: ${JSON.stringify(toasts)})`); | |
| 274 | await Bun.sleep(10); | |
| 275 | } | |
| 270 | 276 | off(); |
| 271 | 277 | expect(toasts.join("\n")).toContain("Proposed to the parent forge as #"); |
| 272 | 278 | // One event, tied to the forge's repository so only its readers hear it. |
new above
offline
This negative check runs immediately after press("v") and proves nothing about a later draw. Wait for a positive signal that v was handled by a reader, then assert the blocks are absent; or remove it.