





















@@ -17,12 +17,14 @@ import {
1717import { MAX_SAFE_TIMEOUT_DELAY_MS } from "../../../gateway-client/src/timeouts.js";
18181919const spawnMock = vi.hoisted(() => vi.fn());
20+const spawnSyncMock = vi.hoisted(() => vi.fn());
20212122vi.mock("node:child_process", async () => {
2223const actual = await vi.importActual<typeof import("node:child_process")>("node:child_process");
2324return {
2425 ...actual,
2526spawn: spawnMock,
27+spawnSync: spawnSyncMock,
2628};
2729});
2830@@ -58,6 +60,17 @@ let platformSpy: MockInstance<() => NodeJS.Platform> | null = null;
5860let fixtureId = 0;
5961const originalPath = process.env.PATH;
6062const originalPathExt = process.env.PATHEXT;
63+const originalSystemRoot = process.env.SystemRoot;
64+const originalWindir = process.env.WINDIR;
65+const taskkillPath = path.win32.join("C:\\Windows", "System32", "taskkill.exe");
66+67+function restoreEnvValue(key: string, value: string | undefined): void {
68+if (value === undefined) {
69+delete process.env[key];
70+return;
71+}
72+process.env[key] = value;
73+}
61746275beforeAll(async () => {
6376fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-qmd-win-spawn-"));
@@ -75,14 +88,20 @@ afterAll(async () => {
7588beforeEach(async () => {
7689tempDir = path.join(fixtureRoot, `case-${fixtureId++}`);
7790await fs.mkdir(tempDir, { recursive: true });
91+process.env.SystemRoot = "C:\\Windows";
92+delete process.env.WINDIR;
7893});
79948095afterEach(() => {
8196vi.useRealTimers();
8297process.env.PATH = originalPath;
8398process.env.PATHEXT = originalPathExt;
99+restoreEnvValue("SystemRoot", originalSystemRoot);
100+restoreEnvValue("WINDIR", originalWindir);
84101platformSpy?.mockReturnValue("win32");
85102spawnMock.mockReset();
103+spawnSyncMock.mockReset();
104+spawnSyncMock.mockReturnValue({ status: 0 });
86105tempDir = "";
87106});
88107@@ -163,7 +182,7 @@ describe("checkQmdBinaryAvailability", () => {
163182});
164183165184it("returns available when the qmd process spawns successfully", async () => {
166-const child = createMockChild();
185+const child = createMockChild({ pid: 12344 });
167186spawnMock.mockImplementationOnce(() => {
168187queueMicrotask(() => child.emit("spawn"));
169188return child;
@@ -172,8 +191,35 @@ describe("checkQmdBinaryAvailability", () => {
172191await expect(
173192checkQmdBinaryAvailability({ command: "qmd", env: process.env, cwd: tempDir }),
174193).resolves.toEqual({ available: true });
175-expect(child.kill).toHaveBeenCalledTimes(1);
176-expect(child.kill).toHaveBeenCalledWith();
194+expect(spawnSyncMock).toHaveBeenCalledWith(taskkillPath, ["/PID", String(child.pid), "/T"], {
195+stdio: "ignore",
196+windowsHide: true,
197+});
198+expect(child.kill).not.toHaveBeenCalled();
199+});
200+201+it("force-kills Windows availability probes when graceful taskkill fails", async () => {
202+const child = createMockChild({ pid: 12345 });
203+spawnMock.mockImplementationOnce(() => {
204+queueMicrotask(() => child.emit("spawn"));
205+return child;
206+});
207+spawnSyncMock.mockReset();
208+spawnSyncMock.mockReturnValueOnce({ status: 1 }).mockReturnValueOnce({ status: 0 });
209+210+await expect(
211+checkQmdBinaryAvailability({ command: "qmd", env: process.env, cwd: tempDir }),
212+).resolves.toEqual({ available: true });
213+214+expect(spawnSyncMock).toHaveBeenNthCalledWith(1, taskkillPath, ["/PID", "12345", "/T"], {
215+stdio: "ignore",
216+windowsHide: true,
217+});
218+expect(spawnSyncMock).toHaveBeenNthCalledWith(2, taskkillPath, ["/PID", "12345", "/T", "/F"], {
219+stdio: "ignore",
220+windowsHide: true,
221+});
222+expect(child.kill).not.toHaveBeenCalled();
177223});
178224179225it("returns unavailable when the qmd process cannot be spawned", async () => {
@@ -411,4 +457,26 @@ describe("runCliCommand", () => {
411457killProcess.mockRestore();
412458}
413459});
460+461+it("force-kills timed-out Windows cli commands with taskkill", async () => {
462+const child = createMockChild({ pid: 12346 });
463+spawnMock.mockReturnValueOnce(child);
464+465+const pending = runCliCommand({
466+commandSummary: "qmd query test",
467+spawnInvocation: { command: "qmd", argv: ["query", "test", "--json"] },
468+env: process.env,
469+cwd: tempDir,
470+maxOutputChars: 10_000,
471+timeoutMs: 1,
472+});
473+474+await expect(pending).rejects.toThrow("qmd query test timed out after 1ms");
475+476+expect(spawnSyncMock).toHaveBeenCalledWith(taskkillPath, ["/PID", "12346", "/T", "/F"], {
477+stdio: "ignore",
478+windowsHide: true,
479+});
480+expect(child.kill).not.toHaveBeenCalledWith("SIGKILL");
481+});
414482});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。