Files
rea/tests/boundary/process/ghidraDecompilerVerifierCleanup.test.ts
T
Syed Rishad c374c2fe72 fix(ghidra): preserve partial dossiers on decompiler failure (#4650)
Preserve listing-backed facts and explicit unavailable decompiler facets when one function fails. Retain Evidence while excluding transient failures and derived workflows from snapshot reuse; keep healthy cache reuse and owned cleanup semantics.

Fixes #4644.
2026-10-20 12:08:08 +00:00

113 lines
4.1 KiB
TypeScript

import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { expect } from "vitest";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { processTest } from "../../support/process/processFixture.js";
processTest
.skipIf(
!["linux", "darwin"].includes(process.platform) ||
!["x64", "arm64"].includes(process.arch),
)
.for([
{ outcome: "stopped", retained: false },
{ outcome: "incomplete", retained: true },
{ outcome: "unknown", retained: true },
])(
"preserves the compiler failure and respects $outcome cleanup ownership",
async ({ outcome, retained }, { processes }) => {
const root = await createTestTempDirectory("rea-decompiler-cleanup-test-");
const temporary = join(root, "temporary");
await mkdir(temporary);
const commandUrl = new URL(
"../../../dist/process/OwnedCommand.js",
import.meta.url,
).href;
const installationUrl = new URL(
"../../../scripts/lib/ghidra-installation-inspection.mjs",
import.meta.url,
).href;
const policyUrl = new URL(
"../../../dist/ghidra/GhidraReleasePolicy.js",
import.meta.url,
).href;
// Execute the production entry point and its real filesystem cleanup.
// Substitute only the two external prerequisites; no Ghidra is launched.
const commandSource = `
export * from ${JSON.stringify(`${commandUrl}?cleanup-test-original`)};
import { OwnedCommandFailure } from ${JSON.stringify(`${commandUrl}?cleanup-test-original`)};
import { writeFile } from "node:fs/promises";
export async function runOwnedCommand(command) {
await writeFile(command.arguments.at(-1), "partial compiler output");
if (${JSON.stringify(outcome)} === "unknown") throw new Error("opaque compiler failure");
throw new OwnedCommandFailure("process", "controlled compiler failure", null,
${JSON.stringify(outcome)} === "incomplete" ? "cleanup refused" : null,
undefined, [], ${JSON.stringify(outcome)} === "incomplete" ? { retained: true } : undefined);
}
`;
const installationSource = `
import { SUPPORTED_GHIDRA_VERSION } from ${JSON.stringify(policyUrl)};
export async function inspectVerifierGhidraInstallation() {
return { status: "available", providerVersion: SUPPORTED_GHIDRA_VERSION };
}
`;
const loader = join(root, "loader.mjs");
await writeFile(
loader,
`import { registerHooks } from "node:module";
registerHooks({ load(url, context, nextLoad) {
if (url === ${JSON.stringify(commandUrl)}) return { format: "module", shortCircuit: true, source: ${JSON.stringify(commandSource)} };
if (url === ${JSON.stringify(installationUrl)}) return { format: "module", shortCircuit: true, source: ${JSON.stringify(installationSource)} };
return nextLoad(url, context);
}});`,
);
const result = await processes.run(
process.execPath,
[
"--import",
loader,
fileURLToPath(
new URL(
"../../../scripts/verify-real-ghidra-decompiler-failure.mjs",
import.meta.url,
),
),
],
{
env: {
...process.env,
TMPDIR: temporary,
TMP: temporary,
TEMP: temporary,
},
timeoutMs: 15_000,
},
);
expect(result.exitCode, result.stderr).toBe(1);
expect(result.stderr).toContain(
outcome === "unknown"
? "opaque compiler failure"
: "controlled compiler failure",
);
const workspaces = await readdir(temporary);
expect(workspaces).toHaveLength(retained ? 1 : 0);
if (retained) {
expect(workspaces[0]).toMatch(/^rea-ghidra-decompiler-/u);
expect(
await readFile(
join(
temporary,
workspaces[0]!,
process.platform === "darwin"
? "cross-format.dylib"
: "cross-format.so",
),
"utf8",
),
).toBe("partial compiler output");
}
},
);