mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-07 10:34:44 +00:00
fix(test): kill openclaw test process groups
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
// OpenClaw test instance tests cover spawned test instance lifecycle.
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { createOpenClawTestInstance, testing } from "./openclaw-test-instance.js";
|
||||
|
||||
async function expectPathMissing(targetPath: string): Promise<void> {
|
||||
@@ -44,6 +44,24 @@ describe("openclaw test instance", () => {
|
||||
).rejects.toThrow("gateway exited before listening");
|
||||
});
|
||||
|
||||
it("signals test instance process groups on POSIX", () => {
|
||||
const child = {
|
||||
pid: 1234,
|
||||
kill: vi.fn(() => true),
|
||||
};
|
||||
const killProcess = vi.fn(() => true);
|
||||
|
||||
testing.signalOpenClawTestProcess(child, "SIGKILL", killProcess);
|
||||
|
||||
if (process.platform === "win32") {
|
||||
expect(killProcess).not.toHaveBeenCalled();
|
||||
expect(child.kill).toHaveBeenCalledWith("SIGKILL");
|
||||
} else {
|
||||
expect(killProcess).toHaveBeenCalledWith(-1234, "SIGKILL");
|
||||
expect(child.kill).not.toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
|
||||
it("creates isolated config and spawn env without mutating process env", async () => {
|
||||
const previousHome = process.env.HOME;
|
||||
const inst = await createOpenClawTestInstance({
|
||||
|
||||
@@ -73,6 +73,8 @@ type BoundedStringLog = string[] & {
|
||||
truncated?: boolean;
|
||||
};
|
||||
|
||||
type OpenClawTestChildProcess = Pick<ChildProcessWithoutNullStreams, "kill" | "pid">;
|
||||
|
||||
function createBoundedStringLog(): string[] {
|
||||
const log = [] as BoundedStringLog;
|
||||
log.byteLength = 0;
|
||||
@@ -152,6 +154,7 @@ async function prepareGatewayEntrypoint(cwd: string): Promise<string[]> {
|
||||
cwd,
|
||||
env: { ...process.env, VITEST: "1" },
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
detached: shouldUseOpenClawTestProcessGroup(),
|
||||
});
|
||||
child.stdout?.setEncoding("utf8");
|
||||
child.stderr?.setEncoding("utf8");
|
||||
@@ -167,7 +170,7 @@ async function prepareGatewayEntrypoint(cwd: string): Promise<string[]> {
|
||||
]);
|
||||
|
||||
if (completed === null) {
|
||||
child.kill("SIGKILL");
|
||||
signalOpenClawTestProcess(child, "SIGKILL");
|
||||
throw new Error(`timeout preparing gateway entrypoint\n${formatLogs(stdout, stderr)}`);
|
||||
}
|
||||
if (completed.code !== 0) {
|
||||
@@ -401,6 +404,7 @@ export async function createOpenClawTestInstance(
|
||||
cwd,
|
||||
env,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
detached: shouldUseOpenClawTestProcessGroup(),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -428,7 +432,7 @@ export async function createOpenClawTestInstance(
|
||||
}
|
||||
if (!hasChildExited(child) && !child.killed) {
|
||||
try {
|
||||
child.kill("SIGTERM");
|
||||
signalOpenClawTestProcess(child, "SIGTERM");
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
@@ -439,7 +443,7 @@ export async function createOpenClawTestInstance(
|
||||
);
|
||||
if (!exited && !hasChildExited(child) && !child.killed) {
|
||||
try {
|
||||
child.kill("SIGKILL");
|
||||
signalOpenClawTestProcess(child, "SIGKILL");
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
@@ -479,6 +483,7 @@ async function runCommand(params: {
|
||||
cwd: params.cwd,
|
||||
env: params.env,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
detached: shouldUseOpenClawTestProcessGroup(),
|
||||
});
|
||||
child.stdout?.setEncoding("utf8");
|
||||
child.stderr?.setEncoding("utf8");
|
||||
@@ -493,7 +498,7 @@ async function runCommand(params: {
|
||||
sleep(params.timeoutMs).then(() => null),
|
||||
]);
|
||||
if (completed === null) {
|
||||
child.kill("SIGKILL");
|
||||
signalOpenClawTestProcess(child, "SIGKILL");
|
||||
await waitForGatewayExit(child, GATEWAY_STOP_TIMEOUT_MS);
|
||||
throw new Error(
|
||||
`command timed out after ${params.timeoutMs}ms: ${params.args.join(" ")}\n${formatLogs(stdout, stderr)}`,
|
||||
@@ -506,10 +511,31 @@ async function runCommand(params: {
|
||||
};
|
||||
}
|
||||
|
||||
function shouldUseOpenClawTestProcessGroup(): boolean {
|
||||
return process.platform !== "win32";
|
||||
}
|
||||
|
||||
function signalOpenClawTestProcess(
|
||||
child: OpenClawTestChildProcess,
|
||||
signal: NodeJS.Signals,
|
||||
killProcess: (pid: number, signal: NodeJS.Signals) => boolean = process.kill,
|
||||
): void {
|
||||
if (shouldUseOpenClawTestProcessGroup() && typeof child.pid === "number") {
|
||||
try {
|
||||
killProcess(-child.pid, signal);
|
||||
return;
|
||||
} catch {
|
||||
// Fall back to the direct child if the process group already exited.
|
||||
}
|
||||
}
|
||||
child.kill(signal);
|
||||
}
|
||||
|
||||
export const testing = {
|
||||
appendLogChunk,
|
||||
createBoundedStringLog,
|
||||
formatLogs,
|
||||
hasChildExited,
|
||||
signalOpenClawTestProcess,
|
||||
waitForPortOpen,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user