mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-07 02:22:46 +00:00
fix(qa): taskkill lifecycle probe trees on windows
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// Plugin Lifecycle Probe tests cover QA Lab plugin lifecycle evidence.
|
||||
import { spawn } from "node:child_process";
|
||||
import { spawn, spawnSync } from "node:child_process";
|
||||
import { randomBytes } from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
@@ -17,6 +17,7 @@ interface CommandOptions {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
outputFile?: string;
|
||||
spawnImpl?: typeof spawn;
|
||||
taskkillImpl?: typeof spawnSync;
|
||||
timeoutKillGraceMs?: number;
|
||||
timeoutMs?: number;
|
||||
}
|
||||
@@ -266,6 +267,26 @@ async function runCommand(command: string, args: readonly string[], options: Com
|
||||
// The process group may already be gone; fall back to the direct child.
|
||||
}
|
||||
}
|
||||
if (!useProcessGroup && child.pid) {
|
||||
const runTaskkill = options.taskkillImpl ?? spawnSync;
|
||||
const args = ["/PID", String(child.pid), "/T"];
|
||||
if (signal === "SIGKILL") {
|
||||
args.push("/F");
|
||||
}
|
||||
const result = runTaskkill("taskkill", args, { stdio: "ignore", windowsHide: true });
|
||||
if (!result.error && result.status === 0) {
|
||||
return;
|
||||
}
|
||||
if (signal !== "SIGKILL") {
|
||||
const forceResult = runTaskkill("taskkill", [...args, "/F"], {
|
||||
stdio: "ignore",
|
||||
windowsHide: true,
|
||||
});
|
||||
if (!forceResult.error && forceResult.status === 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
child.kill(signal);
|
||||
};
|
||||
const isProcessGroupRunning = () => {
|
||||
|
||||
@@ -136,6 +136,50 @@ describe("plugin lifecycle matrix probe", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("force-kills timed Windows commands with taskkill when graceful taskkill fails", async () => {
|
||||
vi.useFakeTimers();
|
||||
const platformDescriptor = Object.getOwnPropertyDescriptor(process, "platform");
|
||||
Object.defineProperty(process, "platform", { value: "win32", configurable: true });
|
||||
try {
|
||||
const child = Object.assign(new FakeCommandChild(), { pid: 12345 });
|
||||
const taskkillImpl = vi
|
||||
.fn()
|
||||
.mockReturnValueOnce({ status: 1 })
|
||||
.mockImplementationOnce(() => {
|
||||
queueMicrotask(() => child.emit("exit", null, "SIGTERM"));
|
||||
return { status: 0 };
|
||||
});
|
||||
const runPromise = probeTesting.runCommand("fake-command", ["install"], {
|
||||
spawnImpl: (() => child) as unknown as typeof import("node:child_process").spawn,
|
||||
taskkillImpl,
|
||||
timeoutKillGraceMs: 100,
|
||||
timeoutMs: 10,
|
||||
});
|
||||
const runError = runPromise.catch((error: unknown) => error);
|
||||
|
||||
await vi.advanceTimersByTimeAsync(10);
|
||||
|
||||
expect(taskkillImpl).toHaveBeenNthCalledWith(1, "taskkill", ["/PID", "12345", "/T"], {
|
||||
stdio: "ignore",
|
||||
windowsHide: true,
|
||||
});
|
||||
expect(taskkillImpl).toHaveBeenNthCalledWith(2, "taskkill", ["/PID", "12345", "/T", "/F"], {
|
||||
stdio: "ignore",
|
||||
windowsHide: true,
|
||||
});
|
||||
expect(child.signals).toEqual([]);
|
||||
|
||||
const error = await runError;
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect((error as Error).message).toBe("fake-command install timed out after 10ms");
|
||||
} finally {
|
||||
if (platformDescriptor) {
|
||||
Object.defineProperty(process, "platform", platformDescriptor);
|
||||
}
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps fallback SIGKILL armed for ignored-stdio descendants", async () => {
|
||||
if (process.platform === "win32") {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user