From bc411c24befceced5a9ea1ca1c1ce123fd02bf48 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Fri, 23 Oct 2026 21:22:52 +0800 Subject: [PATCH] test: align boundary fixtures with producer contracts Model GDB console completion, use real directory reads and cleanup for late-enumeration failures, and distinguish supported PTX pointer qualifiers from unknown declarations. Preserve original assertion and cleanup failures through scoped test finalizers. --- .../referenceSourceQueuedDirectories.test.ts | 65 +++++---- tests/boundary/gpu/codeObjects.test.ts | 20 ++- .../boundary/mcp/gdbCloseDisposition.test.ts | 137 +++++++++--------- 3 files changed, 119 insertions(+), 103 deletions(-) diff --git a/tests/boundary/filesystem/referenceSourceQueuedDirectories.test.ts b/tests/boundary/filesystem/referenceSourceQueuedDirectories.test.ts index 6bc4be7072..c5df498bc9 100644 --- a/tests/boundary/filesystem/referenceSourceQueuedDirectories.test.ts +++ b/tests/boundary/filesystem/referenceSourceQueuedDirectories.test.ts @@ -1,4 +1,3 @@ -import type { Dir, Dirent } from "node:fs"; import { mkdir, symlink, writeFile } from "node:fs/promises"; import { join } from "node:path"; @@ -7,30 +6,42 @@ import { expect, it, vi } from "vitest"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { importReferenceSource } from "../../support/referenceSourceResourceScope.js"; -const { opendirMock } = vi.hoisted(() => ({ opendirMock: vi.fn() })); - -vi.mock("node:fs/promises", async (importOriginal) => ({ - ...(await importOriginal()), - opendir: opendirMock, +const injectedReadFailure = vi.hoisted(() => ({ + path: undefined as string | undefined, + error: undefined as Error | undefined, })); -const directoryHandle = (items: readonly (string | Error | null)[]): Dir => { - let index = 0; +vi.mock("node:fs/promises", async (importOriginal) => { + const actual = await importOriginal(); return { - async read() { - const item = items[index++]; - if (item instanceof Error) throw item; - return item === null || item === undefined - ? null - : ({ name: item } as Dirent); + ...actual, + opendir: async (...args: Parameters) => { + const handle = await actual.opendir(...args); + const [path] = args; + const { path: failingPath, error } = injectedReadFailure; + if (path !== failingPath || error === undefined) return handle; + + const read = handle.read.bind(handle); + let failureInjected = false; + Object.defineProperty(handle, "read", { + configurable: true, + value: async () => { + const entry = await read(); + if (entry === null && !failureInjected) { + failureInjected = true; + throw error; + } + return entry; + }, + }); + return handle; }, - async close() {}, - } as unknown as Dir; -}; + }; +}); it.skipIf(process.platform === "win32")( "traverses directories lstat-observed before their parent enumeration failed", - async () => { + async ({ onTestFinished }) => { const root = await createTestTempDirectory( "rea-queued-source-directories-", ); @@ -46,18 +57,11 @@ it.skipIf(process.platform === "win32")( new Error("injected directory read failure"), { code: "EIO" }, ); - opendirMock.mockImplementation(async (path: string) => { - if (path === root) return directoryHandle(["container", null]); - if (path === container) - return directoryHandle([ - "visible.txt", - "hidden", - "alias-file.txt", - "alias-directory", - readFailure, - ]); - if (path === hidden) return directoryHandle(["target.txt", null]); - throw new Error(`Unexpected directory traversal: ${path}`); + injectedReadFailure.path = container; + injectedReadFailure.error = readFailure; + onTestFinished(() => { + injectedReadFailure.path = undefined; + injectedReadFailure.error = undefined; }); const imported = await importReferenceSource({ @@ -73,6 +77,7 @@ it.skipIf(process.platform === "win32")( path: "container", kind: "directory", tree_state: "unreadable", + limitations: [expect.stringContaining(readFailure.message)], }), expect.objectContaining({ path: "container/hidden", diff --git a/tests/boundary/gpu/codeObjects.test.ts b/tests/boundary/gpu/codeObjects.test.ts index 3b5c01a208..cbe4ced8bd 100644 --- a/tests/boundary/gpu/codeObjects.test.ts +++ b/tests/boundary/gpu/codeObjects.test.ts @@ -429,7 +429,7 @@ it.each([ ]); }); it.each([ - ["parameter", ".u64 output", ".u64 .ptr .global output"], + ["parameter", ".u64 output", ".u64 .vendor_specific output"], ["shared", "scratch[32]", "scratch[]"], ] as const)( "marks unknown PTX %s declarations partial while retaining known facts", @@ -459,6 +459,24 @@ it.each([ } }, ); +it("keeps supported PTX pointer qualifiers complete through the provider", async () => { + const bytes = Buffer.from( + ptxFixture.replace(".u64 output", ".u64 .ptr .global output"), + ); + const result = await inspect(bytes); + if (!result.ok) throw result.error; + expect(result.value.objects[0]!.inspection).toBe("complete"); + expect(result.value.coverage.payloads).toBe("complete"); + expect(result.value.ptx_artifacts[0]!.text).toBe(bytes.toString()); + expect(result.value.kernels[0]).toMatchObject({ + parameter_count: 2, + parameter_bytes: 12, + parameters: [ + { name: "output", offset: 0, size: 8 }, + { name: "value", offset: 8, size: 4 }, + ], + }); +}); it.each(["whitespace", "comment"] as const)( "recognizes standalone PTX after more than 64 KiB of leading %s", async (kind) => { diff --git a/tests/boundary/mcp/gdbCloseDisposition.test.ts b/tests/boundary/mcp/gdbCloseDisposition.test.ts index f49455760d..4cff147d26 100644 --- a/tests/boundary/mcp/gdbCloseDisposition.test.ts +++ b/tests/boundary/mcp/gdbCloseDisposition.test.ts @@ -1,12 +1,12 @@ import { randomUUID } from "node:crypto"; -import { chmod, mkdtemp, rm, writeFile } from "node:fs/promises"; -import { tmpdir } from "node:os"; +import { chmod, writeFile } from "node:fs/promises"; import { join } from "node:path"; import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; import { McpServer } from "@modelcontextprotocol/server"; -import { expect, it } from "vitest"; +import { expect, it, onTestFinished } from "vitest"; +import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { toolContract } from "../../../src/contracts/toolContracts.js"; import { GdbSessionManager } from "../../../src/gdb/GdbSessionManager.js"; import { silentLogger } from "../../../src/logger.js"; @@ -16,7 +16,7 @@ import { createToolResultDelivery } from "../../../src/server/toolResult.js"; it.skipIf(process.platform === "win32").each(["kill", "detach"])( "retains a valid GDB session through disposition refusal and explicit %s recovery", async (disposition) => { - const directory = await mkdtemp(join(tmpdir(), "rea-gdb-disposition-")); + const directory = await createTestTempDirectory("rea-gdb-disposition-"); const provider = join(directory, "fake-gdb.mjs"); await writeFile( provider, @@ -33,6 +33,10 @@ createInterface({ input: process.stdin }).on('line', line => { process.stdout.write(token + '^running\\n*stopped,reason="breakpoint-hit",thread-id="1"\\n(gdb)\\n'); return; } + if (command === '-gdb-show mi-async') { + process.stdout.write(token + '^done,value="off"\\n(gdb)\\n'); + return; + } const disposition = command === '-interpreter-exec console "kill"' || command === '-interpreter-exec console "detach"'; if (disposition) live = false; const result = command === '-list-thread-groups --recurse 1' @@ -50,10 +54,12 @@ createInterface({ input: process.stdin }).on('line', line => { platform: "linux", environment: { REA_GDB_COMMAND: provider }, }); + onTestFinished(() => manager.closeAll()); const server = new McpServer({ name: "gdb-disposition-test", version: "1", }); + onTestFinished(() => server.close()); registerGdbTools( server, manager, @@ -61,80 +67,67 @@ createInterface({ input: process.stdin }).on('line', line => { createToolResultDelivery(undefined), ); const client = new Client({ name: "gdb-disposition-test", version: "1" }); + onTestFinished(() => client.close()); const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); - let sessionId: string | undefined; - try { - await server.connect(serverTransport); - await client.connect(clientTransport); - const started = await client.callTool({ - name: "start_gdb_session", - arguments: {}, - }); - sessionId = toolContract("start_gdb_session").outputSchema.parse( - started.structuredContent, - ).session_id; - const running = await client.callTool({ - name: "gdb_console", - arguments: { session_id: sessionId, command: "run" }, - }); - expect(running.isError).not.toBe(true); - for (let attempt = 0; attempt < 2; attempt += 1) { - const refusal = await client.callTool({ - name: "close_gdb_session", - arguments: { session_id: sessionId }, - }); - expect(refusal.isError).toBe(true); - expect(refusal.structuredContent).toMatchObject({ - error: { - code: "capability_unavailable", - category: "unavailable", - retryable: true, - remediation: { action: expect.stringContaining("gdb_console") }, - details: { - provider_id: "gnu.gdb", - operation: "close_gdb_session", - reason: expect.stringContaining("inferior"), - }, - }, - }); - expect(JSON.stringify(refusal.structuredContent)).not.toContain( - "invalid_value", - ); - expect(manager.status(sessionId)?.state).toBe("ready"); - } - const disposed = await client.callTool({ - name: "gdb_console", - arguments: { session_id: sessionId, command: disposition }, - }); - expect(disposed.isError).not.toBe(true); - const closed = await client.callTool({ + await server.connect(serverTransport); + await client.connect(clientTransport); + const started = await client.callTool({ + name: "start_gdb_session", + arguments: {}, + }); + const sessionId = toolContract("start_gdb_session").outputSchema.parse( + started.structuredContent, + ).session_id; + const running = await client.callTool({ + name: "gdb_console", + arguments: { session_id: sessionId, command: "run" }, + }); + expect(running.isError).not.toBe(true); + for (let attempt = 0; attempt < 2; attempt += 1) { + const refusal = await client.callTool({ name: "close_gdb_session", arguments: { session_id: sessionId }, }); - expect(closed.isError).not.toBe(true); - expect(closed.structuredContent).toMatchObject({ - target_state: "none_observed", + expect(refusal.isError).toBe(true); + expect(refusal.structuredContent).toMatchObject({ + error: { + code: "capability_unavailable", + category: "unavailable", + retryable: true, + remediation: { action: expect.stringContaining("gdb_console") }, + details: { + provider_id: "gnu.gdb", + operation: "close_gdb_session", + reason: expect.stringContaining("inferior"), + }, + }, }); - expect(manager.status(sessionId)).toBeUndefined(); - const unknown = await client.callTool({ - name: "close_gdb_session", - arguments: { session_id: randomUUID() }, - }); - expect(unknown.structuredContent).toMatchObject({ - error: { code: "invalid_request", category: "invalid_input" }, - }); - } finally { - if (sessionId !== undefined && manager.status(sessionId) !== undefined) { - const disposed = await manager.execute(sessionId, "kill"); - if (!disposed.ok) throw disposed.error; - const closed = await manager.close(sessionId); - if (!closed.ok) throw closed.error; - } - await client.close(); - await server.close(); - await manager.closeAll(); - await rm(directory, { recursive: true, force: true }); + expect(JSON.stringify(refusal.structuredContent)).not.toContain( + "invalid_value", + ); + expect(manager.status(sessionId)?.state).toBe("ready"); } + const disposed = await client.callTool({ + name: "gdb_console", + arguments: { session_id: sessionId, command: disposition }, + }); + expect(disposed.isError).not.toBe(true); + const closed = await client.callTool({ + name: "close_gdb_session", + arguments: { session_id: sessionId }, + }); + expect(closed.isError).not.toBe(true); + expect(closed.structuredContent).toMatchObject({ + target_state: "none_observed", + }); + expect(manager.status(sessionId)).toBeUndefined(); + const unknown = await client.callTool({ + name: "close_gdb_session", + arguments: { session_id: randomUUID() }, + }); + expect(unknown.structuredContent).toMatchObject({ + error: { code: "invalid_request", category: "invalid_input" }, + }); }, );