Files
rea/tests/composition/reverseEngineeringEdgeCases.test.ts
T
morluto 46217c69bc refactor(rizin): share validated command requests across admission and execution
Resolve raw input at the application boundary and reuse its facts in both MCP admission paths. Keep cleanup, mutation retirement and execution ordering while retaining structured correction details for rejected commands.

Validation: 186 focused tests, source checks, docs conformance and duplication gate passed. The new compiled CLI regression fails on the main baseline. Provider process controls use deterministic fixtures; no real Rizin verification is claimed.
2026-10-23 04:29:35 +08:00

1054 lines
33 KiB
TypeScript

import { rm } from "node:fs/promises";
import type { Stats } from "node:fs";
import { expect, it, vi } from "vitest";
import { registerSharedReverseEngineeringEdgeCaseTests } from "../support/reverseEngineeringCompositionEdgeCases.js";
import { registerOwnedRizinCommandEvidenceTests } from "../support/ownedRizinCommandEvidenceTests.js";
import {
createArtifact,
providerSnapshot,
} from "../support/reverseEngineeringCompositionFixtures.js";
import { TestReverseEngineeringService as ReverseEngineeringService } from "../support/reverseEngineeringService.js";
import { AnalysisCapabilityUnavailableError } from "../../src/domain/analysisErrorCore.js";
import { ReverseEngineeringService as CoreReverseEngineeringService } from "../../src/application/reverse/ReverseEngineeringService.js";
import { createReverseEngineeringCommandPort } from "../../src/composition/reverseEngineering.js";
import { AnalysisResourceConstraintError } from "../../src/domain/analysisErrorCore.js";
import { objdumpCommand } from "../../src/objdump/ObjdumpCommand.js";
import {
OwnedCommandFailure,
runOwnedCommand,
} from "../../src/process/OwnedCommand.js";
import type { ProviderProcessSupervisor } from "../../src/process/ProviderProcess.js";
import { REVERSE_ENGINEERING_TOOL_CONTRACTS } from "../../src/contracts/reverseEngineeringToolContracts.js";
registerSharedReverseEngineeringEdgeCaseTests();
registerOwnedRizinCommandEvidenceTests({ sweepTokenOwnedProcesses: true });
it("records pathless objdump architecture support as provider capability evidence", async () => {
const { directory, path } = await createArtifact();
expect(objdumpCommand({ operation: "architectures" })).toEqual(["-i"]);
const digest = vi.fn(async () => "a".repeat(64));
const service = new CoreReverseEngineeringService({
commands: createReverseEngineeringCommandPort(process.env),
digest,
run: async () => ({ stdout: "elf64-x86-64", stderr: "" }),
});
try {
const result = await service.inspectWithObjdump({
operation: "architectures",
});
if (!result.ok) throw result.error;
expect(result.value.subject).toBeNull();
expect(result.value.parameters).not.toHaveProperty("path");
expect(result.value.parameters).not.toHaveProperty(
"artifact_sha256_before",
);
expect(result.value.normalized_result).toMatchObject({
artifact_sha256_after: null,
artifact_changed: null,
});
const contract = REVERSE_ENGINEERING_TOOL_CONTRACTS.find(
({ name }) => name === "inspect_with_objdump",
);
expect(contract?.outputSchema.safeParse(result.value).success).toBe(true);
expect(digest).not.toHaveBeenCalled();
expect(
await service.inspectWithObjdump({ path, operation: "architectures" }),
).toMatchObject({ ok: false, error: { _tag: "AnalysisInputError" } });
const ordinaryInspection = await service.inspectWithObjdump({
path,
operation: "file_headers",
});
expect(ordinaryInspection.ok).toBe(true);
expect(digest).toHaveBeenCalledTimes(2);
} finally {
await rm(directory, { recursive: true, force: true });
}
});
it("uses the host environment when the service environment is omitted", async () => {
const { directory, path } = await createArtifact();
const key = "REA_REVERSE_TOOLS_ENV_FALLBACK_TEST";
const previous = process.env[key];
process.env[key] = "host-value";
let observedEnvironment: NodeJS.ProcessEnv | undefined;
const service = new CoreReverseEngineeringService({
commands: createReverseEngineeringCommandPort(process.env),
run: async (_command, _args, options) => {
observedEnvironment = options?.env;
return { stdout: "ok", stderr: "" };
},
});
try {
const result = await service.inspectWithObjdump({
path,
operation: "file_headers",
});
expect(result.ok).toBe(true);
expect(observedEnvironment?.[key]).toBe("host-value");
} finally {
if (previous === undefined) delete process.env[key];
else process.env[key] = previous;
await rm(directory, { recursive: true, force: true });
}
});
it.each(["cancellation", "deadline"] as const)(
"interrupts a pending stat on %s and observes its eventual rejection",
async (interruption) => {
const { directory, path } = await createArtifact();
const controller = new AbortController();
let rejectStat: ((cause: Error) => void) | undefined;
let announceStat: (() => void) | undefined;
const statStarted = new Promise<void>((resolve) => {
announceStat = resolve;
});
const service = new ReverseEngineeringService({
stat: async () => {
announceStat?.();
return await new Promise<Stats>((_resolve, reject) => {
rejectStat = reject;
});
},
run: async () => ({ stdout: "", stderr: "" }),
});
try {
if (interruption === "deadline") vi.useFakeTimers();
const resultPromise = service.inspectWithObjdump(
{ path, operation: "file_headers" },
{ signal: controller.signal },
);
await statStarted;
if (interruption === "cancellation") controller.abort();
else await vi.advanceTimersByTimeAsync(120_000);
await expect(resultPromise).resolves.toMatchObject({
ok: false,
error: {
_tag:
interruption === "cancellation"
? "AnalysisCancelledError"
: "AnalysisTimeoutError",
},
});
rejectStat?.(new Error("late stat failure"));
await Promise.resolve();
} finally {
vi.useRealTimers();
await rm(directory, { recursive: true, force: true });
}
},
);
it("handles cancellation that races with stat listener registration", async () => {
const { directory, path } = await createArtifact();
let aborted = false;
const signal = {
get aborted() {
return aborted;
},
addEventListener: (
_type: string,
listener: Parameters<EventTarget["addEventListener"]>[1],
) => {
aborted = true;
if (typeof listener === "function") listener(new Event("abort"));
else listener.handleEvent(new Event("abort"));
},
removeEventListener: () => {},
dispatchEvent: () => true,
onabort: null,
reason: undefined,
throwIfAborted: () => {},
} as AbortSignal;
const stat = vi.fn(async () => ({ isFile: () => true }) as unknown as Stats);
const run = vi.fn(async () => ({ stdout: "", stderr: "" }));
const service = new ReverseEngineeringService({ stat, run });
try {
const result = await service.inspectWithObjdump(
{ path, operation: "file_headers" },
{ signal },
);
expect(result).toMatchObject({
ok: false,
error: { _tag: "AnalysisCancelledError" },
});
expect(stat).not.toHaveBeenCalled();
expect(run).not.toHaveBeenCalled();
} finally {
await rm(directory, { recursive: true, force: true });
}
});
it("handles a synchronous stat provider throw as an artifact input failure", async () => {
const { directory, path } = await createArtifact();
const stat = vi.fn(() => {
throw Object.assign(new Error("stat failed before returning a promise"), {
code: "ENOENT",
});
});
const run = vi.fn(async () => ({ stdout: "", stderr: "" }));
const service = new ReverseEngineeringService({
stat: stat as unknown as (path: string) => Promise<Stats>,
run,
});
try {
const result = await service.inspectWithObjdump({
path,
operation: "file_headers",
});
expect(result).toMatchObject({
ok: false,
error: { _tag: "AnalysisInputError" },
});
expect(stat).toHaveBeenCalledTimes(1);
expect(run).not.toHaveBeenCalled();
} finally {
await rm(directory, { recursive: true, force: true });
}
});
it("cancels an artifact stream when cancellation races with its listener registration", async () => {
const { directory, path } = await createArtifact();
let aborted = false;
let registrations = 0;
const signal = {
get aborted() {
return aborted;
},
addEventListener: (
_type: string,
listener: Parameters<EventTarget["addEventListener"]>[1],
) => {
registrations += 1;
if (registrations === 2) {
aborted = true;
if (typeof listener === "function") listener(new Event("abort"));
else listener.handleEvent(new Event("abort"));
}
},
removeEventListener: () => {},
dispatchEvent: () => true,
onabort: null,
reason: undefined,
throwIfAborted: () => {},
} as AbortSignal;
const stat = vi.fn(async () => ({ isFile: () => true }) as unknown as Stats);
const run = vi.fn(async () => ({ stdout: "", stderr: "" }));
const service = new ReverseEngineeringService({ stat, run });
try {
const result = await service.inspectWithObjdump(
{ path, operation: "file_headers" },
{ signal },
);
expect(result).toMatchObject({
ok: false,
error: { _tag: "AnalysisCancelledError" },
});
expect(registrations).toBe(2);
expect(stat).toHaveBeenCalledTimes(1);
expect(run).not.toHaveBeenCalled();
} finally {
await rm(directory, { recursive: true, force: true });
}
});
it("cancels before stream listeners when the signal changes after stat", async () => {
const { directory, path } = await createArtifact();
let abortedReads = 0;
const signal = {
get aborted() {
abortedReads += 1;
return abortedReads >= 5;
},
addEventListener: () => {},
removeEventListener: () => {},
dispatchEvent: () => true,
onabort: null,
reason: undefined,
throwIfAborted: () => {},
} as AbortSignal;
const stat = vi.fn(async () => ({ isFile: () => true }) as unknown as Stats);
const run = vi.fn(async () => ({ stdout: "", stderr: "" }));
const service = new ReverseEngineeringService({ stat, run });
try {
const result = await service.inspectWithObjdump(
{ path, operation: "file_headers" },
{ signal },
);
expect(result).toMatchObject({
ok: false,
error: { _tag: "AnalysisCancelledError" },
});
// Cancellation checks may be repeated by the digest cleanup path.
expect(abortedReads).toBeGreaterThanOrEqual(5);
expect(stat).toHaveBeenCalledTimes(1);
expect(run).not.toHaveBeenCalled();
} finally {
await rm(directory, { recursive: true, force: true });
}
});
it("rejects malformed Rizin requests before touching the provider", async () => {
const run = vi.fn(async () => ({ stdout: "", stderr: "" }));
const service = new ReverseEngineeringService({ run });
const result = await service.executeRizinCommand({ path: "sample.bin" });
expect(result).toMatchObject({
ok: false,
error: {
_tag: "AnalysisInputError",
operation: "execute_rizin_command",
issues: [
{ path: ["command"], reason: "missing_argument", expected: "string" },
],
},
});
expect(run).not.toHaveBeenCalled();
});
it("rejects directories as artifacts before invoking the provider", async () => {
const { directory } = await createArtifact();
const run = vi.fn(async () => ({ stdout: "", stderr: "" }));
const service = new ReverseEngineeringService({ run });
try {
const result = await service.inspectWithObjdump({
path: directory,
operation: "file_headers",
});
expect(result).toMatchObject({
ok: false,
error: { _tag: "AnalysisInputError", operation: "inspect_with_objdump" },
});
expect(run).not.toHaveBeenCalled();
} finally {
await rm(directory, { recursive: true, force: true });
}
});
it("rejects new operations after service shutdown begins", async () => {
const { directory, path } = await createArtifact();
const run = vi.fn(async () => ({ stdout: "", stderr: "" }));
const service = new ReverseEngineeringService({ run });
try {
await service.close();
const result = await service.executeRizinCommand({ path, command: "iI" });
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.error._tag).toBe("AnalysisOutputError");
expect(result.error.message).toContain(
"Reverse-engineering service is closed.",
);
}
expect(run).not.toHaveBeenCalled();
} finally {
await rm(directory, { recursive: true, force: true });
}
});
it("rejects work after reaching the concurrent provider-operation limit", async () => {
const { directory, path } = await createArtifact();
let releaseProviders: (() => void) | undefined;
const providerGate = new Promise<void>((resolve) => {
releaseProviders = resolve;
});
let providerCalls = 0;
const service = new ReverseEngineeringService({
run: async () => {
providerCalls += 1;
await providerGate;
return { stdout: "ok", stderr: "" };
},
});
try {
const admitted = Array.from({ length: 8 }, () =>
service.executeRizinCommand({ path, command: "iI" }),
);
const rejected = await service.executeRizinCommand({ path, command: "iI" });
expect(rejected.ok).toBe(false);
if (!rejected.ok) {
expect(rejected.error._tag).toBe("AnalysisResourceConstraintError");
expect(rejected.error).toBeInstanceOf(AnalysisResourceConstraintError);
if (rejected.error instanceof AnalysisResourceConstraintError)
expect(rejected.error.reportedLimits).toMatchObject({
max_concurrent_operations: 8,
active_operations: 8,
});
expect(rejected.error.message).toContain(
"Concurrent reverse-engineering operation capacity reached (8 operations).",
);
}
expect(providerCalls).toBe(0);
releaseProviders?.();
const results = await Promise.all(admitted);
expect(results.every((result) => result.ok)).toBe(true);
expect(providerCalls).toBe(8);
} finally {
releaseProviders?.();
await rm(directory, { recursive: true, force: true });
}
});
it("cancels when an AbortSignal becomes aborted during listener registration", async () => {
const { directory, path } = await createArtifact();
const controller = new AbortController();
let abortedReads = 0;
const signal = new Proxy(controller.signal, {
get(target, property) {
if (property === "aborted") return ++abortedReads > 1;
const value = Reflect.get(target, property, target) as unknown;
return typeof value === "function" ? value.bind(target) : value;
},
});
const run = vi.fn(async () => ({ stdout: "", stderr: "" }));
const service = new ReverseEngineeringService({ run });
try {
const result = await service.inspectWithObjdump(
{ path, operation: "file_headers" },
{ signal },
);
expect(result).toMatchObject({
ok: false,
error: { _tag: "AnalysisCancelledError" },
});
expect(abortedReads).toBe(2);
expect(run).not.toHaveBeenCalled();
} finally {
await rm(directory, { recursive: true, force: true });
}
});
it("drains an active provider operation before service shutdown resolves", async () => {
const { directory, path } = await createArtifact();
let signal: AbortSignal | undefined;
let releaseRun: (() => void) | undefined;
let runStarted: (() => void) | undefined;
const started = new Promise<void>((resolve) => {
runStarted = resolve;
});
const service = new ReverseEngineeringService({
run: async (_command, _args, options) => {
signal = options?.signal;
runStarted?.();
await new Promise<void>((resolve) => {
releaseRun = resolve;
});
return { stdout: "finished", stderr: "" };
},
});
try {
const operation = service.executeRizinCommand({ path, command: "iI" });
await started;
let closed = false;
const closing = service.close().then(() => {
closed = true;
});
expect(signal?.aborted).toBe(true);
expect(closed).toBe(false);
releaseRun?.();
await closing;
expect(closed).toBe(true);
await operation;
} finally {
releaseRun?.();
await rm(directory, { recursive: true, force: true });
}
});
it("relays caller cancellation to the admitted provider signal", async () => {
const { directory, path } = await createArtifact();
const controller = new AbortController();
const addAbortListener = vi.spyOn(controller.signal, "addEventListener");
let announceStarted: (() => void) | undefined;
const started = new Promise<void>((resolve) => {
announceStarted = resolve;
});
let releaseRun: (() => void) | undefined;
let providerSignal: AbortSignal | undefined;
const service = new CoreReverseEngineeringService({
commands: createReverseEngineeringCommandPort(process.env),
run: async (_command, _args, options) => {
providerSignal = options?.signal;
announceStarted?.();
await new Promise<void>((resolve) => {
releaseRun = resolve;
});
return { stdout: "captured", stderr: "" };
},
});
let operation: ReturnType<typeof service.inspectWithObjdump> | undefined;
try {
operation = service.inspectWithObjdump(
{ path, operation: "file_headers" },
{ signal: controller.signal },
);
await started;
controller.abort();
expect(addAbortListener).toHaveBeenCalledWith(
"abort",
expect.any(Function),
{ once: true },
);
expect(providerSignal?.aborted).toBe(true);
releaseRun?.();
await expect(operation).resolves.toMatchObject({
ok: false,
error: { _tag: "AnalysisCancelledError" },
});
} finally {
releaseRun?.();
if (operation !== undefined) await operation.catch(() => undefined);
await rm(directory, { recursive: true, force: true });
}
});
it("passes an AbortSignal-like caller value through without relaying it", async () => {
const { directory, path } = await createArtifact();
const signal = {
aborted: false,
addEventListener: vi.fn(() => {
throw new Error(
"A non-native signal must not register a relay listener.",
);
}),
removeEventListener: vi.fn(),
} as unknown as AbortSignal;
const digest = vi.fn(async (_path: string, receivedSignal?: AbortSignal) => {
expect(receivedSignal).toBe(signal);
return "a".repeat(64);
});
const run = vi.fn(async (_command, _args, options) => {
expect(options?.signal).toBe(signal);
return { stdout: "ok", stderr: "" };
});
const service = new CoreReverseEngineeringService({
commands: createReverseEngineeringCommandPort(process.env),
digest,
run,
});
try {
const result = await service.inspectWithObjdump(
{ path, operation: "file_headers" },
{ signal },
);
expect(result.ok).toBe(true);
expect(signal.addEventListener).not.toHaveBeenCalled();
expect(signal.removeEventListener).toHaveBeenCalledTimes(1);
expect(digest).toHaveBeenCalledTimes(2);
expect(run).toHaveBeenCalledTimes(1);
} finally {
await rm(directory, { recursive: true, force: true });
}
});
it("cancels after operation admission and before starting the artifact digest", async () => {
const { directory, path } = await createArtifact();
let reads = 0;
const signal = {
get aborted() {
reads += 1;
return reads > 1;
},
addEventListener() {},
removeEventListener() {},
throwIfAborted() {},
onabort: null,
reason: undefined,
dispatchEvent: () => true,
} as AbortSignal;
let digestStarted = false;
const service = new ReverseEngineeringService({
digest: async () => {
digestStarted = true;
return "a".repeat(64);
},
});
try {
const result = await service.inspectWithObjdump(
{ path, operation: "file_headers" },
{ signal },
);
expect(result).toMatchObject({
ok: false,
error: { _tag: "AnalysisCancelledError" },
});
expect(reads).toBeGreaterThanOrEqual(2);
expect(digestStarted).toBe(false);
} finally {
await rm(directory, { recursive: true, force: true });
}
});
it("preserves raw retained byte counts when output text replaces invalid UTF-8", async () => {
const { directory, path } = await createArtifact();
const snapshot = providerSnapshot({
stdout: { text: "�", bytes: 1, observedBytes: 1, invalidUtf8: true },
stderr: { text: "�", bytes: 2, observedBytes: 2, invalidUtf8: true },
exitCode: 7,
});
const service = new ReverseEngineeringService({
runOwned: async () => {
throw new OwnedCommandFailure(
"cancelled",
"Command was cancelled after output capture",
snapshot,
);
},
});
try {
const result = await service.executeRizinCommand({ path, command: "iI" });
expect(result).toMatchObject({
ok: false,
error: {
_tag: "AnalysisCancelledError",
capturedOutput: {
stdout: "�",
stderr: "�",
stdout_bytes: 1,
stderr_bytes: 2,
},
},
});
} finally {
await rm(directory, { recursive: true, force: true });
}
});
it("preserves the provider remediation when owned process preparation fails", async () => {
const { directory, path } = await createArtifact();
const remediation = new AnalysisCapabilityUnavailableError(
"rizin",
"execute_rizin_command",
"Rizin needs permission to inspect this process",
{ userMessage: "Grant process inspection permission, then retry." },
);
const service = new ReverseEngineeringService({
runOwned: async () => {
throw new OwnedCommandFailure(
"process",
"Could not prepare process ownership",
null,
null,
{ cause: remediation },
);
},
});
try {
const result = await service.executeRizinCommand({ path, command: "iI" });
expect(result).toMatchObject({
ok: false,
error: {
_tag: "AnalysisCapabilityUnavailableError",
providerId: "rizin",
reason: remediation.reason,
userMessage: remediation.userMessage,
},
});
} finally {
await rm(directory, { recursive: true, force: true });
}
});
it.each([
["Error", new Error("cleanup state unknown"), "cleanup state unknown"],
["non-Error", "cleanup state unknown", "cleanup state unknown"],
] as const)(
"preserves a thrown %s cleanup reason while blocking new provider work",
async (_label, cause, expectedReason) => {
const { directory, path } = await createArtifact();
const cleanupOwner = {
stop: vi.fn(async () => Promise.reject(cause)),
} as unknown as ProviderProcessSupervisor;
const runOwned = vi.fn<typeof runOwnedCommand>(async () => {
throw new OwnedCommandFailure(
"cancelled",
"command cancelled",
providerSnapshot({ exitCode: null }),
"cleanup pending",
undefined,
["run-id", "pid:42"],
cleanupOwner,
);
});
const service = new ReverseEngineeringService({ runOwned });
try {
const first = await service.executeRizinCommand({ path, command: "iI" });
expect(first.ok).toBe(false);
const blocked = await service.executeRizinCommand({
path,
command: "iI",
});
expect(blocked).toMatchObject({
ok: false,
error: {
_tag: "AnalysisOutputError",
cleanup: { reason: expectedReason, resources: ["run-id", "pid:42"] },
},
});
await expect(service.close()).rejects.toMatchObject({
cleanup: { reason: expectedReason, resources: ["run-id", "pid:42"] },
});
expect(cleanupOwner.stop).toHaveBeenCalledTimes(2);
expect(runOwned).toHaveBeenCalledTimes(1);
} finally {
await service.close().catch(() => undefined);
await rm(directory, { recursive: true, force: true });
}
},
);
it("releases a retained provider owner after cleanup succeeds", async () => {
const { directory, path } = await createArtifact();
const cleanupOwner = {
stop: vi.fn(async () => ({ status: "verified-cleanup" as const })),
} as unknown as ProviderProcessSupervisor;
const runOwned = vi
.fn<typeof runOwnedCommand>()
.mockRejectedValueOnce(
new OwnedCommandFailure(
"cancelled",
"command cancelled",
providerSnapshot({ exitCode: null }),
"cleanup pending",
undefined,
["run-id", "pid:42"],
cleanupOwner,
),
)
.mockResolvedValue(providerSnapshot());
const service = new ReverseEngineeringService({ runOwned });
try {
const first = await service.executeRizinCommand({ path, command: "iI" });
expect(first.ok).toBe(false);
const recovered = await service.executeRizinCommand({
path,
command: "iI",
});
expect(recovered.ok).toBe(true);
expect(cleanupOwner.stop).toHaveBeenCalledTimes(1);
expect(runOwned).toHaveBeenCalledTimes(2);
} finally {
await service.close().catch(() => undefined);
await rm(directory, { recursive: true, force: true });
}
});
it("retains a capability failure when its injected owned runner also reports incomplete cleanup", async () => {
const { directory, path } = await createArtifact();
const capability = new AnalysisCapabilityUnavailableError(
"rizin",
"execute_rizin_command",
"Ownership inspection is unavailable.",
{ diagnostics: { source: "owned-runner" } },
);
const cleanupOwner = {
stop: vi.fn(async () => ({ status: "verified-cleanup" as const })),
} as unknown as ProviderProcessSupervisor;
const runOwned = vi
.fn<typeof runOwnedCommand>()
.mockRejectedValueOnce(
new OwnedCommandFailure(
"process",
"Ownership inspection failed after launch.",
null,
"owned descendant remains",
{ cause: capability },
["run-id", "pid:42"],
cleanupOwner,
),
)
.mockResolvedValue(providerSnapshot());
const service = new ReverseEngineeringService({ runOwned });
try {
const first = await service.executeRizinCommand({ path, command: "iI" });
if (first.ok) throw new Error("Expected the capability failure");
expect(first.error._tag).toBe("AnalysisCapabilityUnavailableError");
expect(first.error).toBeInstanceOf(AnalysisCapabilityUnavailableError);
expect(first.error.diagnostics).toEqual({ source: "owned-runner" });
expect(first.error.cleanupIncomplete).toBe(true);
expect(first.error.cleanup).toMatchObject({
reason: "owned descendant remains",
resources: ["run-id", "pid:42"],
});
const recovered = await service.executeRizinCommand({
path,
command: "iI",
});
if (!recovered.ok) throw recovered.error;
expect(cleanupOwner.stop).toHaveBeenCalledOnce();
expect(first.error.cleanupIncomplete).toBe(false);
expect(runOwned).toHaveBeenCalledTimes(2);
} finally {
await service.close().catch(() => undefined);
await rm(directory, { recursive: true, force: true });
}
});
it("preserves a provider timeout when cancellation arrives during the final digest", async () => {
const { directory, path } = await createArtifact();
const controller = new AbortController();
let digests = 0;
const service = new ReverseEngineeringService({
digest: async () => {
digests += 1;
if (digests === 2) controller.abort();
return "a".repeat(64);
},
run: async () => {
throw Object.assign(new Error("provider timed out"), {
stdout: "partial timeout output",
stderr: "timeout diagnostic",
code: "ETIMEDOUT",
signal: null,
killed: true,
});
},
});
try {
const result = await service.executeRizinCommand(
{ path, command: "iI" },
{ signal: controller.signal },
);
expect(result).toMatchObject({
ok: false,
error: {
_tag: "AnalysisTimeoutError",
capturedOutput: {
stdout: "partial timeout output",
stderr: "timeout diagnostic",
},
partialObservation: {
raw_result: {
stdout: "partial timeout output",
completion_status: "unknown",
},
limitations: expect.arrayContaining([
expect.stringContaining("AnalysisTimeoutError"),
]),
},
},
});
} finally {
await rm(directory, { recursive: true, force: true });
}
});
it("classifies E2BIG as a provider command resource constraint", async () => {
const { directory, path } = await createArtifact();
const cleanupOwner = {
stop: vi.fn(async () => ({ status: "verified-cleanup" as const })),
} as unknown as ProviderProcessSupervisor;
const runOwned = vi
.fn<typeof runOwnedCommand>()
.mockRejectedValueOnce(
new OwnedCommandFailure(
"process",
"Command launch failed.",
null,
"owned descendant remains",
{
cause: Object.assign(new Error("argument list too long"), {
code: "E2BIG",
}),
},
["run-id", "pid:42"],
cleanupOwner,
),
)
.mockResolvedValue(providerSnapshot());
const service = new ReverseEngineeringService({
runOwned,
});
try {
const result = await service.executeRizinCommand({ path, command: "iI" });
expect(result).toMatchObject({
ok: false,
error: {
_tag: "AnalysisResourceConstraintError",
resource: "transport",
operation: "execute_rizin_command",
},
});
if (!result.ok)
expect(result.error.message).toContain(
"The host rejected the provider command because its argument list exceeds the process launch limit.",
);
if (!result.ok) {
expect(result.error.cleanupIncomplete).toBe(true);
expect(result.error.cleanup?.resources).toEqual(["run-id", "pid:42"]);
expect(result.error).toBeInstanceOf(AnalysisResourceConstraintError);
if (result.error instanceof AnalysisResourceConstraintError)
expect(result.error.reportedLimits).toMatchObject({
argument_bytes: expect.any(Number),
});
}
const retried = await service.executeRizinCommand({ path, command: "iI" });
expect(retried.ok).toBe(true);
if (!result.ok) expect(result.error.cleanupIncomplete).toBe(false);
expect(cleanupOwner.stop).toHaveBeenCalledOnce();
expect(runOwned).toHaveBeenCalledTimes(2);
} finally {
await rm(directory, { recursive: true, force: true });
}
});
it("preserves completed command Evidence while its post-run digest awaits cleanup", async () => {
const { directory, path } = await createArtifact();
const service = new ReverseEngineeringService({
runOwned: async () => {
throw new OwnedCommandFailure(
"process",
"Owned process cleanup remains incomplete.",
providerSnapshot({ exitCode: 0 }),
"descendant remains active",
undefined,
["run-id", "pid:42"],
);
},
});
try {
const result = await service.executeRizinCommand({ path, command: "iI" });
expect(result).toMatchObject({
ok: false,
error: {
_tag: "AnalysisOutputError",
partialObservation: {
raw_result: {
exit_code: 0,
completion_status: "complete",
artifact_sha256_after: null,
artifact_changed: null,
},
limitations: expect.arrayContaining([
expect.stringContaining(
"owned provider cleanup was incomplete at observation time",
),
]),
},
},
});
} finally {
await rm(directory, { recursive: true, force: true });
}
});
it("records an explicit loss marker when provider bytes are invalid UTF-8", async () => {
const { directory, path } = await createArtifact();
const service = new ReverseEngineeringService({
runOwned: async () =>
providerSnapshot({
stdout: {
text: "captured � output",
bytes: 17,
observedBytes: 17,
invalidUtf8: true,
},
}),
});
try {
const result = await service.executeRizinCommand({ path, command: "iI" });
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.value).toMatchObject({
raw_result: { stdout: "captured � output" },
});
expect(result.value.limitations).toContain(
"Captured stdout bytes contain invalid or incomplete UTF-8; replacement characters in the text capture do not preserve the original bytes.",
);
}
} finally {
await rm(directory, { recursive: true, force: true });
}
});
it.each([
[
"stderr only",
{
stderr: {
text: "diagnostic � output",
bytes: 22,
observedBytes: 22,
invalidUtf8: true,
},
},
"stderr",
],
[
"both streams",
{
stdout: {
text: "captured � output",
bytes: 17,
observedBytes: 17,
invalidUtf8: true,
},
stderr: {
text: "diagnostic � output",
bytes: 22,
observedBytes: 22,
invalidUtf8: true,
},
},
"stdout and stderr",
],
] as const)(
"records invalid UTF-8 loss for %s",
async (_label, overrides, streams) => {
const { directory, path } = await createArtifact();
const service = new ReverseEngineeringService({
runOwned: async () => providerSnapshot(overrides),
});
try {
const result = await service.executeRizinCommand({ path, command: "iI" });
expect(result.ok).toBe(true);
if (result.ok)
expect(result.value.limitations).toContain(
`Captured ${streams} bytes contain invalid or incomplete UTF-8; replacement characters in the text capture do not preserve the original bytes.`,
);
} finally {
await rm(directory, { recursive: true, force: true });
}
},
);