Public: anyone can read this; the crew can change it.
test/crew-already-done.test.ts runs under 10 s #3605
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/3589-test-crew-already-done-test-ts-runs-unde → main 1 commit fork
3 files
changed +30
−17
scripts/test-times.json +1 −1
| @@ -16,7 +16,7 @@ | ||
| 16 | 16 | "test/board-windows.test.ts": 6, |
| 17 | 17 | "test/checks.test.ts": 26.3, |
| 18 | 18 | "test/cloud.test.ts": 7.1, |
| 19 | "test/crew-already-done.test.ts": 16.8, | |
| 19 | "test/crew-already-done.test.ts": 1.2, | |
| 20 | 20 | "test/crew-check-failed.test.ts": 12.7, |
| 21 | 21 | "test/crew-continues.test.ts": 12.5, |
| 22 | 22 | "test/crew-declines.test.ts": 11.3, |
test/crew-already-done.test.ts +23 −14
| @@ -26,39 +26,48 @@ describe("the crew, with a stand-in Claude Code: already done", () => { | ||
| 26 | 26 | let bot: User; |
| 27 | 27 | let repo: Repo; |
| 28 | 28 | |
| 29 | const settled = (where: (run: Run) => boolean, timeoutMs?: number) => | |
| 30 | agentRunSettled(app, repo, where, timeoutMs); | |
| 29 | const settled = (where: (run: Run) => boolean) => | |
| 30 | agentRunSettled(app, repo, where, 10_000, 5); | |
| 31 | 31 | |
| 32 | 32 | beforeAll(async () => { |
| 33 | standIn = await startStandInCrew(); | |
| 33 | // A quick tick: the crew picks an assignment up at once, nothing waits | |
| 34 | // out the five-second default. | |
| 35 | standIn = await startStandInCrew({ tickMs: 20 }); | |
| 34 | 36 | ({ app, alice, bot, repo } = standIn); |
| 35 | 37 | }, 30_000); |
| 36 | 38 | |
| 37 | 39 | afterAll(() => stopStandInCrew(standIn)); |
| 38 | 40 | |
| 39 | test("a decline naming a commit on main is an already-done run; without one that is on the base it is a decline", async () => { | |
| 41 | const ask = async (title: string, marker: string) => { | |
| 42 | const issue = openIssue(app, alice, repo, { | |
| 43 | title, | |
| 44 | body: `DONE-ON-MAIN ${marker}`, | |
| 45 | }); | |
| 46 | if (!issue.ok) throw new Error("issue"); | |
| 47 | expect(assignIssue(app, alice, repo, issue.value, bot).ok).toBe(true); | |
| 48 | return settled((r) => r.issueId === issue.value.id); | |
| 49 | }; | |
| 50 | ||
| 51 | test("a decline naming a commit on main is an already-done run", async () => { | |
| 40 | 52 | const dir = repoDir(app.reposRoot, repo.ownerName, repo.name); |
| 41 | 53 | const main = await resolveOid(dir, `refs/heads/${repo.defaultBranch}`); |
| 42 | const ask = async (title: string, marker: string) => { | |
| 43 | const issue = openIssue(app, alice, repo, { | |
| 44 | title, | |
| 45 | body: `DONE-ON-MAIN ${marker}`, | |
| 46 | }); | |
| 47 | if (!issue.ok) throw new Error("issue"); | |
| 48 | expect(assignIssue(app, alice, repo, issue.value, bot).ok).toBe(true); | |
| 49 | return settled((r) => r.issueId === issue.value.id); | |
| 50 | }; | |
| 51 | 54 | const named = await ask("Already built", "NAMED"); |
| 52 | 55 | expect(named.state).toBe("ok"); |
| 53 | 56 | expect(named.summary).toBe(`already done: ${main}`); |
| 54 | 57 | expect(named.summary).toMatch(/^already done: [0-9a-f]{40}$/); |
| 58 | }, 5_000); | |
| 59 | ||
| 60 | test("a decline naming no commit is a decline", async () => { | |
| 55 | 61 | const none = await ask("Built, says who", "NONE"); |
| 56 | 62 | expect(none.state).toBe("failed"); |
| 57 | 63 | expect(none.summary).toBe("declined: already done on main"); |
| 64 | }, 5_000); | |
| 65 | ||
| 66 | test("a decline naming a commit not on the base is a decline", async () => { | |
| 58 | 67 | const absent = await ask("Built somewhere", "ABSENT"); |
| 59 | 68 | expect(absent.state).toBe("failed"); |
| 60 | 69 | expect(absent.summary).toBe( |
| 61 | 70 | "declined: already done on main in deadbeefdeadbeef", |
| 62 | 71 | ); |
| 63 | }, 90_000); | |
| 72 | }, 5_000); | |
| 64 | 73 | }); |
test/helpers/crew-standin.ts +6 −2
| @@ -42,7 +42,9 @@ export interface StandInCrew { | ||
| 42 | 42 | } |
| 43 | 43 | |
| 44 | 44 | /** A forge with checks and a crew whose Claude Code is the stand-in. */ |
| 45 | export async function startStandInCrew(): Promise<StandInCrew> { | |
| 45 | export async function startStandInCrew( | |
| 46 | options: { readonly tickMs?: number } = {}, | |
| 47 | ): Promise<StandInCrew> { | |
| 46 | 48 | const dataDir = await mkdtemp(join(tmpdir(), "forge-crew-")); |
| 47 | 49 | const app = createApp({ dataDir, port: 0 }); |
| 48 | 50 | const servers = await serve(app); |
| @@ -176,6 +178,7 @@ echo '{"type":"result","subtype":"success","is_error":false,"result":"Added CREW | ||
| 176 | 178 | runTimeoutMs: 60_000, |
| 177 | 179 | networkRetryScale: 0, |
| 178 | 180 | reviewBudgetMs: 4_000, |
| 181 | tickMs: options.tickMs, | |
| 179 | 182 | }); |
| 180 | 183 | const a = await createUser(app.db, { |
| 181 | 184 | origin: "seed", |
| @@ -233,6 +236,7 @@ export async function agentRunSettled( | ||
| 233 | 236 | repo: Repo, |
| 234 | 237 | where: (run: Run) => boolean, |
| 235 | 238 | timeoutMs = 30_000, |
| 239 | pollMs = 100, | |
| 236 | 240 | ): Promise<Run> { |
| 237 | 241 | const deadline = Date.now() + timeoutMs; |
| 238 | 242 | while (Date.now() < deadline) { |
| @@ -244,7 +248,7 @@ export async function agentRunSettled( | ||
| 244 | 248 | run.state === "cancelled") |
| 245 | 249 | ) |
| 246 | 250 | return run; |
| 247 | await Bun.sleep(100); | |
| 251 | await Bun.sleep(pollMs); | |
| 248 | 252 | } |
| 249 | 253 | throw new Error("agent run did not settle"); |
| 250 | 254 | } |
new above
offline