diff --git a/src/agent/hosted/model-call-context-run-event-recorder.test.ts b/src/agent/hosted/model-call-context-run-event-recorder.test.ts index 1a5c10ad3a..28fea6dc75 100644 --- a/src/agent/hosted/model-call-context-run-event-recorder.test.ts +++ b/src/agent/hosted/model-call-context-run-event-recorder.test.ts @@ -25,6 +25,26 @@ import { FakeTime } from "#std/testing/time"; const encoder = new TextEncoder(); const originalFetch = globalThis.fetch; +const wallClockSetTimeout = globalThis.setTimeout.bind(globalThis); +const wallClockClearTimeout = globalThis.clearTimeout.bind(globalThis); + +async function withWallClockTimeout( + promise: Promise, + timeoutMs: number, + message: string, +): Promise { + let timeoutId: ReturnType | undefined; + const timeout = new Promise((_, reject) => { + timeoutId = wallClockSetTimeout(() => reject(new Error(message)), timeoutMs); + }); + try { + return await Promise.race([promise, timeout]); + } finally { + if (timeoutId !== undefined) { + wallClockClearTimeout(timeoutId); + } + } +} afterEach(() => { globalThis.fetch = originalFetch; @@ -533,11 +553,14 @@ describe("agent/hosted/model-call-context-run-event-recorder", () => { }); it("aborts the active append at the deadline and makes disposal terminal", async () => { + using time = new FakeTime(); let requestCount = 0; let appendWasAborted = false; let resolveLateAppend: ((response: Response) => void) | undefined; + const requestStarted = Promise.withResolvers(); globalThis.fetch = ((_input: RequestInfo | URL, init?: RequestInit) => { requestCount += 1; + requestStarted.resolve(); return new Promise((resolve) => { resolveLateAppend = resolve; init?.signal?.addEventListener("abort", () => { @@ -553,12 +576,29 @@ describe("agent/hosted/model-call-context-run-event-recorder", () => { metrics: metrics.result, }); - await assertRejects( - () => Promise.resolve(recorder({ messages: [{ role: "system", content: "deadline" }] })), + const recording = Promise.resolve( + recorder({ messages: [{ role: "system", content: "deadline" }] }), + ); + const recordingSettledBeforeRequest = recording.then( + () => { + throw new Error("model-call recording completed before its append request started"); + }, + (error) => { + throw error; + }, + ); + await withWallClockTimeout( + Promise.race([requestStarted.promise, recordingSettledBeforeRequest]), + 10_000, + "model-call append request did not start", + ); + const assertion = assertRejects( + () => recording, ModelCallContextPersistenceError, "timed out", ); - await Promise.resolve(); + await time.tickAsync(5); + await assertion; assertEquals(appendWasAborted, true); assertEquals(requestCount, 1); assertEquals(metrics.measurements[0]?.appendRequestCount, 1); @@ -567,7 +607,7 @@ describe("agent/hosted/model-call-context-run-event-recorder", () => { await target.appendEvents([{ type: "CUSTOM", value: "after disposal" }]); await target.flush(); resolveLateAppend?.(appendResponse(1, 1)); - await new Promise((resolve) => setTimeout(resolve, 0)); + await time.tickAsync(0); assertEquals(requestCount, 1); const finalSnapshot = target.getSnapshot(); assertEquals(finalSnapshot.latestEventId, rejectedSnapshot.latestEventId); diff --git a/src/rendering/cache/stores/api-store.test.ts b/src/rendering/cache/stores/api-store.test.ts index 2e6aab475c..4d9249cab6 100644 --- a/src/rendering/cache/stores/api-store.test.ts +++ b/src/rendering/cache/stores/api-store.test.ts @@ -1,6 +1,7 @@ import "#veryfront/schemas/_test-setup.ts"; import { assertEquals } from "#veryfront/testing/assert.ts"; import { describe, it } from "#veryfront/testing/bdd.ts"; +import { withTimeoutThrow } from "../../utils/stream-utils.ts"; import { APICacheStore } from "./api-store.ts"; async function withStoreTtlEnabled(fn: () => Promise): Promise { @@ -296,9 +297,10 @@ describe("rendering/cache/stores/api-store", () => { const globals = globalThis as Record; const originalAdapter = globals.__vf_multi_project_adapter; - let releaseSet: () => void = () => {}; - let setStarted = false; + const setStarted = Promise.withResolvers(); + const releaseSet = Promise.withResolvers(); let setCompleted = false; + let setPromise: Promise | undefined; const server = Deno.serve( { hostname: "127.0.0.1", port: 0, onListen: () => {} }, async (request) => { @@ -310,10 +312,8 @@ describe("rendering/cache/stores/api-store", () => { return Response.json({ error: "not found" }, { status: 404 }); } - setStarted = true; - await new Promise((resolve) => { - releaseSet = resolve; - }); + setStarted.resolve(); + await releaseSet.promise; setCompleted = true; return Response.json({ success: true }); }, @@ -336,40 +336,47 @@ describe("rendering/cache/stores/api-store", () => { try { let setResolved = false; - const setPromise = store.set("distributed-key", payload).then(() => { + setPromise = store.set("distributed-key", payload).then(() => { setResolved = true; }); - for (let attempts = 0; attempts < 50 && !setStarted; attempts++) { - await new Promise((resolve) => setTimeout(resolve, 0)); - } - assertEquals(setStarted, true); + await withTimeoutThrow(setStarted.promise, 10_000, "distributed cache write to start"); assertEquals(setResolved, false); assertEquals(setCompleted, false); - releaseSet(); + releaseSet.resolve(); await setPromise; assertEquals(setCompleted, true); assertEquals(setResolved, true); } finally { - releaseSet(); - await store.destroy(); - await server.shutdown(); - if (previousApiBaseUrl === undefined) { - Deno.env.delete("VERYFRONT_API_BASE_URL"); - } else { - Deno.env.set("VERYFRONT_API_BASE_URL", previousApiBaseUrl); - } - if (previousApiToken === undefined) { - Deno.env.delete("VERYFRONT_API_TOKEN"); - } else { - Deno.env.set("VERYFRONT_API_TOKEN", previousApiToken); - } - if (originalAdapter === undefined) { - delete globals.__vf_multi_project_adapter; - } else { - globals.__vf_multi_project_adapter = originalAdapter; + releaseSet.resolve(); + try { + await withTimeoutThrow( + Promise.all([ + store.destroy(), + server.shutdown(), + setPromise ?? Promise.resolve(), + ]), + 10_000, + "distributed cache write test cleanup", + ); + } finally { + if (previousApiBaseUrl === undefined) { + Deno.env.delete("VERYFRONT_API_BASE_URL"); + } else { + Deno.env.set("VERYFRONT_API_BASE_URL", previousApiBaseUrl); + } + if (previousApiToken === undefined) { + Deno.env.delete("VERYFRONT_API_TOKEN"); + } else { + Deno.env.set("VERYFRONT_API_TOKEN", previousApiToken); + } + if (originalAdapter === undefined) { + delete globals.__vf_multi_project_adapter; + } else { + globals.__vf_multi_project_adapter = originalAdapter; + } } } });