fix(ci): align diagnostic regressions and format merged changes
This commit is contained in:
1 parent
80c3d36dfa
commit
67756dacc0
7 files changed
+123
-95
No files matched your search
+1
-1
@@ -307,7 +307,7 @@ prove behavior on the verified Ghidra 12.1.4 and JDK 21 build.
|
||||
|
||||
| Ghidra lane | Supported runner/target | Additional local tools |
|
||||
| ------------------------------------------ | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
|
||||
| `npm run verify:ghidra` | Linux x64/arm64 ELF or macOS x64/arm64 Mach-O | Host C compiler, Ghidra 12.1.4, and full JDK 21 |
|
||||
| `npm run verify:ghidra` | Linux x64/arm64 ELF or macOS x64/arm64 Mach-O | Host C compiler, Ghidra 12.1.4, and full JDK 21 |
|
||||
| `npm run verify:ghidra:switch` | Linux x64 ELF; GCC/Clang optimized and stripped switch fixtures | GCC, Clang, GNU nm/objdump/strip, Ghidra 12.1.4, and full JDK 21 |
|
||||
| `npm run verify:ghidra:aarch64-jump-table` | Any supported Ghidra host; AArch64 ELF; byte/halfword tables; host ARM64 Mach-O | Clang with AArch64 target support, Ghidra 12.1.4, and full JDK 21 |
|
||||
| `npm run verify:ghidra:cross-format` | Any supported Ghidra host; also analyzes AArch64 ELF, x86-64 PE, and x86-64 Mach-O | `clang`, LLD, and `lld-link` in addition to host-lane prerequisites |
|
||||
|
||||
@@ -247,8 +247,7 @@ const inspectJavaRuntime = async (
|
||||
java_executable: java,
|
||||
phase: "module-inspection",
|
||||
exit_code: typeof output.code === "number" ? output.code : null,
|
||||
process_error_code:
|
||||
typeof output.code === "string" ? output.code : null,
|
||||
process_error_code: typeof output.code === "string" ? output.code : null,
|
||||
signal: output.signal,
|
||||
killed: output.killed,
|
||||
stdout: output.stdout,
|
||||
|
||||
@@ -34,33 +34,43 @@ const connect = async (environment: NodeJS.ProcessEnv) => {
|
||||
return client;
|
||||
};
|
||||
|
||||
cliTest.skipIf(process.platform === "win32")(
|
||||
"preserves the selected APK configuration through the compiled CLI and MCP factories",
|
||||
async ({ cli }) => {
|
||||
const root = await createTestTempDirectory("rea-android-composition-");
|
||||
const apk = join(root, "fixture.apk");
|
||||
await writeOrderedZip(apk, ["AndroidManifest.xml", "classes.dex"]);
|
||||
const environment = {
|
||||
REA_LOG_LEVEL: "silent",
|
||||
JAVA_HOME: "relative-fixture-jdk",
|
||||
};
|
||||
const cliResult = await cli.run({
|
||||
arguments: ["inspect-android-package", apk, "--json"],
|
||||
environment,
|
||||
timeoutMs: 10_000,
|
||||
});
|
||||
expect(cliResult.exitCode).toBe(1);
|
||||
expect(cliResult.json).toMatchObject({ code: "capability_unavailable" });
|
||||
const client = await connect(environment);
|
||||
const response = await client.callTool({
|
||||
name: "inspect_android_package",
|
||||
arguments: { path: apk },
|
||||
});
|
||||
expect(response.isError).toBe(true);
|
||||
expect(response.structuredContent).toEqual({ error: cliResult.json });
|
||||
expect(JSON.stringify(response.structuredContent)).toContain("JAVA_HOME");
|
||||
},
|
||||
);
|
||||
for (const { jarConfigured, setting } of [
|
||||
{ jarConfigured: false, setting: "REA_JADX_MCP_JAR" },
|
||||
{ jarConfigured: true, setting: "JAVA_HOME" },
|
||||
])
|
||||
cliTest.skipIf(process.platform === "win32")(
|
||||
`preserves the APK ${setting} failure through the compiled CLI and MCP factories`,
|
||||
async ({ cli }) => {
|
||||
const root = await createTestTempDirectory("rea-android-composition-");
|
||||
const apk = join(root, "fixture.apk");
|
||||
await writeOrderedZip(apk, ["AndroidManifest.xml", "classes.dex"]);
|
||||
const jar = join(root, "jadx-headless-mcp.jar");
|
||||
await writeFile(
|
||||
jar,
|
||||
"fixture jar; validation must fail before execution",
|
||||
);
|
||||
const environment = {
|
||||
REA_LOG_LEVEL: "silent",
|
||||
JAVA_HOME: "relative-fixture-jdk",
|
||||
...(jarConfigured ? { REA_JADX_MCP_JAR: jar } : {}),
|
||||
};
|
||||
const cliResult = await cli.run({
|
||||
arguments: ["inspect-android-package", apk, "--json"],
|
||||
environment,
|
||||
timeoutMs: 10_000,
|
||||
});
|
||||
expect(cliResult.exitCode).toBe(1);
|
||||
expect(cliResult.json).toMatchObject({ code: "capability_unavailable" });
|
||||
const client = await connect(environment);
|
||||
const response = await client.callTool({
|
||||
name: "inspect_android_package",
|
||||
arguments: { path: apk },
|
||||
});
|
||||
expect(response.isError).toBe(true);
|
||||
expect(response.structuredContent).toEqual({ error: cliResult.json });
|
||||
expect(JSON.stringify(response.structuredContent)).toContain(setting);
|
||||
},
|
||||
);
|
||||
|
||||
cliTest.skipIf(process.platform !== "linux")(
|
||||
"preserves the missing firmware command reason through both caller paths",
|
||||
|
||||
@@ -195,11 +195,16 @@ it("distinguishes an unreported Java version from an unsupported old version", a
|
||||
);
|
||||
await chmod(join(unresolvedHome, "bin", "java"), 0o700);
|
||||
await expect(
|
||||
inspectJadxAvailability({ REA_JADX_MCP_JAR: jar, JAVA_HOME: unresolvedHome }),
|
||||
inspectJadxAvailability({
|
||||
REA_JADX_MCP_JAR: jar,
|
||||
JAVA_HOME: unresolvedHome,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
status: "unavailable",
|
||||
code: "version_unresolved",
|
||||
reason: expect.stringContaining("did not report a parseable java.base version"),
|
||||
reason: expect.stringContaining(
|
||||
"did not report a parseable java.base version",
|
||||
),
|
||||
diagnostics: { stdout: "jdk.compiler@21\n", java_major: null },
|
||||
});
|
||||
|
||||
|
||||
@@ -7,11 +7,15 @@ const execute = promisify(execFile);
|
||||
|
||||
const runCli = async (arguments_: readonly string[]) => {
|
||||
try {
|
||||
const result = await execute(process.execPath, ["scripts/rea.mjs", ...arguments_], {
|
||||
cwd: process.cwd(),
|
||||
env: { ...process.env, REA_LOG_LEVEL: "silent" },
|
||||
timeout: 15_000,
|
||||
});
|
||||
const result = await execute(
|
||||
process.execPath,
|
||||
["scripts/rea.mjs", ...arguments_],
|
||||
{
|
||||
cwd: process.cwd(),
|
||||
env: { ...process.env, REA_LOG_LEVEL: "silent" },
|
||||
timeout: 15_000,
|
||||
},
|
||||
);
|
||||
return { exitCode: 0, ...result };
|
||||
} catch (cause: unknown) {
|
||||
if (
|
||||
@@ -46,5 +50,7 @@ it("shows MCP doctor help without starting a server and keeps option errors acti
|
||||
|
||||
const invalid = await runCli(["mcp", "doctor", "--not-an-option"]);
|
||||
expect(invalid.exitCode).toBe(1);
|
||||
expect(invalid.stdout).toContain("Unknown mcp doctor option: --not-an-option");
|
||||
expect(invalid.stdout).toContain(
|
||||
"Unknown mcp doctor option: --not-an-option",
|
||||
);
|
||||
});
|
||||
@@ -201,61 +201,63 @@ describe("Ghidra headless launcher", () => {
|
||||
it.skipIf(process.platform !== "win32")(
|
||||
"keeps authority in a private descriptor and isolates Ghidra state through the batch launcher",
|
||||
async () => {
|
||||
vi.stubEnv("GHIDRA_JAVA_OPTIONS", "-javaagent:/unapproved/agent.jar");
|
||||
vi.stubEnv("JAVA_TOOL_OPTIONS", "-Duser.home=/unapproved/home");
|
||||
vi.stubEnv("JDK_JAVA_OPTIONS", "-XX:MaxRAMPercentage=99");
|
||||
vi.stubEnv("_JAVA_OPTIONS", "-Xmx99G");
|
||||
const parent = await createTestTempDirectory("rea-launcher-test-");
|
||||
const runtime = await createGhidraTestRuntime(parent);
|
||||
runtimes.push(runtime);
|
||||
const runtimeRoot = runtime.path;
|
||||
const token = "secret-token-that-must-not-leak";
|
||||
const javaHome = "C:\\Java\\jdk-21";
|
||||
const launcher = new GhidraHeadlessLauncher({
|
||||
analyzeHeadlessPath: fixturePath,
|
||||
// POSIX follows the real inspected-JVM route; Windows uses the official
|
||||
// batch script because its launcher contract differs.
|
||||
platform: "win32",
|
||||
javaHome,
|
||||
bridgeScriptPath: "/package/bridge/ReaGhidraBridge.java",
|
||||
});
|
||||
const launched = await launcher.launch({
|
||||
runtimeRoot,
|
||||
transport: "unix-socket",
|
||||
endpointPath: join(runtimeRoot, "bridge.sock"),
|
||||
token,
|
||||
runId: "d6fcbb66-e829-4ff6-a535-0035aec63139",
|
||||
targetPath: "/tmp/fixture",
|
||||
targetSha256: "b".repeat(64),
|
||||
providerVersion: "12.1.4",
|
||||
profileDigest: "a".repeat(64),
|
||||
});
|
||||
if (!launched.ok) throw launched.error;
|
||||
const capturePath = join(runtimeRoot, "launch-capture.json");
|
||||
await vi.waitFor(() => access(`${capturePath}.ready`), { timeout: 10_000 });
|
||||
const capture = launchCaptureSchema.parse(
|
||||
JSON.parse(await readFile(capturePath, "utf8")),
|
||||
);
|
||||
const encodedArguments = JSON.stringify(capture.arguments);
|
||||
const encodedEnvironment = JSON.stringify(capture.environment);
|
||||
expect(encodedArguments).not.toContain(token);
|
||||
expect(encodedEnvironment).not.toContain(token);
|
||||
expect(capture).toMatchObject({
|
||||
...(process.platform === "win32" ? {} : { descriptor_mode: 0o600 }),
|
||||
descriptor_has_token: true,
|
||||
});
|
||||
expectIsolatedEnvironment(capture.environment, runtimeRoot, javaHome);
|
||||
expect(capture.environment.GHIDRA_HEADLESS_JAVA_OPTIONS).toBe(
|
||||
"",
|
||||
);
|
||||
if (process.platform !== "win32")
|
||||
expect(
|
||||
(await stat(join(runtimeRoot, "ownership.json"))).mode & 0o777,
|
||||
).toBe(0o600);
|
||||
vi.stubEnv("GHIDRA_JAVA_OPTIONS", "-javaagent:/unapproved/agent.jar");
|
||||
vi.stubEnv("JAVA_TOOL_OPTIONS", "-Duser.home=/unapproved/home");
|
||||
vi.stubEnv("JDK_JAVA_OPTIONS", "-XX:MaxRAMPercentage=99");
|
||||
vi.stubEnv("_JAVA_OPTIONS", "-Xmx99G");
|
||||
const parent = await createTestTempDirectory("rea-launcher-test-");
|
||||
const runtime = await createGhidraTestRuntime(parent);
|
||||
runtimes.push(runtime);
|
||||
const runtimeRoot = runtime.path;
|
||||
const token = "secret-token-that-must-not-leak";
|
||||
const javaHome = "C:\\Java\\jdk-21";
|
||||
const launcher = new GhidraHeadlessLauncher({
|
||||
analyzeHeadlessPath: fixturePath,
|
||||
// POSIX follows the real inspected-JVM route; Windows uses the official
|
||||
// batch script because its launcher contract differs.
|
||||
platform: "win32",
|
||||
javaHome,
|
||||
bridgeScriptPath: "/package/bridge/ReaGhidraBridge.java",
|
||||
});
|
||||
const launched = await launcher.launch({
|
||||
runtimeRoot,
|
||||
transport: "unix-socket",
|
||||
endpointPath: join(runtimeRoot, "bridge.sock"),
|
||||
token,
|
||||
runId: "d6fcbb66-e829-4ff6-a535-0035aec63139",
|
||||
targetPath: "/tmp/fixture",
|
||||
targetSha256: "b".repeat(64),
|
||||
providerVersion: "12.1.4",
|
||||
profileDigest: "a".repeat(64),
|
||||
});
|
||||
if (!launched.ok) throw launched.error;
|
||||
const capturePath = join(runtimeRoot, "launch-capture.json");
|
||||
await vi.waitFor(() => access(`${capturePath}.ready`), {
|
||||
timeout: 10_000,
|
||||
});
|
||||
const capture = launchCaptureSchema.parse(
|
||||
JSON.parse(await readFile(capturePath, "utf8")),
|
||||
);
|
||||
const encodedArguments = JSON.stringify(capture.arguments);
|
||||
const encodedEnvironment = JSON.stringify(capture.environment);
|
||||
expect(encodedArguments).not.toContain(token);
|
||||
expect(encodedEnvironment).not.toContain(token);
|
||||
expect(capture).toMatchObject({
|
||||
...(process.platform === "win32" ? {} : { descriptor_mode: 0o600 }),
|
||||
descriptor_has_token: true,
|
||||
});
|
||||
expectIsolatedEnvironment(capture.environment, runtimeRoot, javaHome);
|
||||
expect(capture.environment.GHIDRA_HEADLESS_JAVA_OPTIONS).toBe("");
|
||||
if (process.platform !== "win32")
|
||||
expect(
|
||||
(await stat(join(runtimeRoot, "ownership.json"))).mode & 0o777,
|
||||
).toBe(0o600);
|
||||
|
||||
const cleaned = await launched.value.cleanup?.();
|
||||
expect(cleaned).toMatchObject({ cleaned: true });
|
||||
await expect(access(join(runtimeRoot, "project"))).resolves.toBeUndefined();
|
||||
const cleaned = await launched.value.cleanup?.();
|
||||
expect(cleaned).toMatchObject({ cleaned: true });
|
||||
await expect(
|
||||
access(join(runtimeRoot, "project")),
|
||||
).resolves.toBeUndefined();
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -10,7 +10,7 @@ import { projectAnalysisError } from "../../../src/domain/analysisErrorProjectio
|
||||
import { type AnalysisError } from "../../../src/domain/analysisErrorBase.js";
|
||||
|
||||
describe("analysis error projection contract", () => {
|
||||
it("accepts every closed error-reason variant without exposing diagnostics", () => {
|
||||
it("accepts every closed error-reason variant", () => {
|
||||
const variants: AnalysisError[] = [
|
||||
...(
|
||||
[
|
||||
@@ -40,7 +40,9 @@ describe("analysis error projection contract", () => {
|
||||
).map((reason) => new UnknownRegistryError(reason)),
|
||||
...(["capture_failed", "cleanup_incomplete", "cancelled"] as const).map(
|
||||
(reason) =>
|
||||
new ProcessCaptureError("SECRET capture diagnostic", { reason }),
|
||||
new ProcessCaptureError("API_TOKEN=selected capture diagnostic", {
|
||||
reason,
|
||||
}),
|
||||
),
|
||||
...(
|
||||
[
|
||||
@@ -62,7 +64,11 @@ describe("analysis error projection contract", () => {
|
||||
JSON.stringify({ variant, projected }),
|
||||
).toMatchObject({ success: true });
|
||||
expect(projected.code).toMatch(/^[a-z][a-z0-9_]*$/u);
|
||||
expect(JSON.stringify(projected)).not.toContain("SECRET");
|
||||
if (variant instanceof ProcessCaptureError)
|
||||
expect(projected).toMatchObject({
|
||||
message: variant.message,
|
||||
details: { execution_failure: variant.message },
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user