mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 19:23:24 +00:00
fix(process): relay a start abort missed during listener registration and expect owner-closed Rizin starts (#11200)
* test(rizin): expect closeAll to abort in-flight starts and cancel TTY validation with a real signal * fix(process): relay a caller abort dispatched while the start registered its listener
This commit is contained in:
1 parent
456ca8e973
commit
4c5f5a3338
3 files changed
+62
-32
No files matched your search
@@ -139,6 +139,8 @@ export class OwnedDebuggerSessionRegistry<
|
||||
controller.abort(callerSignal?.reason);
|
||||
};
|
||||
callerSignal?.addEventListener("abort", relayAbort, { once: true });
|
||||
// An abort dispatched while the listener was being added never reaches it.
|
||||
if (callerSignal?.aborted) relayAbort();
|
||||
const started = Promise.resolve()
|
||||
.then(() => run(controller.signal))
|
||||
.finally(() => {
|
||||
|
||||
@@ -10,9 +10,9 @@ import type { SpawnedOwnedProviderProcess } from "../../../src/process/ProviderP
|
||||
import {
|
||||
createRizinCoverageHarness,
|
||||
createInjectedProcess,
|
||||
abortOnCheck,
|
||||
incompleteStop,
|
||||
} from "./RizinDebugSessionCoverage.fixtures.js";
|
||||
import { OwnerCancellation } from "../../../src/domain/analysisErrorCore.js";
|
||||
|
||||
const {
|
||||
cleanup,
|
||||
@@ -135,34 +135,10 @@ it.skipIf(process.platform === "win32")(
|
||||
);
|
||||
|
||||
it.skipIf(process.platform === "win32")(
|
||||
"does not spawn Rizin when cancellation arrives during TTY validation",
|
||||
async () => {
|
||||
let spawned = false;
|
||||
const manager = createManager(
|
||||
{ REA_RIZIN_COMMAND: "injected-rizin" },
|
||||
{
|
||||
spawnProcess: async () => {
|
||||
spawned = true;
|
||||
throw new Error("must not spawn after cancellation");
|
||||
},
|
||||
},
|
||||
);
|
||||
const result = await manager.start(
|
||||
{
|
||||
path: "/tmp/cancel-before-spawn",
|
||||
inferior_tty: await rizinTestInferiorTty(),
|
||||
},
|
||||
abortOnCheck(2),
|
||||
);
|
||||
expect(result.ok).toBe(false);
|
||||
expect(spawned).toBe(false);
|
||||
},
|
||||
);
|
||||
|
||||
it.skipIf(process.platform === "win32")(
|
||||
"closeAll drains concurrent startups and blocks later admission",
|
||||
"closeAll aborts concurrent startups as owner-closed, waits for them, and blocks later admission",
|
||||
async () => {
|
||||
let spawnCount = 0;
|
||||
let stopCount = 0;
|
||||
let resolveSpawnCalls!: () => void;
|
||||
const bothSpawnsCalled = new Promise<void>((resolve) => {
|
||||
resolveSpawnCalls = resolve;
|
||||
@@ -181,7 +157,10 @@ it.skipIf(process.platform === "win32")(
|
||||
await spawnGate;
|
||||
return injected.spawnProcess(options);
|
||||
},
|
||||
stopProcess: async () => ({ status: "stopped" }),
|
||||
stopProcess: async () => {
|
||||
stopCount += 1;
|
||||
return { status: "stopped" };
|
||||
},
|
||||
},
|
||||
);
|
||||
const tty = await rizinTestInferiorTty();
|
||||
@@ -195,14 +174,21 @@ it.skipIf(process.platform === "win32")(
|
||||
closeAllResolved = true;
|
||||
});
|
||||
await Promise.resolve();
|
||||
// The injected spawns ignore the abort, so closeAll waits for them.
|
||||
expect(closeAllResolved).toBe(false);
|
||||
releaseSpawns();
|
||||
const started = await Promise.all(starts);
|
||||
expect(started.every((result) => result.ok)).toBe(true);
|
||||
for (const result of started)
|
||||
expect(result).toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
_tag: "AnalysisCancelledError",
|
||||
cause: OwnerCancellation.ownerClosed,
|
||||
},
|
||||
});
|
||||
await closing;
|
||||
expect(
|
||||
manager.status(started[0]!.ok ? started[0]!.value.session_id : "missing"),
|
||||
).toBeUndefined();
|
||||
// Each process spawned before the abort was observed is stopped.
|
||||
expect(stopCount).toBe(2);
|
||||
expect(
|
||||
(await manager.start({ path: "/tmp/after-close-all", inferior_tty: tty }))
|
||||
.ok,
|
||||
|
||||
@@ -81,6 +81,48 @@ it.skipIf(process.platform === "win32")(
|
||||
},
|
||||
);
|
||||
|
||||
it.skipIf(process.platform === "win32")(
|
||||
"does not spawn Rizin when cancellation arrives during TTY validation",
|
||||
async () => {
|
||||
const actual =
|
||||
await vi.importActual<typeof import("node:fs/promises")>(
|
||||
"node:fs/promises",
|
||||
);
|
||||
vi.mocked(isatty).mockReturnValue(true);
|
||||
const controller = new AbortController();
|
||||
const reason = { cancelled: "during TTY validation" };
|
||||
vi.mocked(open).mockImplementation(async (...args) => {
|
||||
const handle = await actual.open(...args);
|
||||
controller.abort(reason);
|
||||
return handle;
|
||||
});
|
||||
onTestFinished(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
let spawned = false;
|
||||
const manager = new RizinDebugSessionManager(
|
||||
{},
|
||||
{
|
||||
platform: "linux",
|
||||
spawnProcess: () => {
|
||||
spawned = true;
|
||||
throw new Error("must not spawn after cancellation");
|
||||
},
|
||||
},
|
||||
);
|
||||
const result = await manager.start(
|
||||
{ path: "/fixture", inferior_tty: "/dev/null" },
|
||||
controller.signal,
|
||||
);
|
||||
expect(result).toMatchObject({
|
||||
ok: false,
|
||||
error: { _tag: "AnalysisCancelledError", cause: reason },
|
||||
});
|
||||
expect(spawned).toBe(false);
|
||||
await manager.closeAll();
|
||||
},
|
||||
);
|
||||
|
||||
it.skipIf(process.platform === "win32")(
|
||||
"preserves cancellation during final TTY close with failed cleanup",
|
||||
async () => {
|
||||
|
||||
Reference in new issue
Block a user