Replace ambiguous absent observations with an owning Result producer for Java prerequisite probes. Preserve missing-command, runtime-failure and unresolved-property outcomes, process error codes, both bounded streams, raw byte counts and partial properties through provider and doctor adapters. Reject malformed numeric-prefix versions while retaining their source facts. Migrate all injected hosts to the named probe contract and remove the old parser/observation ownership from installation discovery. Validation: 17 focused files, 350 passed; npm run check passed. Public doctor fixtures distinguish absent/nonzero/malformed commands. Real installed JDK and Ghidra readiness facts matched across 20 baseline/candidate CLI tasks. Warm repeat raw replies6392tokens each, elapsed .448/.444s, CPU .662/.660s; sampled family memory varied and reversed direction on repeat. No causal performance improvement claim, true-cold or full-agent/fault-path measurement. Synchronous probe deadline/cancellation/family cleanup remains #4561 and needs coordinated discovery/admission supervision; no claim that it is fixed here. No real failing JVM or Windows runtime verification performed. Refs #4560, #4561
118 lines
4.0 KiB
TypeScript
118 lines
4.0 KiB
TypeScript
import { chmod, mkdir, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { expect } from "vitest";
|
|
import { elf, thinMach } from "../../../src/domain/binaryTarget.fixture.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { cliTest } from "../../support/cli/cliFixture.js";
|
|
|
|
cliTest.skipIf(!["darwin", "linux"].includes(process.platform)).for([
|
|
["missing", "missing_dependency", "runtime_missing"],
|
|
["nonzero", "runtime_failure", "runtime_unavailable"],
|
|
["properties", "config_drift", "version_unresolved"],
|
|
] as const)(
|
|
"preserves the %s Java prerequisite result through doctor",
|
|
async ([failure, classification, code], { cli }) => {
|
|
const root = await createTestTempDirectory("rea-java-doctor-");
|
|
const jdk = join(root, "jdk");
|
|
const install = join(root, "ghidra");
|
|
const platform = process.platform === "darwin" ? "mac" : "linux";
|
|
const architecture = process.arch === "arm64" ? "arm_64" : "x86_64";
|
|
const native = join(
|
|
install,
|
|
"Ghidra/Features/Decompiler/os",
|
|
`${platform}_${architecture}`,
|
|
"decompile",
|
|
);
|
|
for (const path of [
|
|
join(jdk, "bin"),
|
|
join(install, "support"),
|
|
join(install, "Ghidra"),
|
|
join(native, ".."),
|
|
])
|
|
await mkdir(path, { recursive: true });
|
|
await writeFile(
|
|
join(install, "Ghidra/application.properties"),
|
|
"application.version=12.1.4\napplication.java.min=21\n",
|
|
);
|
|
await writeFile(
|
|
join(install, "support/analyzeHeadless"),
|
|
"#!/bin/sh\nexit 99\n",
|
|
);
|
|
await chmod(join(install, "support/analyzeHeadless"), 0o700);
|
|
await writeFile(
|
|
native,
|
|
process.platform === "darwin"
|
|
? thinMach(
|
|
0xfeedfacf,
|
|
process.arch === "arm64" ? 0x0100000c : 0x01000007,
|
|
)
|
|
: elf(2, 1, process.arch === "arm64" ? 183 : 62),
|
|
);
|
|
await chmod(native, 0o700);
|
|
if (failure !== "missing") {
|
|
const body =
|
|
failure === "nonzero"
|
|
? 'process.stderr.write("controlled runtime failure\\n"); process.exit(7);'
|
|
: `process.stdout.write(${JSON.stringify(`java.home = ${jdk}\nos.arch = aarch64\n`)});`;
|
|
await writeFile(
|
|
join(jdk, "bin/java"),
|
|
`#!${process.execPath}\n${body}\n`,
|
|
);
|
|
await chmod(join(jdk, "bin/java"), 0o700);
|
|
}
|
|
const result = await cli.run({
|
|
arguments: ["doctor", "--provider", "ghidra", "--json"],
|
|
environment: {
|
|
JAVA_HOME: jdk,
|
|
GHIDRA_INSTALL_DIR: install,
|
|
REA_LOG_LEVEL: "silent",
|
|
},
|
|
});
|
|
expect(result.exitCode, `${result.stdout}\n${result.stderr}`).toBe(1);
|
|
expect(result.json).toMatchObject({
|
|
checks: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
name: "ghidra-java",
|
|
classification,
|
|
ok: false,
|
|
}),
|
|
]),
|
|
providerInspections: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
id: "ghidra",
|
|
checks: expect.arrayContaining([
|
|
expect.objectContaining({ name: "java", code }),
|
|
]),
|
|
}),
|
|
]),
|
|
});
|
|
if (failure === "nonzero")
|
|
expect(result.json).toMatchObject({
|
|
checks: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
name: "ghidra-java",
|
|
detail: expect.stringContaining("Java exited 7"),
|
|
details: expect.objectContaining({
|
|
output: expect.objectContaining({
|
|
stderr: "controlled runtime failure\n",
|
|
exit_code: 7,
|
|
stderr_bytes: 27,
|
|
}),
|
|
}),
|
|
}),
|
|
]),
|
|
});
|
|
if (failure === "properties")
|
|
expect(result.json).toMatchObject({
|
|
checks: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
name: "ghidra-java",
|
|
details: expect.objectContaining({
|
|
properties: expect.objectContaining({ home: jdk, version: null }),
|
|
}),
|
|
}),
|
|
]),
|
|
});
|
|
},
|
|
);
|