mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 19:23:24 +00:00
refactor(verify): give the real-Ghidra and Kuna verifiers' roots to VerifierFixture (#11170)
This commit is contained in:
1 parent
6a3d00f85b
commit
5e625e3139
6 files changed
+81
-81
No files matched your search
@@ -7,14 +7,11 @@ import { execFile } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import {
|
||||
copyFile,
|
||||
mkdtemp,
|
||||
mkdir,
|
||||
readFile,
|
||||
readdir,
|
||||
rm,
|
||||
writeFile,
|
||||
} from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
@@ -33,6 +30,7 @@ import {
|
||||
completeVerifierRun,
|
||||
assertVerifierCleanupComplete,
|
||||
} from "./lib/verifier-run.mjs";
|
||||
import { VerifierFixture } from "./lib/verifier-fixture.mjs";
|
||||
|
||||
if (process.platform === "win32")
|
||||
throw new Error(
|
||||
@@ -40,7 +38,12 @@ if (process.platform === "win32")
|
||||
);
|
||||
const run = createVerifierRun(),
|
||||
exec = promisify(execFile);
|
||||
const root = await mkdtemp(join(tmpdir(), "rea-ghidra-checkpoints-"));
|
||||
// Ghidra children use the root as HOME and TMPDIR; a plain root admits them on every host.
|
||||
const workspace = await VerifierFixture.open({
|
||||
prefix: "rea-ghidra-checkpoints-",
|
||||
private: false,
|
||||
});
|
||||
const root = workspace.path;
|
||||
const runtime = join(root, "runtime"),
|
||||
checkpointDirectory = join(root, "checkpoints");
|
||||
const entrypoint = fileURLToPath(new URL("./rea.mjs", import.meta.url));
|
||||
@@ -474,7 +477,7 @@ int main(void) { CheckpointRecord record = { 3, 11 }; return rea_checkpoint_leaf
|
||||
}
|
||||
if (cleanupVerified) {
|
||||
try {
|
||||
await rm(root, { recursive: true, force: true });
|
||||
await workspace.release();
|
||||
} catch (cause) {
|
||||
report.cleanup_error = String(cause);
|
||||
report.retained_workspace = root;
|
||||
|
||||
@@ -4,28 +4,27 @@
|
||||
// is intentionally synthetic; it is not a claim about Ghidra's analysis history.
|
||||
import assert from "node:assert/strict";
|
||||
import { createHash } from "node:crypto";
|
||||
import {
|
||||
mkdtemp,
|
||||
mkdir,
|
||||
readFile,
|
||||
readdir,
|
||||
rm,
|
||||
writeFile,
|
||||
} from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
||||
import { join, resolve } from "node:path";
|
||||
import { createVerifierCommands } from "./lib/verify-commands.mjs";
|
||||
import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs";
|
||||
import {
|
||||
VerifierFixture,
|
||||
withVerifierFixture,
|
||||
} from "./lib/verifier-fixture.mjs";
|
||||
const installation = process.env.GHIDRA_INSTALL_DIR;
|
||||
if (!installation || !process.env.JAVA_HOME)
|
||||
throw new Error(
|
||||
"Set GHIDRA_INSTALL_DIR and JAVA_HOME to caller-installed Ghidra and JDK 21.",
|
||||
);
|
||||
const commands = await createVerifierCommands();
|
||||
let root;
|
||||
let commandsClosed = false;
|
||||
const targetHash = await withVerifierCleanup(async () => {
|
||||
root = await mkdtemp(join(tmpdir(), "rea-function-bundle-"));
|
||||
const workspace = await VerifierFixture.open({
|
||||
prefix: "rea-function-bundle-",
|
||||
private: false,
|
||||
});
|
||||
// Ghidra and the CLI run as owned commands that may use the root until they close.
|
||||
workspace.releaseAfter(() => commands.close());
|
||||
const targetHash = await withVerifierFixture(workspace, async () => {
|
||||
const root = workspace.path;
|
||||
const target = join(root, "fixture");
|
||||
const projectDir = join(root, "project");
|
||||
const scriptDir = join(root, "scripts");
|
||||
@@ -172,16 +171,7 @@ public class SetHistory extends GhidraScript {
|
||||
assert.equal(hash(await readFile(target)), targetHash);
|
||||
assert.deepEqual(await tree(projectDir), before);
|
||||
return targetHash;
|
||||
}, [
|
||||
async () => {
|
||||
await commands.close();
|
||||
commandsClosed = true;
|
||||
},
|
||||
async () => {
|
||||
if (commandsClosed && root !== undefined)
|
||||
await rm(root, { recursive: true, force: true });
|
||||
},
|
||||
]);
|
||||
});
|
||||
process.stdout.write(
|
||||
`PASS Ghidra function bundles: complete, summary, no-overwrite; target and project unchanged (${targetHash})\n`,
|
||||
);
|
||||
@@ -2,16 +2,8 @@
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { createHash } from "node:crypto";
|
||||
import {
|
||||
mkdir,
|
||||
mkdtemp,
|
||||
readFile,
|
||||
readdir,
|
||||
rm,
|
||||
writeFile,
|
||||
} from "node:fs/promises";
|
||||
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
||||
import { realpathSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { isAbsolute, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import {
|
||||
@@ -24,6 +16,10 @@ import { createAnalysisProfile } from "../dist/domain/analysisProfile.js";
|
||||
import { createEvidenceBundle } from "../dist/domain/evidenceBundle.js";
|
||||
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||
import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs";
|
||||
import {
|
||||
VerifierFixture,
|
||||
withVerifierFixture,
|
||||
} from "./lib/verifier-fixture.mjs";
|
||||
import {
|
||||
createVerifierMcpOwner,
|
||||
VerifierStdioTransport,
|
||||
@@ -56,10 +52,14 @@ export async function verifyProjectCoverage(run) {
|
||||
new URL("../tests/conformance/ghidra/", import.meta.url),
|
||||
);
|
||||
const entrypoint = fileURLToPath(new URL("./rea.mjs", import.meta.url));
|
||||
const workspace = await mkdtemp(
|
||||
join(tmpdir(), "rea-ghidra-project-coverage-"),
|
||||
);
|
||||
const fixture = await VerifierFixture.open({
|
||||
prefix: "rea-ghidra-project-coverage-",
|
||||
private: false,
|
||||
});
|
||||
const workspace = fixture.path;
|
||||
const connections = createVerifierMcpOwner();
|
||||
// The workspace is removed only after every MCP connection confirms.
|
||||
fixture.releaseAfter(() => connections.close());
|
||||
const target = join(workspace, "fixture");
|
||||
const projects = join(workspace, "projects");
|
||||
const env = {
|
||||
@@ -105,7 +105,6 @@ export async function verifyProjectCoverage(run) {
|
||||
);
|
||||
return requireMcpOutput(reply, name);
|
||||
}
|
||||
let commandCleanupIncomplete = false;
|
||||
const command = async (executable, args, timeoutMs) => {
|
||||
try {
|
||||
return await runOwnedCommand(
|
||||
@@ -119,13 +118,19 @@ export async function verifyProjectCoverage(run) {
|
||||
{ timeoutMs, diagnosticBytes: 16 * 1024 * 1024 },
|
||||
);
|
||||
} catch (cause) {
|
||||
commandCleanupIncomplete =
|
||||
// A command whose process group was not confirmed stopped may still
|
||||
// use the workspace, so the fixture keeps it.
|
||||
if (
|
||||
cause instanceof OwnedCommandFailure &&
|
||||
cause.cleanupOwner !== undefined;
|
||||
cause.cleanupOwner !== undefined
|
||||
)
|
||||
fixture.retain(
|
||||
`${executable} cleanup is incomplete; it may still use ${workspace}`,
|
||||
);
|
||||
throw cause;
|
||||
}
|
||||
};
|
||||
const result = await withVerifierCleanup(async () => {
|
||||
return await withVerifierFixture(fixture, async () => {
|
||||
await mkdir(projects);
|
||||
// REA_CC is the invoking developer's explicitly selected compiler, not target input.
|
||||
await command(
|
||||
@@ -400,14 +405,7 @@ export async function verifyProjectCoverage(run) {
|
||||
provider: installation.providerVersion,
|
||||
results: report,
|
||||
};
|
||||
}, [
|
||||
async () => {
|
||||
await connections.close();
|
||||
if (!commandCleanupIncomplete)
|
||||
await rm(workspace, { recursive: true, force: true });
|
||||
},
|
||||
]);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
import { verifyProjectCoverage } from "./verify-real-ghidra-project-coverage.mjs";
|
||||
|
||||
import { execFile } from "node:child_process";
|
||||
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import { isAbsolute, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
@@ -33,6 +32,11 @@ import {
|
||||
verifyInventoryOperations,
|
||||
} from "./verify-real-ghidra-inventory.mjs";
|
||||
import { completeVerifierRun, createVerifierRun } from "./lib/verifier-run.mjs";
|
||||
import {
|
||||
VerifierFixture,
|
||||
onceConfirmed,
|
||||
withVerifierFixture,
|
||||
} from "./lib/verifier-fixture.mjs";
|
||||
import { hasTypedSwitchEvidence } from "./verify-real-ghidra-switch-assertions.mjs";
|
||||
import { verifyGhidraRelocationSymbols } from "./lib/real-ghidra-relocation-symbols.mjs";
|
||||
|
||||
@@ -75,7 +79,12 @@ const expectedNativeTarget = nativeFixtureTarget(
|
||||
process.platform,
|
||||
process.arch,
|
||||
);
|
||||
const fixtureRoot = await mkdtemp(join(tmpdir(), "rea-ghidra-fixtures-"));
|
||||
// Ghidra children read and write under the root; a plain root admits them on every host.
|
||||
const fixtures = await VerifierFixture.open({
|
||||
prefix: "rea-ghidra-fixtures-",
|
||||
private: false,
|
||||
});
|
||||
const fixtureRoot = fixtures.path;
|
||||
const sourcePath = fileURLToPath(
|
||||
new URL("../tests/conformance/ghidra/inventory.c", import.meta.url),
|
||||
);
|
||||
@@ -123,7 +132,7 @@ for (const [command, role] of toolchain) {
|
||||
);
|
||||
}
|
||||
}
|
||||
try {
|
||||
await withVerifierFixture(fixtures, async () => {
|
||||
const common = ["-O0", "-g", "-fno-inline"];
|
||||
if (expectedNativeTarget.format === "elf") common.push("-fno-pie", "-no-pie");
|
||||
await exec(compiler, [...common, sourcePath, "-o", debugPath]);
|
||||
@@ -351,9 +360,7 @@ try {
|
||||
custom_target: custom === null ? null : summary(custom),
|
||||
cleanup: "complete",
|
||||
};
|
||||
} finally {
|
||||
await rm(fixtureRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
process.stdout.write(
|
||||
`${JSON.stringify({ verifier_run: await completeVerifierRun(verifierRun), ...report })}\n`,
|
||||
);
|
||||
@@ -521,6 +528,14 @@ async function verifyTarget(targetPath, variant, expectedTarget = null) {
|
||||
profileDigest: profile.value.profile.digest,
|
||||
});
|
||||
|
||||
// The fixture root is removed only after this client confirms it closed;
|
||||
// a failed close is retried by the fixture's release.
|
||||
const closeClient = fixtures.releaseAfter(
|
||||
onceConfirmed(async () => {
|
||||
const closed = await client.close();
|
||||
if (!closed.ok) throw closed.error;
|
||||
}),
|
||||
);
|
||||
let runtimeCoordinates;
|
||||
let primaryFailure;
|
||||
try {
|
||||
@@ -615,14 +630,16 @@ async function verifyTarget(targetPath, variant, expectedTarget = null) {
|
||||
primaryFailure = cause;
|
||||
throw cause;
|
||||
} finally {
|
||||
const closed = await client.close();
|
||||
if (!closed.ok)
|
||||
try {
|
||||
await closeClient();
|
||||
} catch (closeFailure) {
|
||||
throw primaryFailure === undefined
|
||||
? closed.error
|
||||
? closeFailure
|
||||
: new AggregateError(
|
||||
[primaryFailure, closed.error],
|
||||
[primaryFailure, closeFailure],
|
||||
"Ghidra verification and cleanup failed",
|
||||
);
|
||||
}
|
||||
if (runtimeCoordinates !== undefined)
|
||||
await assertCleanup(runtimeCoordinates);
|
||||
}
|
||||
|
||||
@@ -10,16 +10,7 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { execFile } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import {
|
||||
mkdir,
|
||||
mkdtemp,
|
||||
readFile,
|
||||
readdir,
|
||||
rm,
|
||||
stat,
|
||||
writeFile,
|
||||
} from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
||||
import { join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { parseArgs, promisify } from "node:util";
|
||||
@@ -52,6 +43,7 @@ import {
|
||||
completeVerifierRun,
|
||||
createVerifierRun,
|
||||
} from "./lib/verifier-run.mjs";
|
||||
import { VerifierFixture } from "./lib/verifier-fixture.mjs";
|
||||
|
||||
const exec = promisify(execFile);
|
||||
const { values } = parseArgs({
|
||||
@@ -94,7 +86,12 @@ const resourceScope = {
|
||||
}),
|
||||
run: (operation) => inspectionResources.run(operation),
|
||||
};
|
||||
const workspace = await mkdtemp(join(tmpdir(), "rea-kuna-lane-"));
|
||||
// Kuna children cache under the root; a plain root admits them on every host.
|
||||
const workspaceFixture = await VerifierFixture.open({
|
||||
prefix: "rea-kuna-lane-",
|
||||
private: false,
|
||||
});
|
||||
const workspace = workspaceFixture.path;
|
||||
const cacheRoot = join(workspace, "cache");
|
||||
// A server retains its workspace until both provider and transport cleanup
|
||||
// have positively completed, even if later lineage observation is empty.
|
||||
@@ -844,7 +841,7 @@ try {
|
||||
}
|
||||
if (cleanupComplete)
|
||||
try {
|
||||
await rm(workspace, { recursive: true, force: true });
|
||||
await workspaceFixture.release();
|
||||
} catch (cause) {
|
||||
cleanupComplete = false;
|
||||
failures.push(cause);
|
||||
|
||||
@@ -407,11 +407,6 @@
|
||||
"scripts/verify-pe-load-config.mjs": 1,
|
||||
"scripts/verify-published-package.mjs": 1,
|
||||
"scripts/verify-real-cutter.mjs": 1,
|
||||
"scripts/verify-real-ghidra-checkpoints.mjs": 1,
|
||||
"scripts/verify-real-ghidra-function-bundle.mjs": 1,
|
||||
"scripts/verify-real-ghidra-project-coverage.mjs": 1,
|
||||
"scripts/verify-real-ghidra.mjs": 1,
|
||||
"scripts/verify-real-kuna.mjs": 1,
|
||||
"scripts/verify-release-artifact.mjs": 1,
|
||||
"scripts/verify-release-exposure.mjs": 1,
|
||||
"scripts/verify-test-discovery.mjs": 1,
|
||||
|
||||
Reference in new issue
Block a user