test: await async outcomes and drive saved deadlines deterministically (#18416)

Reviewed and verified at ab0b15d234.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip authored and GitHub committed 2026-10-31 08:18:15 -05:00
1 parent f2db909393
commit ab51cabbbd
5 files changed
+56 -11

No files matched your search

@@ -1796,8 +1796,22 @@ describeEmbeddedPostgres("heartbeat stale queued-run invalidation", () => {
const interactionId = randomUUID(); const interactionId = randomUUID();
const wakeCommentId = randomUUID(); const wakeCommentId = randomUUID();
let claimedIssue: { status: string; executionRunId: string | null } | null = null; let claimedIssue: { status: string; executionRunId: string | null } | null = null;
let resumedRunId: string | undefined;
let signalAdapterStarted!: () => void;
const adapterStarted = new Promise<void>(resolve => { signalAdapterStarted = resolve; });
const execute = mockAdapterExecute.getMockImplementation()!;
mockAdapterExecute.mockImplementation(async (...args: unknown[]) => {
if ((args[0] as { runId: string }).runId === resumedRunId) {
signalAdapterStarted();
// A claimed continuation can finish after the old terminal poll.
if (interactionStatus === "rejected" && interactionKind === "connection_intent") {
await new Promise(resolve => setTimeout(resolve, 3100));
}
}
return execute();
});
afterContinuationDispatchCheck = async ({ runId: checkedRunId, issueId: checkedIssueId }) => { afterContinuationDispatchCheck = async ({ runId: checkedRunId, issueId: checkedIssueId }) => {
if (checkedIssueId !== issueId) return; if (checkedIssueId !== issueId || checkedRunId !== resumedRunId) return;
claimedIssue = await db claimedIssue = await db
.select({ status: issues.status, executionRunId: issues.executionRunId }) .select({ status: issues.status, executionRunId: issues.executionRunId })
.from(issues) .from(issues)
@@ -1831,13 +1845,14 @@ describeEmbeddedPostgres("heartbeat stale queued-run invalidation", () => {
contextExtras: { interactionId, interactionKind, interactionStatus, wakeCommentId, contextExtras: { interactionId, interactionKind, interactionStatus, wakeCommentId,
originCommentIds: [wakeCommentId], originCommentIds: [wakeCommentId],
interactionResolvedAt: new Date().toISOString(), mutation: "interaction", source: `${interactionKind}.resolved`, forceFreshSession: true } }); interactionResolvedAt: new Date().toISOString(), mutation: "interaction", source: `${interactionKind}.resolved`, forceFreshSession: true } });
resumedRunId = runId;
await heartbeat.resumeQueuedRuns(); await heartbeat.resumeQueuedRuns();
expect(await waitForCondition(async () => (await db.select({ status: heartbeatRuns.status }).from(heartbeatRuns).where(eq(heartbeatRuns.id, runId)))[0]?.status === "succeeded")).toBe(true); // Adapter entry guarantees this exact execution is registered. Await its
// Terminal status precedes completion bookkeeping. Drain those writes before // owning completion barrier before terminal assertions and fixture cleanup.
// afterEach truncates the fixture, otherwise PostgreSQL can deadlock. await adapterStarted;
await heartbeat.waitForRunExecutionDrain(runId); await heartbeat.waitForRunExecutionDrain(runId);
expect((await db.select({ status: heartbeatRuns.status }).from(heartbeatRuns).where(eq(heartbeatRuns.id, runId)))[0]?.status).toBe("succeeded");
expect(countExecuteCallsForRun(runId)).toBe(1); expect(countExecuteCallsForRun(runId)).toBe(1);
await waitForCondition(async () => claimedIssue !== null);
expect(claimedIssue).toEqual({ status: "in_progress", executionRunId: runId }); expect(claimedIssue).toEqual({ status: "in_progress", executionRunId: runId });
}); });
}); });
@@ -83,6 +83,7 @@ describeEmbeddedPostgres("skill version snapshot retention", () => {
let coordinator: ReturnType<typeof createDeliveryWorkCoordinator> | undefined; let coordinator: ReturnType<typeof createDeliveryWorkCoordinator> | undefined;
let worker: ReturnType<typeof registerSkillVersionRetentionWork> | undefined; let worker: ReturnType<typeof registerSkillVersionRetentionWork> | undefined;
let admitted = true; let admitted = true;
const passes = new Set<Promise<unknown>>();
afterEach(async () => { afterEach(async () => {
worker?.unsubscribe(); worker?.unsubscribe();
await coordinator?.stop(); await coordinator?.stop();
@@ -160,12 +161,20 @@ describeEmbeddedPostgres("skill version snapshot retention", () => {
async function startRetention( async function startRetention(
graceMs = 20_000, graceMs = 20_000,
service = skillVersionRetentionService(db, { graceMs }), service = skillVersionRetentionService(db, { graceMs }),
advanceAutomatically = true, clock: boolean | { shouldAdvanceTime?: boolean; now?: number } = true,
) { ) {
const clockOptions = typeof clock === "boolean" ? { shouldAdvanceTime: clock } : clock;
const advanceAutomatically = clockOptions.shouldAdvanceTime ?? true;
await Promise.all(Array.from({ length: 6 }, () => db.execute(sql`select 1`))); await Promise.all(Array.from({ length: 6 }, () => db.execute(sql`select 1`)));
vi.useFakeTimers({ toFake: ["Date", "setTimeout", "clearTimeout", "setInterval", "clearInterval"], shouldAdvanceTime: advanceAutomatically }); vi.useFakeTimers({ toFake: ["Date", "setTimeout", "clearTimeout", "setInterval", "clearInterval"], shouldAdvanceTime: advanceAutomatically, ...clockOptions });
coordinator = createDeliveryWorkCoordinator({ owner: db, canRun: () => admitted, canReconcile: () => true, coordinator = createDeliveryWorkCoordinator({ owner: db, canRun: () => admitted, canReconcile: () => true,
onError: error => { throw error; } }); onError: error => { throw error; } });
const register = coordinator.register.bind(coordinator);
coordinator.register = (queue, task) => register(queue, { ...task, run: signal => {
const pass = task.run(signal); passes.add(pass);
void pass.then(() => passes.delete(pass), () => passes.delete(pass));
return pass;
} });
worker = registerSkillVersionRetentionWork(coordinator, service, () => admitted); worker = registerSkillVersionRetentionWork(coordinator, service, () => admitted);
if (advanceAutomatically) await worker.ready; if (advanceAutomatically) await worker.ready;
else await waitForIo(worker.ready); else await waitForIo(worker.ready);
@@ -194,6 +203,7 @@ describeEmbeddedPostgres("skill version snapshot retention", () => {
for (;;) { for (;;) {
if (process.hrtime.bigint() >= deadline) throw new Error("Retention work did not settle within 10000ms"); if (process.hrtime.bigint() >= deadline) throw new Error("Retention work did not settle within 10000ms");
if (idleWorkSnapshot().active > 0) { if (idleWorkSnapshot().active > 0) {
if (passes.size) await waitForIo(Promise.all([...passes]));
await vi.advanceTimersByTimeAsync(0); await vi.advanceTimersByTimeAsync(0);
await realDelay(0); await realDelay(0);
continue; continue;
@@ -631,7 +641,6 @@ describeEmbeddedPostgres("skill version snapshot retention", () => {
it("keeps directory pages moving while another company's grace deadline is due", async () => { it("keeps directory pages moving while another company's grace deadline is due", async () => {
const dueCompany = await createCompany(), pagedCompany = await createCompany(); const dueCompany = await createCompany(), pagedCompany = await createCompany();
const dueSkill = await createSkill(dueCompany, 2), pagedSkill = await createSkill(pagedCompany, 1); const dueSkill = await createSkill(dueCompany, 2), pagedSkill = await createSkill(pagedCompany, 1);
const expiring = await writeSnapshot(dueCompany, dueSkill.skillId, dueSkill.versionIds[0]!, 0);
const parent = path.join(resolveCompanySkillVersionsRoot(pagedCompany), pagedSkill.skillId); const parent = path.join(resolveCompanySkillVersionsRoot(pagedCompany), pagedSkill.skillId);
await fs.mkdir(parent, { recursive: true }); await fs.mkdir(parent, { recursive: true });
for (let index = 0; index < 410; index++) await fs.mkdir(path.join(parent, `operator-${index}`)); for (let index = 0; index < 410; index++) await fs.mkdir(path.join(parent, `operator-${index}`));
@@ -642,7 +651,13 @@ describeEmbeddedPostgres("skill version snapshot retention", () => {
if (id === pagedCompany) pageTimes.push(Date.now()); if (id === pagedCompany) pageTimes.push(Date.now());
return prune(id, options); return prune(id, options);
}; };
await startRetention(5000, service, false); await settledAtDeadlines(); const expiring = await writeSnapshot(dueCompany, dueSkill.skillId, dueSkill.versionIds[0]!, 0);
const accessedAt = (await lastUsedAt(skillVersionCachePaths(dueCompany, dueSkill.skillId, dueSkill.versionIds[0]!).access))!;
await startRetention(5000, service, { shouldAdvanceTime: false, now: accessedAt });
expect(await exists(expiring)).toBe(true);
// Keep virtual time fixed during real I/O; measure scheduler fairness,
// independent of filesystem latency and CI CPU scheduling.
await settledAtDeadlines();
expect(pageTimes.length).toBeGreaterThan(5); expect(pageTimes.length).toBeGreaterThan(5);
expect(Math.max(...pageTimes.slice(1).map((at, index) => at - pageTimes[index]!))).toBeLessThan(3000); expect(Math.max(...pageTimes.slice(1).map((at, index) => at - pageTimes[index]!))).toBeLessThan(3000);
expect(await exists(expiring)).toBe(false); await quiet(); expect(await exists(expiring)).toBe(false); await quiet();
+3
View File
@@ -459,12 +459,15 @@ test("plan approval hands the preserved revision to an assigned project task", a
{ timeout: 60_000 }, { timeout: 60_000 },
) )
.toBe(1); .toBe(1);
// Task completion is delivered back to the chat as another real turn.
await idle(request, f.chatPath, 3);
const task = ( const task = (
await json(await request.get(`/api/companies/${f.company.id}/issues`)) await json(await request.get(`/api/companies/${f.company.id}/issues`))
)[0]; )[0];
expect(task.parentId).toBeNull(); expect(task.parentId).toBeNull();
expect(task.projectId).toBeTruthy(); expect(task.projectId).toBeTruthy();
expect(task.assigneeAgentId).toBe(f.agent.id); expect(task.assigneeAgentId).toBe(f.agent.id);
await expect(page.getByText(`Delegated work completed: ${task.identifier}`, { exact: true })).toBeVisible();
const plan = await json( const plan = await json(
await request.get(`/api/issues/${task.id}/documents/plan`), await request.get(`/api/issues/${task.id}/documents/plan`),
); );
+4
View File
@@ -44,6 +44,10 @@ if (!task.conversationAgentId) {
}); });
process.exit(0); process.exit(0);
} }
if (ctx.wakeReason === "chat_task_completed") {
await comment(`Delegated work completed: ${ctx.chatCompletionUpdates.map((update) => update.identifier).join(", ")}`);
process.exit(0);
}
const comments = await api(`/issues/${task.id}/comments?order=asc`); const comments = await api(`/issues/${task.id}/comments?order=asc`);
const current = const current =
comments.find((c) => c.id === ctx.wakeCommentId) ?? comments.find((c) => c.id === ctx.wakeCommentId) ??
+10 -2
View File
@@ -143,9 +143,17 @@ it("discovers a panel started after the task is already open, then hides it on s
get.mockRejectedValue(new Error("Access denied")); await refresh(); expect(current).toEqual([]); get.mockRejectedValue(new Error("Access denied")); await refresh(); expect(current).toEqual([]);
}); });
it("clears automatic activation when the run ends even if its view is still ready", async () => { it("clears automatic activation when the run ends even if its view is still ready", async () => {
await render(); expect(current).not.toEqual([]); await render();
await vi.waitFor(async () => {
await act(async () => {});
expect(current).not.toEqual([]);
});
await act(async () => { client.setQueryData(queryKeys.issues.liveRuns("task"), []); }); await act(async () => { client.setQueryData(queryKeys.issues.liveRuns("task"), []); });
await render(); expect(current).toEqual([]); await render();
await vi.waitFor(async () => {
await act(async () => {});
expect(current).toEqual([]);
});
}); });
it("does not carry an old task's ready panel to a new task or disabled plugin", async () => { it("does not carry an old task's ready panel to a new task or disabled plugin", async () => {
await render(); expect(current).not.toEqual([]); await render(); expect(current).not.toEqual([]);