From 461dc769782d84b263ec5b2bb696925e68c4a37a Mon Sep 17 00:00:00 2001 From: Dr Kennedy Umege <205099287+Kenmege@users.noreply.github.com> Date: Wed, 14 Oct 2026 22:40:35 +0100 Subject: [PATCH] fix(test): reap the Rizin provider left by startup cleanup uncertainty "preserves startup cleanup uncertainty after cancellation" starts a fake Rizin that never answers the detach request. The real stop therefore reports "inferior detach was not verified" and correctly retains ownership, and the injected incompleteStop teardown cannot stop it either, so every run left an orphaned fake-rizin.mjs process group behind (reproduced on macOS 27.0.1). Reap the owned launch in a finally block with cleanupOwnedProcessGroup and a SIGKILL fallback, then await its exit, matching the GDB review regression. Also drop the unused RizinDebugSessionManager import flagged by oxlint. Co-Authored-By: Claude Opus 5.5 --- .../RizinDebugSessionCoverage.part1.test.ts | 54 ++++++++++++++----- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/tests/boundary/providers/RizinDebugSessionCoverage.part1.test.ts b/tests/boundary/providers/RizinDebugSessionCoverage.part1.test.ts index 1f5c8e49c..88db7beac 100644 --- a/tests/boundary/providers/RizinDebugSessionCoverage.part1.test.ts +++ b/tests/boundary/providers/RizinDebugSessionCoverage.part1.test.ts @@ -1,5 +1,4 @@ import { afterEach, expect, it, vi } from "vitest"; -import { RizinDebugSessionManager } from "../../../src/rizin/RizinDebugSessionManager.js"; import { createRizinTestManager, stopFakeNoInferior, @@ -7,6 +6,7 @@ import { } from "./RizinTestInferiorTty.js"; import { rizinCommand } from "../../../src/rizin/RizinCommand.js"; import { spawnOwnedProviderProcess } from "../../../src/process/ProviderProcess.js"; +import { cleanupOwnedProcessGroup } from "../../../src/process/ProcessOwnership.js"; import { createRizinCoverageHarness, createInjectedProcess, @@ -298,29 +298,55 @@ it.skipIf(process.platform === "win32")( const provider = await makeProvider( "process.stdin.resume(); setInterval(() => {}, 1000);", ); + // This provider never answers the detach request, so the retained owner + // cannot stop it; the test reaps the owned launch itself afterwards. + let ownedLaunch: + | Awaited> + | undefined; + let ownedProcessExit: Promise | undefined; const manager = createRizinTestManager( { REA_RIZIN_COMMAND: provider }, { stopProcess: incompleteStop, spawnProcess: async (options) => { - const launched = await spawnOwnedProviderProcess(options); + ownedLaunch = await spawnOwnedProviderProcess(options); + ownedProcessExit = new Promise((resolve) => { + const child = ownedLaunch!.process; + if (child.exitCode !== null || child.signalCode !== null) resolve(); + else child.once("exit", () => resolve()); + }); resolveSpawned(); - return launched; + return ownedLaunch; }, }, ); track(manager); - const abort = new AbortController(); - const starting = manager.start( - { path: "/tmp/cancel-start", inferior_tty: await rizinTestInferiorTty() }, - abort.signal, - ); - await spawned; - abort.abort(); - const result = await starting; - expect(result.ok).toBe(false); - if (!result.ok) - expect(result.error.message).toContain("injected stop uncertainty"); + try { + const abort = new AbortController(); + const starting = manager.start( + { + path: "/tmp/cancel-start", + inferior_tty: await rizinTestInferiorTty(), + }, + abort.signal, + ); + await spawned; + abort.abort(); + const result = await starting; + expect(result.ok).toBe(false); + if (!result.ok) + expect(result.error.message).toContain("injected stop uncertainty"); + } finally { + if (ownedLaunch !== undefined) { + const reaped = await cleanupOwnedProcessGroup(ownedLaunch.ownership); + if (!reaped.cleaned) { + const child = ownedLaunch.process; + if (child.exitCode === null && child.signalCode === null) + child.kill("SIGKILL"); + } + await ownedProcessExit; + } + } }, );