From ab0696ae9d7976ae668c65fe91e8b7427ea69d82 Mon Sep 17 00:00:00 2001 From: Dotta Date: Tue, 29 Sep 2026 13:11:45 -0500 Subject: [PATCH] test(runner): isolate cancellation timing and portable snapshot paths Paperclip-Task: rich-acp-production --- .../acpx/installation-integrity.test.ts | 12 +++++-- .../src/drivers/runner-tool-bridge.test.ts | 36 ++++++++++++++----- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/packages/paperclip-runner/src/drivers/acpx/installation-integrity.test.ts b/packages/paperclip-runner/src/drivers/acpx/installation-integrity.test.ts index b97b7c51acf..2318cbf2981 100644 --- a/packages/paperclip-runner/src/drivers/acpx/installation-integrity.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/installation-integrity.test.ts @@ -1926,9 +1926,15 @@ async function expectOutput( }); const [exitCode] = await once(child, "exit"); expect(exitCode, stderr).toBe(0); - const normalized = process.platform === "darwin" - ? stdout.replace(/\/private\/var\/[^"\s]*\/paperclip-acpx-[^/]+\/0/g, "/proc/self/fd/4") - : stdout; + let normalized = stdout; + if (process.platform === "darwin") { + const snapshotPrefix = join(await realpath(tmpdir()), "paperclip-acpx-") + .replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + normalized = stdout.replace( + new RegExp(`${snapshotPrefix}[^/"\\s]+/0(?=[/"])`, "g"), + "/proc/self/fd/4", + ); + } expect(normalized).toBe(expected); } diff --git a/packages/paperclip-runner/src/drivers/runner-tool-bridge.test.ts b/packages/paperclip-runner/src/drivers/runner-tool-bridge.test.ts index b552adcb28a..ff2a412d3ba 100644 --- a/packages/paperclip-runner/src/drivers/runner-tool-bridge.test.ts +++ b/packages/paperclip-runner/src/drivers/runner-tool-bridge.test.ts @@ -186,14 +186,12 @@ describe("runner semantic MCP bridge", () => { expect(handler).toHaveBeenCalledTimes(2); }); - it("times out calls and honors MCP cancellation", async () => { - const starts: string[] = []; + it("times out calls", async () => { const bridge = await startRunnerToolBridge({ tools: [tool("documents.read")], timeoutMs: 20, - handler: ({ callId, signal }) => + handler: ({ signal }) => new Promise((_resolve, reject) => { - starts.push(callId); signal.addEventListener( "abort", () => reject(new Error("handler aborted")), @@ -217,15 +215,36 @@ describe("runner semantic MCP bridge", () => { content: [{ text: "Paperclip tool call timed out" }], }, }); + }); + + it("honors MCP cancellation after the handler starts", async () => { + let markStarted!: () => void; + const started = new Promise((resolve) => { + markStarted = resolve; + }); + const aborted = vi.fn(); + const bridge = await startRunnerToolBridge({ + tools: [tool("documents.read")], + timeoutMs: 60_000, + handler: ({ signal }) => new Promise((_resolve, reject) => { + signal.addEventListener("abort", () => { + aborted(); + reject(new Error("handler aborted")); + }, { once: true }); + markStarted(); + }), + }); + bridges.push(bridge); const pending = rpc(bridge, { id: "cancel-me", method: "tools/call", params: { name: "documents.read", arguments: {} }, }); - await vi.waitFor(() => expect(starts).toContain("cancel-me"), { - interval: 1, - timeout: 15, - }); + // Observe request arrival without racing the separate timeout behavior. + // Retain the rejection for the assertion while avoiding an unhandled fetch + // rejection if a failed test closes the bridge before the request arrives. + void pending.catch(() => undefined); + await started; expect( ( await rpc(bridge, { @@ -240,6 +259,7 @@ describe("runner semantic MCP bridge", () => { content: [{ text: "Paperclip tool call cancelled" }], }, }); + expect(aborted).toHaveBeenCalledOnce(); }); it("preserves successful mutation identity when the result is oversized", async () => {