Compare commits

...
Author SHA1 Message Date
morluto 41bfee3b5d fix(test): reconcile Kuna target selection with main 2026-11-07 23:09:58 +08:00
Cursor Agent 947ffcd64c merge(main): record caller-target selection in the Kuna lane
testing.md now points at the moved lane. The selection report still
names the directory, requested functions, nm matches, and a partial
scan when symbol inspection fails.
2026-11-07 11:57:12 +00:00
morluto 8c5e7511c8 test(kuna): cover passing caller target selection 2026-11-01 08:54:13 +08:00
morluto 8c9bf74145 fix(test): report caller target selection coverage 2026-11-01 08:48:07 +08:00
4 changed files with 325 additions and 19 deletions

No files matched your search

+1 -1
View File
@@ -183,7 +183,7 @@ REA_KUNA_COMMAND=/absolute/path/to/kuna GHIDRA_INSTALL_DIR=/absolute/path/to/ghi
npm run verify:kuna -- --targets DIR --functions inflate,crc32 --compare-ghidra
```
The lane compiles a small source-owned fixture with the host C compiler and verifies the MCP workflow end to end. `--targets DIR --functions a,b` adds caller-built binaries. `--compare-ghidra` (with `GHIDRA_INSTALL_DIR`) runs those same functions through Ghidra; it requires `--targets` and `--functions` and refuses empty matrices or requested function names missing from every target. See [testing](testing.md#kuna-lane) for coverage.
The lane compiles a small source-owned fixture with the host C compiler and verifies the MCP workflow end to end. `--targets DIR --functions a,b` adds caller-built binaries. `--compare-ghidra` (with `GHIDRA_INSTALL_DIR`) runs those same functions through Ghidra; it requires `--targets` and `--functions` and refuses empty matrices or requested function names missing from every target. The report lists examined caller binaries and their matching requested symbols, including inputs omitted from analysis. See [testing](testing.md#kuna-lane) for coverage.
Failed runs also report process-lineage evidence and exit unsuccessfully. If provider or transport cleanup is unconfirmed, the report says `cleanup: "incomplete"` and records `retained_workspace`; that directory is preserved for investigation and cleanup retries.
+10 -1
View File
@@ -448,7 +448,16 @@ caller-built functions through Ghidra (it refuses to run without `--targets` and
reports latency, sampled process-tree CPU and peak memory, UTF-8 response size,
and goto and warning-marker counts. Those counts are not a correctness judgement,
and model tokens are not measured. Reference timings and response sizes are
reported separately; reference caches expire when the server closes. `--report PATH` writes the JSON report.
reported separately; reference caches expire when the server closes. The report's
`caller_target_selection` retains the selected directory, the original functions
argument, and each examined binary's requested names matched against `nm` text
symbols. `symbol_scan_complete` means the scan of non-`.stripped` entries in
the immediate flavor directories finished; it does not cover other directory
entries. An `inspection_failed` entry and `symbol_scan_complete: false` retain
partial discovery when symbol inspection fails. A missing symbol is not proof
that code is absent, and selection does not establish that a stripped counterpart
was verified; consult `results` for completed variants. `--report PATH` writes
the JSON report.
The first matrix run used Kuna 1.833 and Ghidra 12.1.4 with JDK 21 on macOS
arm64. It covered zlib 1.3.2 `minigzip`, coreutils 9.9 `sort` and the sqlite
+49 -13
View File
@@ -595,15 +595,37 @@ const measureGhidra = async (path, functions) => {
return answers;
};
/** Symbol-bearing caller binaries with the requested functions, beside their stripped copies. */
const targetPairs = async (root, names) => {
const describeFailure = (cause) => ({
message: cause instanceof Error ? cause.message : String(cause),
...(cause instanceof AggregateError
? { causes: cause.errors.map(describeFailure) }
: {}),
});
/** Inspect symbol-bearing caller binaries, retaining both selected and unmatched inputs. */
const targetPairs = async (root, names, selection) => {
const pairs = [];
for (const flavor of (await readdir(root)).sort()) {
const directory = join(root, flavor);
if (!(await stat(directory)).isDirectory()) continue;
for (const file of (await readdir(directory)).sort()) {
if (file.endsWith(".stripped")) continue;
const symbols = await symbolAddresses(join(directory, file), names);
let symbols;
try {
symbols = await symbolAddresses(join(directory, file), names);
} catch (cause) {
selection.examined.push({
binary: `${flavor}/${file}`,
status: "inspection_failed",
error: describeFailure(cause),
});
throw cause;
}
selection.examined.push({
binary: `${flavor}/${file}`,
status: symbols.size > 0 ? "selected" : "no_matching_text_symbols",
matched_functions: [...symbols.keys()],
});
if (symbols.size > 0) pairs.push({ flavor, directory, file, symbols });
}
}
@@ -624,6 +646,18 @@ const verifyVariant = async ({ flavor, directory, symbols }, variant) => {
const results = [];
let timeoutRecovery;
const failures = [];
const callerTargetSelection =
values.targets === undefined
? undefined
: {
directory: values.targets,
functions_argument: values.functions ?? null,
requested_functions: (values.functions ?? "")
.split(",")
.filter(Boolean),
symbol_scan_complete: false,
examined: [],
};
let inspectionResourcesClean = true;
try {
const installation = await inspectKunaInstallation(
@@ -636,13 +670,18 @@ try {
);
kunaVersion = `kuna ${installation.value.version}`;
let callerPairs = [];
if (values.targets !== undefined) {
const names = (values.functions ?? "").split(",").filter(Boolean);
if (callerTargetSelection !== undefined) {
const names = callerTargetSelection.requested_functions;
assert.ok(names.length > 0, "--targets requires --functions");
callerPairs = await targetPairs(values.targets, names);
callerPairs = await targetPairs(
callerTargetSelection.directory,
names,
callerTargetSelection,
);
callerTargetSelection.symbol_scan_complete = true;
assert.ok(
callerPairs.length > 0,
`No caller-built targets contain the requested functions in ${values.targets}`,
`No caller-built targets contain the requested functions in ${callerTargetSelection.directory}`,
);
const found = new Set(
callerPairs.flatMap(({ symbols }) => [...symbols.keys()]),
@@ -730,18 +769,15 @@ if (cleanupComplete)
cleanupComplete = false;
failures.push(cause);
}
const describeFailure = (cause) => ({
message: cause instanceof Error ? cause.message : String(cause),
...(cause instanceof AggregateError
? { causes: cause.errors.map(describeFailure) }
: {}),
});
const report = {
lane: "kuna",
status: failures.length === 0 ? "passed" : "failed",
kuna_version: kunaVersion,
platform: `${process.platform}/${process.arch}`,
verifier_run: lifecycle,
...(callerTargetSelection === undefined
? {}
: { caller_target_selection: callerTargetSelection }),
results,
...(timeoutRecovery === undefined
? {}
@@ -1,5 +1,6 @@
import { spawnSync } from "node:child_process";
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
import { createHash } from "node:crypto";
import { copyFile, mkdir, readFile, rm, writeFile } from "node:fs/promises";
import { join, resolve } from "node:path";
import { pathToFileURL } from "node:url";
@@ -10,6 +11,164 @@ import { fakeKunaCalls, writeFakeKuna } from "./fakeKuna.js";
const it = test.skipIf(process.platform === "win32");
const createCallerMatrix = async (root: string) => {
const targets = join(root, "targets");
const binaries: string[] = [];
for (const [flavor, name, multiplier] of [
["left", "audit_left", 3],
["right", "audit_right", 7],
["unmatched", "audit_other", 11],
] as const) {
const directory = join(targets, flavor);
await mkdir(directory, { recursive: true });
const source = join(root, `${flavor}.c`);
const binary = join(directory, "program");
await writeFile(
source,
`__attribute__((noinline,used)) int ${name}(int x) { return x * ${multiplier} + 1; }\nint main(void) { return ${name}(2); }\n`,
);
const compiled = spawnSync(
process.env.REA_CC ?? "cc",
["-O1", "-o", binary, source],
{ encoding: "utf8", timeout: 30_000 },
);
expect(compiled.error).toBeUndefined();
expect(compiled.status, compiled.stderr).toBe(0);
const stripped = `${binary}.stripped`;
await copyFile(binary, stripped);
const strip = spawnSync(process.env.REA_STRIP ?? "strip", [stripped], {
encoding: "utf8",
timeout: 30_000,
});
expect(strip.error).toBeUndefined();
expect(strip.status, strip.stderr).toBe(0);
binaries.push(binary, stripped);
}
return { targets, binaries };
};
const expectedCallerSelection = (targets: string) => ({
directory: targets,
functions_argument: "audit_left,audit_right",
requested_functions: ["audit_left", "audit_right"],
symbol_scan_complete: true,
examined: [
{
binary: "left/program",
status: "selected",
matched_functions: ["audit_left"],
},
{
binary: "right/program",
status: "selected",
matched_functions: ["audit_right"],
},
{
binary: "unmatched/program",
status: "no_matching_text_symbols",
matched_functions: [],
},
],
});
it("accepts requested functions distributed across caller targets", async () => {
const root = await createTestTempDirectory(
"rea-kuna-verifier-matrix-boundary-",
);
const { targets } = await createCallerMatrix(root);
const kuna = await writeFakeKuna(root);
const result = spawnSync(
process.execPath,
[
"scripts/verify-real-kuna.mjs",
"--targets",
targets,
"--functions",
"audit_left,audit_right",
],
{
encoding: "utf8",
timeout: 10_000,
env: {
PATH: process.env.PATH,
HOME: root,
REA_KUNA_COMMAND: kuna,
REA_CC: join(root, "compiler-must-not-run"),
},
},
);
expect(result.error).toBeUndefined();
expect(result.status).toBe(1);
expect(result.stderr).toContain("compiler-must-not-run");
expect(JSON.parse(result.stdout)).toMatchObject({
status: "failed",
cleanup: "complete",
caller_target_selection: expectedCallerSelection(targets),
});
});
test.skipIf(
process.platform === "win32" || process.env.REA_KUNA_COMMAND === undefined,
)(
"reports the complete selection of a passing real-provider matrix",
async () => {
const root = await createTestTempDirectory("rea-kuna-verifier-matrix-");
const { targets, binaries } = await createCallerMatrix(root);
const hashes = async () =>
Promise.all(
binaries.map(async (path) =>
createHash("sha256")
.update(await readFile(path))
.digest("hex"),
),
);
const before = await hashes();
const reportPath = join(root, "report.json");
const result = spawnSync(
process.execPath,
[
"scripts/verify-real-kuna.mjs",
"--targets",
targets,
"--functions",
"audit_left,audit_right",
"--report",
reportPath,
],
{
encoding: "utf8",
timeout: 100_000,
maxBuffer: 10 * 1024 * 1024,
env: process.env,
},
);
expect(result.error).toBeUndefined();
expect(result.status, result.stderr).toBe(0);
const report = JSON.parse(await readFile(reportPath, "utf8"));
expect(JSON.parse(result.stdout)).toEqual(report);
expect(report).toMatchObject({
status: "passed",
cleanup: "complete",
verifier_run: {
process_lineage: { status: "verified", descendants: [] },
},
caller_target_selection: expectedCallerSelection(targets),
});
expect(
report.results.map(({ binary }: { binary: string }) => binary),
).toEqual([
"fixture/rea-kuna-fixture",
"fixture/rea-kuna-fixture.stripped",
"left/program",
"left/program.stripped",
"right/program",
"right/program.stripped",
]);
expect(await hashes()).toEqual(before);
},
120_000,
);
it("accepts empty caller inventories but requires source-owned fixture literals", async () => {
const root = await createTestTempDirectory("rea-kuna-verifier-empty-");
const result = spawnSync(
@@ -48,16 +207,39 @@ await assert.rejects(() => verifyKunaStrings({ ...options, expectedStrings: ["re
});
it.each([
{ populated: false, names: "main", reason: "No caller-built targets" },
{ populated: true, names: "typo", reason: "No caller-built targets" },
{
populated: false,
names: "main",
reason: "No caller-built targets",
examined: [],
},
{
populated: true,
names: "typo",
reason: "No caller-built targets",
examined: [
{
binary: "linux/program",
status: "no_matching_text_symbols",
matched_functions: [],
},
],
},
{
populated: true,
names: "main,typo",
reason: "Requested functions were not found",
examined: [
{
binary: "linux/program",
status: "selected",
matched_functions: ["main"],
},
],
},
])(
"rejects an unverified caller matrix: $names, populated=$populated",
async ({ populated, names, reason }) => {
async ({ populated, names, reason, examined }) => {
const root = await createTestTempDirectory("rea-kuna-verifier-selection-");
const kuna = await writeFakeKuna(root);
const targets = join(root, "targets");
@@ -104,12 +286,91 @@ it.each([
expect(result.status).toBe(1);
expect(result.stderr).toContain(reason);
expect(result.stderr).not.toContain("compiler-must-not-run");
const report = JSON.parse(result.stdout);
expect(report.caller_target_selection).toEqual({
directory: targets,
functions_argument: names,
requested_functions: names.split(","),
symbol_scan_complete: true,
examined,
});
expect(report.cleanup).toBe("complete");
expect((await fakeKunaCalls(kuna)).map(({ argv }) => argv)).toEqual([
["--version"],
]);
},
);
it("retains examined caller targets when a later symbol inspection fails", async () => {
const root = await createTestTempDirectory("rea-kuna-verifier-inspection-");
const kuna = await writeFakeKuna(root);
const targets = join(root, "targets");
await mkdir(join(targets, "first"), { recursive: true });
await mkdir(join(targets, "second"), { recursive: true });
await writeFile(join(targets, "first", "program"), "not analyzed");
await writeFile(join(targets, "second", "broken"), "not analyzed");
const nm = join(root, "nm");
await writeFile(
nm,
`#!${process.execPath}\nif (process.argv.at(-1).endsWith("broken")) { process.stderr.write("inspection failed"); process.exit(9); }\nprocess.stdout.write("00001000 T main\\n");\n`,
{ mode: 0o755 },
);
const reportPath = join(root, "report.json");
const result = spawnSync(
process.execPath,
[
"scripts/verify-real-kuna.mjs",
"--targets",
targets,
"--functions",
"main",
"--report",
reportPath,
],
{
encoding: "utf8",
timeout: 10_000,
env: {
PATH: process.env.PATH,
HOME: root,
REA_KUNA_COMMAND: kuna,
REA_NM: nm,
REA_CC: join(root, "compiler-must-not-run"),
},
},
);
expect(result.error).toBeUndefined();
expect(result.status).toBe(1);
const report = JSON.parse(await readFile(reportPath, "utf8"));
expect(JSON.parse(result.stdout)).toEqual(report);
expect(report).toMatchObject({
status: "failed",
cleanup: "complete",
caller_target_selection: {
directory: targets,
functions_argument: "main",
requested_functions: ["main"],
symbol_scan_complete: false,
examined: [
{
binary: "first/program",
status: "selected",
matched_functions: ["main"],
},
{
binary: "second/broken",
status: "inspection_failed",
error: { message: expect.stringContaining("inspection failed") },
},
],
},
});
expect(result.stderr).not.toContain("compiler-must-not-run");
expect((await fakeKunaCalls(kuna)).map(({ argv }) => argv)).toEqual([
["--version"],
]);
});
it.each([
{
rejectClose: false,