





















@@ -3,7 +3,17 @@ import { EventEmitter } from "node:events";
33import fs from "node:fs/promises";
44import os from "node:os";
55import path from "node:path";
6-import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
6+import {
7+afterAll,
8+afterEach,
9+beforeAll,
10+beforeEach,
11+describe,
12+expect,
13+it,
14+vi,
15+type MockInstance,
16+} from "vitest";
717import { MAX_SAFE_TIMEOUT_DELAY_MS } from "../../../gateway-client/src/timeouts.js";
818919const spawnMock = vi.hoisted(() => vi.fn());
@@ -24,13 +34,15 @@ import {
2434type QmdBinaryAvailability,
2535} from "./qmd-process.js";
263627-function createMockChild() {
37+function createMockChild(params: { pid?: number } = {}) {
2838const child = new EventEmitter() as EventEmitter & {
39+pid?: number;
2940stdout: EventEmitter;
3041stderr: EventEmitter;
3142kill: ReturnType<typeof vi.fn>;
3243closeWith: (code?: number | null, signal?: NodeJS.Signals | null) => void;
3344};
45+child.pid = params.pid;
3446child.stdout = new EventEmitter();
3547child.stderr = new EventEmitter();
3648child.kill = vi.fn();
@@ -42,7 +54,7 @@ function createMockChild() {
42544355let fixtureRoot = "";
4456let tempDir = "";
45-let platformSpy: { mockRestore(): void } | null = null;
57+let platformSpy: MockInstance<() => NodeJS.Platform> | null = null;
4658let fixtureId = 0;
4759const originalPath = process.env.PATH;
4860const originalPathExt = process.env.PATHEXT;
@@ -69,6 +81,7 @@ afterEach(() => {
6981vi.useRealTimers();
7082process.env.PATH = originalPath;
7183process.env.PATHEXT = originalPathExt;
84+platformSpy?.mockReturnValue("win32");
7285spawnMock.mockReset();
7386tempDir = "";
7487});
@@ -218,6 +231,34 @@ describe("checkQmdBinaryAvailability", () => {
218231219232expect(timeoutSpy).toHaveBeenCalledWith(expect.any(Function), MAX_SAFE_TIMEOUT_DELAY_MS);
220233});
234+235+it("kills timed-out availability probes by process group on POSIX", async () => {
236+platformSpy?.mockReturnValue("linux");
237+const killProcess = vi.spyOn(process, "kill").mockImplementation(() => true);
238+const child = createMockChild({ pid: 4321 });
239+spawnMock.mockReturnValueOnce(child);
240+241+try {
242+await expect(
243+checkQmdBinaryAvailability({
244+command: "qmd",
245+env: process.env,
246+cwd: tempDir,
247+timeoutMs: 1,
248+}),
249+).resolves.toEqual({
250+available: false,
251+reason: "binary",
252+error: "spawn qmd timed out after 1ms",
253+});
254+255+expect(spawnMock.mock.calls[0]?.[2]).toMatchObject({ detached: true });
256+expect(killProcess).toHaveBeenCalledWith(-4321, "SIGKILL");
257+expect(child.kill).not.toHaveBeenCalledWith("SIGKILL");
258+} finally {
259+killProcess.mockRestore();
260+}
261+});
221262});
222263223264describe("runCliCommand", () => {
@@ -341,4 +382,33 @@ describe("runCliCommand", () => {
341382342383expect(timeoutSpy).toHaveBeenCalledWith(expect.any(Function), MAX_SAFE_TIMEOUT_DELAY_MS);
343384});
385+386+it("kills timed-out cli command process groups on POSIX", async () => {
387+platformSpy?.mockReturnValue("linux");
388+const killProcess = vi.spyOn(process, "kill").mockImplementation(() => true);
389+const child = createMockChild({ pid: 8765 });
390+spawnMock.mockReturnValueOnce(child);
391+392+try {
393+const pending = runCliCommand({
394+commandSummary: "qmd query test",
395+spawnInvocation: { command: "qmd", argv: ["query", "test", "--json"] },
396+env: process.env,
397+cwd: tempDir,
398+maxOutputChars: 10_000,
399+timeoutMs: 1,
400+});
401+const timeoutAssertion = expect(pending).rejects.toThrow(
402+"qmd query test timed out after 1ms",
403+);
404+405+await timeoutAssertion;
406+407+expect(spawnMock.mock.calls[0]?.[2]).toMatchObject({ detached: true });
408+expect(killProcess).toHaveBeenCalledWith(-8765, "SIGKILL");
409+expect(child.kill).not.toHaveBeenCalledWith("SIGKILL");
410+} finally {
411+killProcess.mockRestore();
412+}
413+});
344414});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。