-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest.js
More file actions
183 lines (157 loc) · 8.43 KB
/
Copy pathtest.js
File metadata and controls
183 lines (157 loc) · 8.43 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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
const assert = require("node:assert/strict");
const http = require("node:http");
const net = require("node:net");
const os = require("node:os");
const { makeMatcher, parseLsof, parseCwd, appLabel, listListeningPorts, describeProcess } = require("./src/ports");
const { probeHttp, probeAll, pruneProbeCache, overallState, SERVING, WAKING, MAX_ATTEMPTS } = require("./src/probe");
const { portSummary, shorten, health, killPrompt } = require("./src/format");
let checks = 0;
const ok = (label) => { checks++; console.log(` ok ${label}`); };
// --- makeMatcher ---
const m = makeMatcher(["3000-3999", "5173", "9-5"]);
assert.equal(m(3000), true);
assert.equal(m(3999), true);
assert.equal(m(2999), false);
assert.equal(m(4000), false);
assert.equal(m(5173), true);
assert.equal(m(7), true);
ok("ranges are inclusive and reversed ranges normalise");
assert.equal(makeMatcher([])(3000), false);
assert.equal(makeMatcher(["junk", null, undefined, "-"])(3000), false);
assert.equal(makeMatcher(["0"])(0), false);
assert.equal(makeMatcher(["70000"])(70000), false);
ok("garbage and out-of-range specs are dropped, not thrown");
// --- parseLsof ---
const rows = parseLsof([
"p1234", "cnode", "n*:3000", "n127.0.0.1:3000",
"p5678", "cnext-server", "n[::1]:3002",
"p91", "cControlCe", "n*:5000",
"cdangling", "n*:9999",
].join("\n"));
assert.deepEqual(rows, [
{ port: 3000, pid: 1234, cmd: "node" },
{ port: 3002, pid: 5678, cmd: "next-server" },
{ port: 5000, pid: 91, cmd: "ControlCe" },
]);
ok("dedups pid+port, reads IPv6, sorts by port");
// !critical: a `c` with no preceding `p` must not inherit the previous pid —
// that would aim the kill at a process the user never selected.
assert.equal(rows.some((r) => r.port === 9999), false);
ok("truncated lsof output never yields a borrowed pid");
assert.deepEqual(parseLsof(""), []);
assert.deepEqual(parseLsof("garbage\nmore garbage"), []);
ok("empty and malformed stdout return no rows");
// --- parseCwd ---
assert.equal(parseCwd("p123\nn/Users/me/repo\n"), "/Users/me/repo");
assert.equal(parseCwd("p123\n"), "");
ok("cwd is read from the lsof name field");
// --- appLabel ---
assert.equal(appLabel({ pkg: "@fintoc/web", cwd: "/r/web", command: "next dev", cmd: "node" }), "@fintoc/web");
assert.equal(appLabel({ pkg: "", cwd: "/srv/api", command: "python3 -u serve.py", cmd: "Python" }), "serve.py");
assert.equal(appLabel({ pkg: "", cwd: "/r/api", command: "./bin/server", cmd: "server" }), "api");
ok("project name wins, then the script, then the directory");
// lsof truncates COMMAND to 9 chars, so "node" identifies nothing on its own;
// but an empty label is worse than a truncated one - these must never be blank.
assert.equal(appLabel({ pkg: "", cwd: "/", command: "/System/x/ControlCenter", cmd: "ControlCe" }), "ControlCenter");
assert.equal(appLabel({ pkg: "", cwd: os.homedir(), command: "/A/x.app/MacOS/stable", cmd: "stable" }), "stable");
assert.equal(appLabel({ pkg: "", cwd: "", command: "", cmd: "ControlCe" }), "ControlCe");
assert.equal(appLabel({ pkg: "", cwd: "", command: "", cmd: "" }), "unknown");
ok("root and home never become the label, and the label is never empty");
// --- portSummary ---
assert.equal(portSummary([]), "All inactive");
assert.equal(portSummary([3000]), "3000");
assert.equal(portSummary([3000, 3002, 4790]), "3000, 3002, 4790");
assert.equal(portSummary([3000, 3002, 4790, 4791]), "4 active");
ok("ports are listed up to three, then collapse to a count");
// --- health ---
assert.equal(health({ state: SERVING, status: 200 }), "HTTP 200");
assert.equal(health({ state: WAKING, status: null }), "not answering");
// health checks can be switched off; that must not read as a failed probe.
assert.equal(health({ state: SERVING, status: null }), "listening");
ok("a probe that never ran is not reported as a failure");
// --- killPrompt ---
const mixed = [
{ port: 4300, app: "queue-worker", pid: 3, state: WAKING, status: null },
{ port: 3100, app: "storefront", pid: 1, state: SERVING, status: 200 },
{ port: 3200, app: "admin-panel", pid: 2, state: SERVING, status: 404 },
];
const p = killPrompt(mixed);
assert.equal(p.message, "Kill 3 processes?");
assert.equal(p.confirmLabel, "Kill 3", "the button carries the count so a skimmed click is unambiguous");
assert.match(p.detail, /queue-worker never answered HTTP/);
// The one that might not be a dev server has to be visible, not buried mid-list.
assert.ok(p.detail.indexOf(":3100") < p.detail.indexOf(":4300"), "answering ports come first");
assert.match(p.detail, /SIGTERM/);
ok("bulk kill names the port that may not be a dev server");
const solo = killPrompt([{ port: 3100, app: "storefront", pid: 1, state: SERVING, status: 200 }]);
assert.equal(solo.message, "Kill storefront on :3100?");
assert.equal(solo.confirmLabel, "Kill");
assert.doesNotMatch(solo.detail, /never answered/);
ok("a single healthy target gets a named prompt and no false warning");
const many = killPrompt([
{ port: 4300, app: "a", pid: 1, state: WAKING, status: null },
{ port: 4301, app: "b", pid: 2, state: WAKING, status: null },
]);
assert.match(many.detail, /2 of these never answered HTTP/);
ok("the warning pluralises instead of naming every silent port");
// --- shorten ---
assert.equal(shorten(`${os.homedir()}/r/web`), "~/r/web");
assert.equal(shorten(os.homedir()), "~");
assert.equal(shorten("/srv/app"), "/srv/app");
assert.equal(shorten(`${os.homedir()}-other/x`), `${os.homedir()}-other/x`);
ok("home collapses to ~ without eating a sibling directory");
// --- overallState ---
assert.equal(overallState([]), "idle");
assert.equal(overallState([{ state: SERVING }, { state: SERVING }]), SERVING);
assert.equal(overallState([{ state: SERVING }, { state: WAKING }]), WAKING);
ok("one silent port downgrades the whole dot to starting");
// --- live: probe + scan against real sockets ---
const server = http.createServer((_, res) => { res.writeHead(204); res.end(); }).listen(3971);
const mute = net.createServer(() => {}).listen(3972);
setTimeout(async () => {
const serving = await probeHttp(3971);
const waking = await probeHttp(3972, 300);
assert.equal(serving.state, SERVING);
assert.equal(serving.status, 204);
assert.equal(waking.state, WAKING);
ok("http reply is serving, bound-but-silent socket is starting");
const live = await listListeningPorts(makeMatcher(["3971-3972"]));
assert.deepEqual(live.map((p) => p.port), [3971, 3972]);
assert.ok(live.every((p) => p.pid === process.pid), "both sockets trace back to this process");
ok("live lsof scan finds exactly the sockets we opened");
// The whole point of caching probes: a dev server must not get a request every
// poll forever, because every one of them lands in the user's terminal.
// Deliberately outside every default range: a DevPorts instance running in the
// editor watches 3000-3999 and would probe these too, inflating the count.
const COUNTED = 19811;
const SILENT = 19812;
let hits = 0;
const counted = http.createServer((_, res) => { hits++; res.writeHead(200); res.end(); }).listen(COUNTED);
const silent = net.createServer(() => {}).listen(SILENT);
await new Promise((r) => setTimeout(r, 150));
const cache = new Map();
const both = [{ pid: process.pid, port: COUNTED }, { pid: process.pid, port: SILENT }];
for (let cycle = 0; cycle < 10; cycle++) await probeAll(both, 150, cache);
assert.equal(hits, 1, `an answering server is probed once, not once per poll (got ${hits})`);
assert.equal(cache.get(`${process.pid}:${COUNTED}`).state, SERVING);
assert.equal(cache.get(`${process.pid}:${SILENT}`).attempts, MAX_ATTEMPTS,
"a port that never answers stops being retried");
ok(`10 poll cycles cost ${hits} request to the live server, capped at ${MAX_ATTEMPTS} for the silent one`);
pruneProbeCache(cache, [{ pid: process.pid, port: COUNTED }]);
assert.equal(cache.has(`${process.pid}:${SILENT}`), false, "gone processes drop out of the cache");
assert.equal(cache.has(`${process.pid}:${COUNTED}`), true);
ok("pruning forgets dead pids so a recycled pid is never trusted");
counted.close();
silent.close();
const t0 = Date.now();
await describeProcess(process.pid, "node");
const cold = Date.now() - t0;
const t1 = Date.now();
for (let i = 0; i < 50; i++) await describeProcess(process.pid, "node");
const warm = Date.now() - t1;
assert.ok(warm < cold, `50 cached lookups (${warm}ms) must beat one cold one (${cold}ms)`);
ok(`describeProcess memoises: cold ${cold}ms, 50 warm ${warm}ms`);
server.close();
mute.close();
console.log(`\n${checks} checks passed`);
}, 300);