mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-23 18:28:15 +00:00
128 lines
3.5 KiB
TypeScript
128 lines
3.5 KiB
TypeScript
// Watch Node tests cover watch node script behavior.
|
|
import { EventEmitter } from "node:events";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { runWatchMain } from "../../scripts/watch-node.mjs";
|
|
|
|
class FakeProcess extends EventEmitter {
|
|
execPath = process.execPath;
|
|
pid = 12345;
|
|
stdin = {
|
|
isTTY: false,
|
|
};
|
|
stderr = {
|
|
write: () => true,
|
|
};
|
|
}
|
|
|
|
class FakeChild extends EventEmitter {
|
|
signals: string[] = [];
|
|
pid?: number;
|
|
|
|
constructor(pid?: number) {
|
|
super();
|
|
this.pid = pid;
|
|
}
|
|
|
|
kill(signal: string): boolean {
|
|
this.signals.push(signal);
|
|
if (signal === "SIGKILL") {
|
|
this.emit("exit", null, "SIGKILL");
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
describe("watch-node shutdown cleanup", () => {
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
it("waits for the child and escalates when interrupted children ignore SIGTERM", async () => {
|
|
vi.useFakeTimers();
|
|
const fakeProcess = new FakeProcess();
|
|
const child = new FakeChild();
|
|
let resolvedCode: number | undefined;
|
|
|
|
const run = runWatchMain({
|
|
args: ["gateway"],
|
|
createWatcher: () => ({ close: async () => {}, on: () => {} }),
|
|
lockDisabled: true,
|
|
process: fakeProcess as unknown as NodeJS.Process,
|
|
spawn: () => child as never,
|
|
}).then((code) => {
|
|
resolvedCode = code;
|
|
return code;
|
|
});
|
|
|
|
fakeProcess.emit("SIGTERM");
|
|
await vi.advanceTimersByTimeAsync(4_999);
|
|
expect(resolvedCode).toBeUndefined();
|
|
expect(child.signals).toEqual(["SIGTERM"]);
|
|
|
|
await vi.advanceTimersByTimeAsync(1);
|
|
await expect(run).resolves.toBe(143);
|
|
expect(child.signals).toEqual(["SIGTERM", "SIGKILL"]);
|
|
});
|
|
|
|
it("force-cleans the child process group when the leader exits after shutdown", async () => {
|
|
vi.useFakeTimers();
|
|
const fakeProcess = new FakeProcess();
|
|
const child = new FakeChild(4_242);
|
|
const groupSignals: Array<[number, string | number]> = [];
|
|
|
|
const run = runWatchMain({
|
|
args: ["gateway"],
|
|
createWatcher: () => ({ close: async () => {}, on: () => {} }),
|
|
lockDisabled: true,
|
|
process: fakeProcess as unknown as NodeJS.Process,
|
|
signalProcess: (pid, signal) => {
|
|
groupSignals.push([pid, signal]);
|
|
},
|
|
spawn: () => child as never,
|
|
});
|
|
|
|
fakeProcess.emit("SIGTERM");
|
|
expect(groupSignals).toEqual([[-4_242, "SIGTERM"]]);
|
|
child.emit("exit", 0, null);
|
|
|
|
await expect(run).resolves.toBe(143);
|
|
expect(groupSignals).toEqual([
|
|
[-4_242, "SIGTERM"],
|
|
[-4_242, "SIGKILL"],
|
|
]);
|
|
});
|
|
|
|
it("waits for the auto-doctor child when interrupted during repair", async () => {
|
|
vi.useFakeTimers();
|
|
const fakeProcess = new FakeProcess();
|
|
const runner = new FakeChild();
|
|
const doctor = new FakeChild();
|
|
const children = [runner, doctor];
|
|
let resolvedCode: number | undefined;
|
|
|
|
const run = runWatchMain({
|
|
args: ["gateway"],
|
|
createWatcher: () => ({ close: async () => {}, on: () => {} }),
|
|
env: {},
|
|
lockDisabled: true,
|
|
process: fakeProcess as unknown as NodeJS.Process,
|
|
spawn: () => children.shift() as never,
|
|
}).then((code) => {
|
|
resolvedCode = code;
|
|
return code;
|
|
});
|
|
|
|
runner.emit("exit", 1, null);
|
|
expect(children).toHaveLength(0);
|
|
|
|
fakeProcess.emit("SIGTERM");
|
|
await vi.advanceTimersByTimeAsync(4_999);
|
|
expect(resolvedCode).toBeUndefined();
|
|
expect(doctor.signals).toEqual(["SIGTERM"]);
|
|
|
|
await vi.advanceTimersByTimeAsync(1);
|
|
await expect(run).resolves.toBe(143);
|
|
expect(doctor.signals).toEqual(["SIGTERM", "SIGKILL"]);
|
|
});
|
|
});
|