



























@@ -161,6 +161,35 @@ function installInitialBusyPoll(
161161return () => call;
162162}
163163164+function mockCall(mock: ReturnType<typeof vi.fn>, callIndex = 0): unknown[] {
165+const call = mock.mock.calls[callIndex] as unknown[] | undefined;
166+if (!call) {
167+throw new Error(`expected mock call ${callIndex}`);
168+}
169+return call;
170+}
171+172+function mockCallRecordArg(
173+mock: ReturnType<typeof vi.fn>,
174+callIndex: number,
175+argIndex: number,
176+label: string,
177+): Record<string, unknown> {
178+const value = mockCall(mock, callIndex)[argIndex];
179+if (!value || typeof value !== "object") {
180+throw new Error(`expected ${label} to be an object`);
181+}
182+return value as Record<string, unknown>;
183+}
184+185+function expectWarningContaining(text: string): void {
186+expect(
187+mockRestartWarn.mock.calls.some((call) =>
188+typeof call[0] === "string" ? call[0].includes(text) : false,
189+),
190+).toBe(true);
191+}
192+164193describe.skipIf(isWindows)("restart-stale-pids", () => {
165194beforeAll(async () => {
166195({ __testing, cleanStaleGatewayProcessesSync, findGatewayPidsOnPortSync } =
@@ -222,9 +251,7 @@ describe.skipIf(isWindows)("restart-stale-pids", () => {
222251it("logs warning when initial lsof scan exits with status > 1", () => {
223252mockSpawnSync.mockReturnValue({ error: null, status: 2, stdout: "", stderr: "lsof error" });
224253expect(findGatewayPidsOnPortSync(18789)).toStrictEqual([]);
225-expect(mockRestartWarn).toHaveBeenCalledWith(
226-expect.stringContaining("lsof exited with status 2"),
227-);
254+expectWarningContaining("lsof exited with status 2");
228255});
229256230257it("returns [] when lsof returns an error object (e.g. ENOENT)", () => {
@@ -235,9 +262,7 @@ describe.skipIf(isWindows)("restart-stale-pids", () => {
235262stderr: "",
236263});
237264expect(findGatewayPidsOnPortSync(18789)).toStrictEqual([]);
238-expect(mockRestartWarn).toHaveBeenCalledWith(
239-expect.stringContaining("lsof failed during initial stale-pid scan"),
240-);
265+expectWarningContaining("lsof failed during initial stale-pid scan");
241266});
242267243268it("parses openclaw-gateway pids and excludes the current process", () => {
@@ -276,11 +301,9 @@ describe.skipIf(isWindows)("restart-stale-pids", () => {
276301});
277302278303expect(findGatewayPidsOnPortSync(18789)).toEqual([stalePid]);
279-expect(mockSpawnSync).toHaveBeenCalledWith(
280-"ps",
281-["-ww", "-p", String(stalePid), "-o", "command="],
282-expect.objectContaining({ timeout: 2000 }),
283-);
304+const psCall = mockSpawnSync.mock.calls.find((call) => call[0] === "ps");
305+expect(psCall?.[1]).toEqual(["-ww", "-p", String(stalePid), "-o", "command="]);
306+expect(psCall?.[2]).toEqual({ timeout: 2000, encoding: "utf8" });
284307});
285308286309it("excludes ancestor pids so a sidecar cannot kill its parent gateway — regression for #68451", () => {
@@ -433,11 +456,10 @@ describe.skipIf(isWindows)("restart-stale-pids", () => {
433456it("forwards the spawnTimeoutMs argument to spawnSync", () => {
434457mockSpawnSync.mockReturnValue({ error: null, status: 0, stdout: "", stderr: "" });
435458findGatewayPidsOnPortSync(18789, 400);
436-expect(mockSpawnSync).toHaveBeenCalledWith(
437-"lsof",
438-expect.any(Array),
439-expect.objectContaining({ timeout: 400 }),
440-);
459+const lsofCall = mockCall(mockSpawnSync);
460+expect(lsofCall[0]).toBe("lsof");
461+expect(Array.isArray(lsofCall[1])).toBe(true);
462+expect(mockCallRecordArg(mockSpawnSync, 0, 2, "lsof options").timeout).toBe(400);
441463});
442464443465it("deduplicates pids from dual-stack listeners (IPv4+IPv6 emit same pid twice)", () => {
@@ -944,9 +966,7 @@ describe.skipIf(isWindows)("restart-stale-pids", () => {
944966945967expect(cleanStaleGatewayProcessesSync()).toEqual([stalePid]);
946968expect(mockReadWindowsListeningPidsResult).toHaveBeenCalledWith(18789, 400);
947-expect(mockRestartWarn).toHaveBeenCalledWith(
948-expect.stringContaining("port 18789 still in use after 2000ms"),
949-);
969+expectWarningContaining("port 18789 still in use after 2000ms");
950970expect(killSpy).toHaveBeenCalledWith(stalePid, 0);
951971} finally {
952972__testing.setDateNowOverride(null);
@@ -972,9 +992,7 @@ describe.skipIf(isWindows)("restart-stale-pids", () => {
972992973993expect(cleanStaleGatewayProcessesSync()).toStrictEqual([]);
974994expect(mockReadWindowsListeningPidsResult).toHaveBeenCalledWith(18789, 400);
975-expect(mockRestartWarn).toHaveBeenCalledWith(
976-expect.stringContaining("port 18789 still in use after 2000ms"),
977-);
995+expectWarningContaining("port 18789 still in use after 2000ms");
978996expect(killSpy).not.toHaveBeenCalled();
979997} finally {
980998__testing.setDateNowOverride(null);
@@ -1002,9 +1020,7 @@ describe.skipIf(isWindows)("restart-stale-pids", () => {
1002102010031021expect(cleanStaleGatewayProcessesSync()).toStrictEqual([]);
10041022expect(mockReadWindowsProcessArgsResult).toHaveBeenCalledWith(stalePid, undefined);
1005-expect(mockRestartWarn).toHaveBeenCalledWith(
1006-expect.stringContaining("port 18789 still in use after 2000ms"),
1007-);
1023+expectWarningContaining("port 18789 still in use after 2000ms");
10081024expect(killSpy).not.toHaveBeenCalled();
10091025} finally {
10101026__testing.setDateNowOverride(null);
@@ -1049,11 +1065,11 @@ describe.skipIf(isWindows)("restart-stale-pids", () => {
10491065});
1050106610511067expect(cleanStaleGatewayProcessesSync()).toStrictEqual([]);
1052-expect(mockSpawnSync).toHaveBeenCalledWith(
1053-"C:\\Windows\\System32\\taskkill.exe",
1054-["/T", "/PID", String(stalePid)],
1055-expect.objectContaining({ timeout: 5000 }),
1068+const taskkillCall = mockSpawnSync.mock.calls.find(
1069+(call) => call[0] === "C:\\Windows\\System32\\taskkill.exe",
10561070);
1071+expect(taskkillCall?.[1]).toEqual(["/T", "/PID", String(stalePid)]);
1072+expect((taskkillCall?.[2] as { timeout?: number } | undefined)?.timeout).toBe(5000);
10571073} finally {
10581074__testing.setDateNowOverride(null);
10591075if (originalSystemRoot === undefined) {
@@ -1104,17 +1120,13 @@ describe.skipIf(isWindows)("restart-stale-pids", () => {
11041120});
1105112111061122expect(cleanStaleGatewayProcessesSync()).toStrictEqual([]);
1107-expect(mockSpawnSync).toHaveBeenNthCalledWith(
1108-1,
1109-"C:\\Windows\\System32\\taskkill.exe",
1110-["/T", "/PID", String(stalePid)],
1111-expect.objectContaining({ timeout: 5000 }),
1112-);
1113-expect(mockSpawnSync).toHaveBeenNthCalledWith(
1114-2,
1115-"C:\\Windows\\System32\\taskkill.exe",
1116-["/F", "/T", "/PID", String(stalePid)],
1117-expect.objectContaining({ timeout: 5000 }),
1123+expect(mockCall(mockSpawnSync, 0)[0]).toBe("C:\\Windows\\System32\\taskkill.exe");
1124+expect(mockCall(mockSpawnSync, 0)[1]).toEqual(["/T", "/PID", String(stalePid)]);
1125+expect(mockCallRecordArg(mockSpawnSync, 0, 2, "taskkill options").timeout).toBe(5000);
1126+expect(mockCall(mockSpawnSync, 1)[0]).toBe("C:\\Windows\\System32\\taskkill.exe");
1127+expect(mockCall(mockSpawnSync, 1)[1]).toEqual(["/F", "/T", "/PID", String(stalePid)]);
1128+expect(mockCallRecordArg(mockSpawnSync, 1, 2, "forced taskkill options").timeout).toBe(
1129+5000,
11181130);
11191131} finally {
11201132__testing.setSleepSyncOverride(null);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。