Files
rea/src/application/FunctionAnalysisEvidence.test.ts
T

150 lines
5.3 KiB
TypeScript

import { expect, it } from "vitest";
import { createAnalysisExecution } from "./AnalysisProvider.js";
import { executeFunctionAnalysisEvidence } from "./FunctionAnalysisEvidence.js";
import { AnalysisOutputError } from "../domain/analysisErrorCore.js";
import { parseEvidence } from "../domain/evidence.js";
import { createAnalysisProfile } from "../domain/analysisProfile.js";
import { ghidraFunctionDossier } from "../domain/ghidraValues.fixture.js";
import type { BinaryTarget } from "../domain/binaryTargetTypes.js";
import { err, ok } from "../domain/result.js";
const provider = {
id: "dossier-fixture",
name: "Synthetic dossier",
version: "1",
};
const target: BinaryTarget = {
path: "/selected/application.elf",
sha256: "a".repeat(64),
kind: "executable",
format: "elf",
architecture: "x86_64",
availableArchitectures: ["x86_64"],
};
const parameters = { procedure: "fixture_main" };
it("does not start function work when admission is already cancelled", async () => {
const reason = { request: "cancelled before provider admission" };
let calls = 0;
const analysis = {
execute: async () => {
calls++;
return ok(createAnalysisExecution(ghidraFunctionDossier(), provider));
},
};
const result = await executeFunctionAnalysisEvidence(
analysis,
parameters,
target,
{ signal: AbortSignal.abort(reason) },
);
if (result.ok) throw new Error("Expected pre-admission cancellation");
expect(result.error).toMatchObject({ _tag: "AnalysisCancelledError" });
expect(result.error.cause).toBe(reason);
expect(calls).toBe(0);
});
it.each(["selected-target", "no-subject", "provider-subject"] as const)(
"retains the completed dossier and its %s binding after cancellation",
async (binding) => {
const dossier = ghidraFunctionDossier();
const reason = { request: "cancelled after provider observation" };
const analysisProfile = createAnalysisProfile(provider, {
language: "x86:LE:64",
});
const relationshipCoverage = {
status: "partial" as const,
limitations: ["Indirect callers remain unknown"],
limited_directions: ["callers"] as ["callers"],
};
const subject = {
...target,
path: "/observed/provider.elf",
sha256: "b".repeat(64),
};
for (const aborted of [false, true]) {
const controller = new AbortController();
const execution = createAnalysisExecution(dossier, provider, {
...(binding === "selected-target"
? {}
: { subject: binding === "no-subject" ? null : subject }),
analysisProfile,
relationshipCoverage,
rawResult: { provider_text: "preserved" },
limitations: ["Synthetic provider observation"],
locations: [{ kind: "address", address: "0x401000" }],
});
const result = await executeFunctionAnalysisEvidence(
{
execute: async () => {
if (aborted) controller.abort(reason);
return ok(execution);
},
},
parameters,
target,
{ signal: controller.signal },
);
if (aborted) {
if (result.ok) throw new Error("Expected late cancellation");
expect(result.error).toMatchObject({ _tag: "AnalysisCancelledError" });
expect(result.error.cause).toBe(reason);
} else if (!result.ok) throw result.error;
const evidence = parseEvidence(
result.ok ? result.value : result.error.partialObservation,
);
expect(evidence.operation).toBe("analyze_function");
expect(evidence.parameters).toEqual(parameters);
expect(evidence.normalized_result).toEqual(dossier);
expect(evidence.raw_result).toEqual({ provider_text: "preserved" });
expect(evidence.provider).toEqual(provider);
expect(evidence.analysis_profile).toEqual(analysisProfile);
expect(evidence.relationship_coverage).toEqual(relationshipCoverage);
expect(evidence.limitations).toContain("Synthetic provider observation");
expect(evidence.locations).toContainEqual({
kind: "address",
address: "0x401000",
});
if (binding === "no-subject") expect(evidence.subject).toBeNull();
else
expect(evidence.subject?.local_path).toBe(
binding === "selected-target" ? target.path : subject.path,
);
}
},
);
it.each(["provider-failure", "invalid-dossier"] as const)(
"keeps %s primary if cancellation arrives before result consumption",
async (kind) => {
const controller = new AbortController();
const failure = new AnalysisOutputError(
"analyze_function",
"Provider failure",
);
const result = await executeFunctionAnalysisEvidence(
{
execute: async () => {
controller.abort({ request: "unrelated late cancellation" });
return kind === "provider-failure"
? err(failure)
: ok(
createAnalysisExecution({ procedure: "incomplete" }, provider),
);
},
},
parameters,
target,
{ signal: controller.signal },
);
if (result.ok) throw new Error("Expected the producer/output failure");
if (kind === "provider-failure") expect(result.error).toBe(failure);
else
expect(result.error).toMatchObject({
_tag: "AnalysisOutputError",
operation: "analyze_function",
});
expect(result.error.partialObservation).toBeUndefined();
},
);