test(runner): control the durable resume watchdog (#16623)

This commit is contained in:
Nicky Leach authored and GitHub committed 2026-10-17 01:26:01 -07:00
1 parent 5555817c2b
commit 2723168083
1 file changed
+25 -7
@@ -1,9 +1,10 @@
import { resolveQualifiedAcpxProfile } from "../drivers/acpx/qualified-profiles.js"; import { resolveQualifiedAcpxProfile } from "../drivers/acpx/qualified-profiles.js";
import { describe, expect, it, vi } from "vitest"; import { describe, expect, it, onTestFinished, vi } from "vitest";
import { createHash } from "node:crypto"; import { createHash } from "node:crypto";
import { existsSync } from "node:fs"; import { existsSync } from "node:fs";
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os"; import { tmpdir } from "node:os";
import { setTimeout as realDelay } from "node:timers/promises";
import { join } from "node:path"; import { join } from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
@@ -1766,7 +1767,7 @@ describe("Capability live runnerd and Codex session", () => {
expect((await store.load(replacement.id))?.status).toBe("suspended"); expect((await store.load(replacement.id))?.status).toBe("suspended");
}); });
it("durably resumes a distinct attempt, preserves partial usage, and deduplicates the semantic effect", async () => { it.each([0, 650])("durably resumes a distinct attempt, preserves partial usage, and deduplicates the semantic effect (%i ms persistence delay)", async persistenceDelayMs => {
const state = providerState(); const state = providerState();
const directory = await mkdtemp(join(tmpdir(), "capability-live-durable-")); const directory = await mkdtemp(join(tmpdir(), "capability-live-durable-"));
const binding = { const binding = {
@@ -1786,6 +1787,17 @@ describe("Capability live runnerd and Codex session", () => {
attemptId: "attempt-killed", attemptId: "attempt-killed",
turnTimeoutMs: 500, turnTimeoutMs: 500,
}); });
const save = firstStore.save.bind(firstStore);
let delayed = false;
const saveSpy = vi.spyOn(firstStore, "save").mockImplementation(async snapshot => {
if (!delayed && JSON.parse(snapshot.mockState).comments.length > 0) {
delayed = true;
await realDelay(persistenceDelayMs);
}
await save(snapshot);
});
vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] });
onTestFinished(() => { vi.useRealTimers(); saveSpy.mockRestore(); });
state.holdAfterTool = true; state.holdAfterTool = true;
let turnSettled = false; let turnSettled = false;
let turnFailure: unknown = null; let turnFailure: unknown = null;
@@ -1795,11 +1807,14 @@ describe("Capability live runnerd and Codex session", () => {
return error; return error;
}); });
try { try {
await vi.waitFor(async () => { const deadline = performance.now() + 5_000;
expect((await firstStore.load(binding.sessionId))?.mockState).toContain( while (true) {
"Progress persisted through the live Codex tool loop.", await vi.advanceTimersByTimeAsync(0);
); const persisted = (await firstStore.load(binding.sessionId))?.mockState;
}); if (persisted?.includes("Progress persisted through the live Codex tool loop.")) break;
expect(performance.now(), "durable progress did not arrive").toBeLessThan(deadline);
await realDelay(10);
}
} catch (error) { } catch (error) {
try { try {
const summarize = (snapshot: CapabilityLiveSessionSnapshot | null) => { const summarize = (snapshot: CapabilityLiveSessionSnapshot | null) => {
@@ -1833,6 +1848,7 @@ describe("Capability live runnerd and Codex session", () => {
} }
throw error; throw error;
} }
expect(turnSettled, "watchdog fired before durable progress was captured").toBe(false);
await expect(first.recordUsage({ await expect(first.recordUsage({
receiptId: "provider-response-1", receiptId: "provider-response-1",
providerResponseId: "response-1", providerResponseId: "response-1",
@@ -1845,7 +1861,9 @@ describe("Capability live runnerd and Codex session", () => {
costNanodollars: 1_500, costNanodollars: 1_500,
})).resolves.toBe("committed"); })).resolves.toBe("committed");
await state.transports[0]?.close(); await state.transports[0]?.close();
await vi.advanceTimersByTimeAsync(500);
await expect(killedTurn).resolves.toMatchObject({ message: expect.stringContaining("timed out") }); await expect(killedTurn).resolves.toMatchObject({ message: expect.stringContaining("timed out") });
vi.useRealTimers();
// A new service models worker termination: it owns no in-memory session. // A new service models worker termination: it owns no in-memory session.
const resumedService = new CapabilityLiveSessionService({ const resumedService = new CapabilityLiveSessionService({