fix(doctor): interrupt Hopper display readiness on cancel

Fixes #6227. Forward caller cancellation to private-display selection and preserve its exact cause after joined selector finalization. Covers active, late-finalization, healthy and pre-aborted controls. Verified 3 x 14 source tests, 22 compiled process/CLI boundaries, full static checks and independent local Codex review. Greptile passed the current head. Used the explicitly authorized maintainer bypass: the current GitHub rerun remained queued and author-readiness metadata was not filled. Original-head code CI passed; no human-review attestation was manufactured.

Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
This commit is contained in:
Matt Van Horn authored and GitHub committed 2026-10-22 22:22:22 +08:00
1 parent 2589c0074d
commit 5d7d6ad9ab
2 files changed
+177

No files matched your search

+169
View File
@@ -15,6 +15,7 @@ import { AnalysisError } from "./domain/analysisErrorBase.js";
import { AnalysisCancelledError } from "./domain/analysisErrorCore.js";
import { createDoctorHostFixture } from "./application/Doctor.fixture.js";
import { runDoctorOwned } from "./application/Doctor.js";
import { waitForAbortable } from "./process/AbortablePromise.js";
const readyDisplay = {
ok: true,
@@ -158,6 +159,174 @@ test.each(["unavailable", "unknown"] as const)(
},
);
test.each([true, false])(
"interrupts the Hopper display readiness check when doctor is cancelled: cancel=%s",
async (cancel) => {
const controller = new AbortController();
const reason = { request: "cancel doctor display check" };
let enter!: () => void;
const entered = new Promise<void>((resolve) => {
enter = resolve;
});
let release!: () => void;
const gate = new Promise<void>((resolve) => {
release = resolve;
});
const selected = vi
.spyOn(privateDisplay, "selectLinuxPrivateDisplayStrategy")
.mockImplementation(async (options) => {
enter();
await waitForAbortable(gate, options.signal);
return readyDisplay;
});
const inspected = vi
.spyOn(pythonRuntime, "inspectLinuxHopperPythonRuntime")
.mockImplementation(async (_environment, signal) =>
signal?.aborted === true
? err(
new pythonRuntime.LinuxHopperPythonError(
"cancelled",
"Fixture cancellation",
signal.reason,
),
)
: ok({
interpreter: "/usr/bin/python3",
version: [3, 14],
library: {
status: "loadable",
path: "/fixture/libpython.so",
},
}),
);
const system = createSystemDoctorHost({}, controller.signal);
let settled = false;
const pending = runDoctorOwned(
undefined,
createDoctorHostFixture({
platform: "linux",
configuredHopperPath: "/fixture/Hopper",
signal: controller.signal,
linuxDemoRuntimeCheck: system.linuxDemoRuntimeCheck,
close: async () => {
await system.close?.();
return undefined;
},
}),
{ providers: ["hopper"] },
)
.then(
(report) => ({ report, error: undefined }),
(error: unknown) => ({ report: undefined, error }),
)
.finally(() => {
settled = true;
});
try {
await entered;
expect(selected.mock.calls).toHaveLength(1);
if (cancel) {
controller.abort(reason);
const result = await pending;
expect(selected.mock.calls[0]?.[0]).toHaveProperty(
"signal",
controller.signal,
);
expect(inspected).not.toHaveBeenCalled();
expect(result.error).toBeInstanceOf(AnalysisCancelledError);
expect((result.error as Error).cause).toBe(reason);
} else {
expect(settled).toBe(false);
expect(inspected).not.toHaveBeenCalled();
release();
const result = await pending;
expect(result.error).toBeUndefined();
expect(result.report?.checks).toContainEqual(
expect.objectContaining({ name: "hopper-demo-runtime", ok: true }),
);
}
} finally {
release();
await pending;
await system.close?.();
inspected.mockRestore();
selected.mockRestore();
}
},
5_000,
);
test("joins display finalization before preserving a late cancellation", async () => {
const controller = new AbortController();
const reason = { phase: "display finalization" };
let enter!: () => void;
const entered = new Promise<void>((resolve) => {
enter = resolve;
});
let release!: () => void;
const finalized = new Promise<void>((resolve) => {
release = resolve;
});
const selected = vi
.spyOn(privateDisplay, "selectLinuxPrivateDisplayStrategy")
.mockImplementation(async () => {
// Controlled selector finalization: cancellation must not detach this wait.
enter();
await finalized;
return readyDisplay;
});
const inspected = vi
.spyOn(pythonRuntime, "inspectLinuxHopperPythonRuntime")
.mockRejectedValue(
new Error("Python inspection must not start after cancellation"),
);
const system = createSystemDoctorHost({}, controller.signal);
let settled = false;
const pending = system.linuxDemoRuntimeCheck!()
.then(
() => undefined,
(error: unknown) => error,
)
.finally(() => {
settled = true;
});
try {
await entered;
controller.abort(reason);
await Promise.resolve();
expect(settled).toBe(false);
expect(inspected).not.toHaveBeenCalled();
release();
const failure = await pending;
expect(failure).toBeInstanceOf(AnalysisCancelledError);
expect((failure as Error).cause).toBe(reason);
expect(inspected).not.toHaveBeenCalled();
} finally {
release();
await pending;
await system.close?.();
inspected.mockRestore();
selected.mockRestore();
}
});
test("rejects a pre-aborted caller before the Hopper display check", async () => {
const controller = new AbortController();
controller.abort("pre-aborted");
const runtime = vi.fn();
await expect(
runDoctorOwned(
undefined,
createDoctorHostFixture({
platform: "linux",
signal: controller.signal,
linuxDemoRuntimeCheck: runtime,
}),
),
).rejects.toBeInstanceOf(AnalysisCancelledError);
expect(runtime).not.toHaveBeenCalled();
});
test("preserves both X11 and Python failures and their independent remediation", async () => {
const display = vi
.spyOn(privateDisplay, "selectLinuxPrivateDisplayStrategy")
+8
View File
@@ -120,7 +120,15 @@ const inspectLinuxHopperDemoRuntime = async (
const selection = await selectLinuxPrivateDisplayStrategy({
helperPath: HOPPER_DEMO_HELPER_PATH,
environment,
...(signal === undefined ? {} : { signal }),
});
if (signal?.aborted === true)
throw new AnalysisCancelledError("doctor", {
cause: signal.reason,
diagnostics: {
hopper_demo_runtime: { private_display: { ...selection.diagnostic } },
},
});
const python = await inspectLinuxHopperPythonRuntime(environment, signal);
const pythonFailure = !python.ok
? python.error