forked from openclaw/openclaw
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathprogram.force.test.ts
More file actions
148 lines (125 loc) · 4.13 KB
/
program.force.test.ts
File metadata and controls
148 lines (125 loc) · 4.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("node:child_process", async () => {
const actual = await vi.importActual<typeof import("node:child_process")>("node:child_process");
return {
...actual,
execFileSync: vi.fn(),
};
});
import { execFileSync } from "node:child_process";
import {
forceFreePort,
forceFreePortAndWait,
listPortListeners,
type PortProcess,
parseLsofOutput,
} from "./ports.js";
describe("gateway --force helpers", () => {
let originalKill: typeof process.kill;
beforeEach(() => {
vi.clearAllMocks();
originalKill = process.kill.bind(process);
});
afterEach(() => {
process.kill = originalKill;
});
it("parses lsof output into pid/command pairs", () => {
const sample = ["p123", "cnode", "p456", "cpython", ""].join("\n");
const parsed = parseLsofOutput(sample);
expect(parsed).toEqual<PortProcess[]>([
{ pid: 123, command: "node" },
{ pid: 456, command: "python" },
]);
});
it("returns empty list when lsof finds nothing", () => {
(execFileSync as unknown as vi.Mock).mockImplementation(() => {
const err = new Error("no matches");
// @ts-expect-error partial
err.status = 1; // lsof uses exit 1 for no matches
throw err;
});
expect(listPortListeners(18789)).toEqual([]);
});
it("throws when lsof missing", () => {
(execFileSync as unknown as vi.Mock).mockImplementation(() => {
const err = new Error("not found");
// @ts-expect-error partial
err.code = "ENOENT";
throw err;
});
expect(() => listPortListeners(18789)).toThrow(/lsof not found/);
});
it("kills each listener and returns metadata", () => {
(execFileSync as unknown as vi.Mock).mockReturnValue(
["p42", "cnode", "p99", "cssh", ""].join("\n"),
);
const killMock = vi.fn();
// @ts-expect-error override for test
process.kill = killMock;
const killed = forceFreePort(18789);
expect(execFileSync).toHaveBeenCalled();
expect(killMock).toHaveBeenCalledTimes(2);
expect(killMock).toHaveBeenCalledWith(42, "SIGTERM");
expect(killMock).toHaveBeenCalledWith(99, "SIGTERM");
expect(killed).toEqual<PortProcess[]>([
{ pid: 42, command: "node" },
{ pid: 99, command: "ssh" },
]);
});
it("retries until the port is free", async () => {
vi.useFakeTimers();
let call = 0;
(execFileSync as unknown as vi.Mock).mockImplementation(() => {
call += 1;
// 1st call: initial listeners to kill; 2nd call: still listed; 3rd call: gone.
if (call === 1) {
return ["p42", "cnode", ""].join("\n");
}
if (call === 2) {
return ["p42", "cnode", ""].join("\n");
}
return "";
});
const killMock = vi.fn();
// @ts-expect-error override for test
process.kill = killMock;
const promise = forceFreePortAndWait(18789, {
timeoutMs: 500,
intervalMs: 100,
sigtermTimeoutMs: 400,
});
await vi.runAllTimersAsync();
const res = await promise;
expect(killMock).toHaveBeenCalledWith(42, "SIGTERM");
expect(res.killed).toEqual<PortProcess[]>([{ pid: 42, command: "node" }]);
expect(res.escalatedToSigkill).toBe(false);
expect(res.waitedMs).toBeGreaterThan(0);
vi.useRealTimers();
});
it("escalates to SIGKILL if SIGTERM doesn't free the port", async () => {
vi.useFakeTimers();
let call = 0;
(execFileSync as unknown as vi.Mock).mockImplementation(() => {
call += 1;
// 1st call: initial kill list; then keep showing until after SIGKILL.
if (call <= 6) {
return ["p42", "cnode", ""].join("\n");
}
return "";
});
const killMock = vi.fn();
// @ts-expect-error override for test
process.kill = killMock;
const promise = forceFreePortAndWait(18789, {
timeoutMs: 800,
intervalMs: 100,
sigtermTimeoutMs: 300,
});
await vi.runAllTimersAsync();
const res = await promise;
expect(killMock).toHaveBeenCalledWith(42, "SIGTERM");
expect(killMock).toHaveBeenCalledWith(42, "SIGKILL");
expect(res.escalatedToSigkill).toBe(true);
vi.useRealTimers();
});
});