Files
reverse-engineering-anything/tests/boundary/mcp/recordedCrashMcp.test.ts
T
Yijie Luandmorluto 11fe5a13f8 fix(native): retain failed pwntools process and runtime cleanup (#1708)
* fix(native): retain failed pwntools process and runtime cleanup

Keep failed worker/debugger cleanup owners and their private files until
verified process shutdown and successful directory removal. Retry cleanup
before another inspection and expose shutdown through the shared services,
CLI and MCP server. Serialize acquisition without delaying cancellation of
queued callers, and preserve primary diagnostics when CLI cleanup fails.

The original decoder reproduction left a live owned Node child with its
runtime deleted and no retained cleanup owner; the regression now verifies
retention, admission blocking, retries and eventual process/file cleanup.

Validation: 10 lifecycle/public CLI/MCP tests, 20 service tests, test
discovery 814/814, build:cached and full check including architecture guards.
Actual POSIX process/filesystem boundaries used injected Node workers on
macOS; real Linux pwntools/GDB provider parsing was not exercised.

* test(native): verify retained recorded-crash cleanup lifecycle

* fix(native): finalize diagnostic cleanup before publishing results

* fix(native): retire decoder stages before reading runtime evidence

---------

Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-10-13 01:37:47 +08:00

89 lines
3.4 KiB
TypeScript

import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
import { Ajv2020 } from "ajv/dist/2020.js";
import { expect, it, onTestFinished } from "vitest";
import { RecordedCrashService } from "../../../src/application/binaryDiagnostics/RecordedCrashService.js";
import { toolContract } from "../../../src/contracts/toolContracts.js";
import { parseEvidence } from "../../../src/domain/evidence.js";
import { ok } from "../../../src/domain/result.js";
import { createServer } from "../../../src/server/createServer.js";
import { createTestBinarySession } from "../../fixtures/binarySession.js";
import {
recordedCrashFixture,
RECORDED_CRASH_TEST_PROVIDER,
} from "../../fixtures/binaryDiagnostics/recordedCrash.js";
it("advertises exact valid schemas and records inline recorded-core evidence without a binary target", async () => {
const value = recordedCrashFixture();
let providerClosed = false;
const service = new RecordedCrashService({
inspectAndClose(input, options) {
return this.inspect(input, options);
},
identity: RECORDED_CRASH_TEST_PROVIDER,
inspect: () => Promise.resolve(ok(value)),
close: async () => {
providerClosed = true;
},
});
const session = createTestBinarySession(() => {
throw new Error("deep provider must not start");
});
const server = createServer(
{ kind: "session", session },
{ recordedCrash: service },
);
const client = new Client({ name: "recorded-crash-contract", version: "1" });
onTestFinished(async () => {
await client.close();
await server.close();
expect(providerClosed).toBe(true);
await session.close();
});
const [clientTransport, serverTransport] =
InMemoryTransport.createLinkedPair();
await server.connect(serverTransport);
await client.connect(clientTransport);
const advertised = (await client.listTools()).tools.find(
(tool) => tool.name === "inspect_recorded_crash",
);
if (advertised?.outputSchema === undefined)
throw new Error("recorded crash must publish both schemas");
const ajv = new Ajv2020({ strict: false, validateFormats: false });
const inputSchema: Record<string, unknown> = advertised.inputSchema;
const outputSchema: Record<string, unknown> = advertised.outputSchema;
expect(ajv.validateSchema(inputSchema)).toBe(true);
expect(ajv.validateSchema(outputSchema)).toBe(true);
expect(ajv.validate(inputSchema, { path: value.artifact.path })).toBe(true);
expect(
ajv.validate(inputSchema, {
path: value.artifact.path,
approval: true,
}),
).toBe(false);
const response = await client.callTool({
name: "inspect_recorded_crash",
arguments: { path: value.artifact.path },
});
expect(response.isError).not.toBe(true);
expect(
ajv.validate(outputSchema, response.structuredContent),
JSON.stringify(ajv.errors),
).toBe(true);
const parsed = toolContract("inspect_recorded_crash").outputSchema.parse(
response.structuredContent,
);
const evidence = parseEvidence(parsed);
expect(evidence.confidence).toBe("observed");
expect(evidence.subject?.digest.sha256).toBe(value.artifact.sha256);
expect(parsed.normalized_result).toEqual(value);
expect(session.evidenceById(evidence.evidence_id)).toEqual(evidence);
expect(
(
await client.callTool({
name: "inspect_recorded_crash",
arguments: { path: "relative.elf" },
})
).isError,
).toBe(true);
});