Retain native fixture ownership through observed bounded cleanup and preserve files and handles when cleanup cannot be confirmed. Verified all seven files on Linux and native macOS, including cleanup failure and exit-wait controls. Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
208 lines
6.5 KiB
TypeScript
208 lines
6.5 KiB
TypeScript
import { afterEach, expect, it } from "vitest";
|
|
import { rizinTestInferiorTty } from "./RizinTestInferiorTty.js";
|
|
import {
|
|
createRizinCoverageHarness,
|
|
abortOnCheck,
|
|
createInjectedProcess,
|
|
incompleteStop,
|
|
} from "./RizinDebugSessionCoverage.fixtures.js";
|
|
|
|
const { cleanup, makeProvider, start, startInjected, createManager } =
|
|
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 = createManager(
|
|
{ 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 = createManager(
|
|
{ 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 = createManager(
|
|
{ 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 = createManager(
|
|
{ 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 = createManager(
|
|
{ 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);
|
|
},
|
|
);
|