mirror of
https://github.com/morluto/rea.git
synced 2026-11-10 16:57:19 +00:00
Correct two independently reproduced CI fixture preconditions: close the real Rizin child's stdin before its readiness frame, and provide scoped Linux loader evidence for the Hopper doctor shell fixture. Preserve original timeouts, rejection checks, path spelling, launch prevention and cleanup. Current-head CI passed, including both complete reverse-tools mutation passes and 1,273 unique portable test files (14,521 passed, 126 skipped, zero failures). Each issue retains its own branch/commit; the validated integration tree includes the Hopper fix from #3410. That duplicate PR can close after the same code is verified on main. Fixes #3295 Fixes #3406 Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
212 lines
6.6 KiB
TypeScript
212 lines
6.6 KiB
TypeScript
import { afterEach, expect, it } from "vitest";
|
|
import { RizinDebugSessionManager } from "../../../src/rizin/RizinDebugSessionManager.js";
|
|
import {
|
|
createRizinTestManager,
|
|
rizinTestInferiorTty,
|
|
} from "./RizinTestInferiorTty.js";
|
|
import {
|
|
createRizinCoverageHarness,
|
|
abortOnCheck,
|
|
createInjectedProcess,
|
|
incompleteStop,
|
|
} from "./RizinDebugSessionCoverage.fixtures.js";
|
|
|
|
const { cleanup, makeProvider, start, startInjected, track } =
|
|
createRizinCoverageHarness();
|
|
afterEach(cleanup);
|
|
|
|
it.skipIf(process.platform === "win32")(
|
|
"stops the session when the provider closes its stdin pipe",
|
|
async () => {
|
|
const provider = await makeProvider(
|
|
// Readiness must guarantee that the child's stdin is already closed.
|
|
'exec 0<&-; printf "\\000"; while :; do :; done',
|
|
"#!/bin/sh",
|
|
);
|
|
const { manager, sessionId } = await start(provider, {
|
|
frameTimeoutMs: 500,
|
|
});
|
|
const result = await manager.execute(sessionId, "dr");
|
|
expect(result.ok).toBe(false);
|
|
expect(manager.status(sessionId)?.state).toBe("closed");
|
|
},
|
|
);
|
|
|
|
it
|
|
.skipIf(process.platform === "win32")
|
|
.each([
|
|
"null",
|
|
"destroyed",
|
|
"throws",
|
|
"throws-opaque",
|
|
"write-error",
|
|
] as const)(
|
|
"fails safely when injected Rizin stdin is %s",
|
|
async (stdinMode) => {
|
|
const injected = createInjectedProcess({ stdin: stdinMode });
|
|
const { manager, started } = await startInjected(injected.spawnProcess);
|
|
expect(started.ok).toBe(true);
|
|
if (!started.ok) return;
|
|
const result = await manager.execute(started.value.session_id, "dr");
|
|
expect(result.ok).toBe(false);
|
|
},
|
|
);
|
|
|
|
it.skipIf(process.platform === "win32")(
|
|
"fails an active command when the injected stdin stream closes",
|
|
async () => {
|
|
const injected = createInjectedProcess();
|
|
const { manager, started } = await startInjected(injected.spawnProcess);
|
|
expect(started.ok).toBe(true);
|
|
if (!started.ok) return;
|
|
injected.control.closeStdin();
|
|
const result = await manager.execute(started.value.session_id, "dr");
|
|
expect(result.ok).toBe(false);
|
|
},
|
|
);
|
|
|
|
it.skipIf(process.platform === "win32").each([false, true])(
|
|
"uses protocol cleanup when an injected write callback fails (incomplete=%s)",
|
|
async (incomplete) => {
|
|
const injected = createInjectedProcess({ stdin: "write-error" });
|
|
const { manager, started } = await startInjected(injected.spawnProcess, {
|
|
stopProcess: incomplete
|
|
? incompleteStop
|
|
: async () => ({ status: "stopped" }),
|
|
});
|
|
expect(started.ok).toBe(true);
|
|
if (!started.ok) return;
|
|
const result = await manager.execute(started.value.session_id, "dr");
|
|
expect(result.ok).toBe(false);
|
|
if (!result.ok && incomplete)
|
|
expect(result.error.message).toContain("injected stop uncertainty");
|
|
},
|
|
);
|
|
|
|
it.skipIf(process.platform === "win32")(
|
|
"maps injected spawn errors and startup protocol failure",
|
|
async () => {
|
|
for (const spawnError of [new Error("injected spawn failure"), "opaque"]) {
|
|
const injected = createInjectedProcess({ spawnError });
|
|
const manager = createRizinTestManager(
|
|
{ REA_RIZIN_COMMAND: "injected-rizin" },
|
|
{ spawnProcess: injected.spawnProcess },
|
|
);
|
|
const result = await manager.start({
|
|
path: "/tmp/injected",
|
|
inferior_tty: await rizinTestInferiorTty(),
|
|
});
|
|
expect(result.ok).toBe(false);
|
|
}
|
|
|
|
const injected = createInjectedProcess({
|
|
startup: (control) => control.closeStdin(),
|
|
});
|
|
const manager = createRizinTestManager(
|
|
{ REA_RIZIN_COMMAND: "injected-rizin" },
|
|
{ spawnProcess: injected.spawnProcess },
|
|
);
|
|
const result = await manager.start({
|
|
path: "/tmp/injected",
|
|
inferior_tty: await rizinTestInferiorTty(),
|
|
});
|
|
expect(result.ok).toBe(false);
|
|
if (!result.ok)
|
|
expect(result.error.message).toContain("stdin stream closed");
|
|
},
|
|
);
|
|
|
|
it.skipIf(process.platform === "win32")(
|
|
"maps spawn rejection as cancellation when the signal changes during spawn",
|
|
async () => {
|
|
const abort = new AbortController();
|
|
const injected = createInjectedProcess({
|
|
beforeSpawnReturns: () => abort.abort(),
|
|
spawnError: new Error("spawn interrupted"),
|
|
});
|
|
const manager = createRizinTestManager(
|
|
{ REA_RIZIN_COMMAND: "injected-rizin" },
|
|
{ spawnProcess: injected.spawnProcess },
|
|
);
|
|
const result = await manager.start(
|
|
{ path: "/tmp/injected", inferior_tty: await rizinTestInferiorTty() },
|
|
abort.signal,
|
|
);
|
|
expect(result.ok).toBe(false);
|
|
if (!result.ok) expect(result.error.message).toContain("cancelled");
|
|
},
|
|
);
|
|
|
|
it.skipIf(process.platform === "win32")(
|
|
"cancels startup after receiving its initial frame",
|
|
async () => {
|
|
const abort = new AbortController();
|
|
const injected = createInjectedProcess({
|
|
startup: (control) => {
|
|
control.output(Buffer.from([0]));
|
|
abort.abort();
|
|
},
|
|
});
|
|
const manager = createRizinTestManager(
|
|
{ REA_RIZIN_COMMAND: "injected-rizin" },
|
|
{ spawnProcess: injected.spawnProcess },
|
|
);
|
|
const result = await manager.start(
|
|
{ path: "/tmp/injected", inferior_tty: await rizinTestInferiorTty() },
|
|
abort.signal,
|
|
);
|
|
expect(result.ok).toBe(false);
|
|
},
|
|
);
|
|
|
|
it.skipIf(process.platform === "win32")(
|
|
"observes startup cancellation before the first frame waiter is installed",
|
|
async () => {
|
|
const abort = new AbortController();
|
|
const injected = createInjectedProcess({
|
|
beforeSpawnReturns: () => abort.abort(),
|
|
});
|
|
const manager = createRizinTestManager(
|
|
{ REA_RIZIN_COMMAND: "injected-rizin" },
|
|
{ spawnProcess: injected.spawnProcess },
|
|
);
|
|
const result = await manager.start(
|
|
{ path: "/tmp/injected", inferior_tty: await rizinTestInferiorTty() },
|
|
abort.signal,
|
|
);
|
|
expect(result.ok).toBe(false);
|
|
},
|
|
);
|
|
|
|
it.skipIf(process.platform === "win32")(
|
|
"cancels a command after its turn is granted",
|
|
async () => {
|
|
const injected = createInjectedProcess();
|
|
const { manager, started } = await startInjected(injected.spawnProcess);
|
|
expect(started.ok).toBe(true);
|
|
if (!started.ok) return;
|
|
const result = await manager.execute(
|
|
started.value.session_id,
|
|
"dr",
|
|
abortOnCheck(3),
|
|
);
|
|
expect(result.ok).toBe(false);
|
|
},
|
|
);
|
|
|
|
it.skipIf(process.platform === "win32")(
|
|
"cancels while waiting for the previous command turn",
|
|
async () => {
|
|
const injected = createInjectedProcess();
|
|
const { manager, started } = await startInjected(injected.spawnProcess);
|
|
expect(started.ok).toBe(true);
|
|
if (!started.ok) return;
|
|
const result = await manager.execute(
|
|
started.value.session_id,
|
|
"dr",
|
|
abortOnCheck(2),
|
|
);
|
|
expect(result.ok).toBe(false);
|
|
},
|
|
);
|