mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-13 18:32:12 +03:00
* test(infra): retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) Two shards on release/v3.8.51 went red in one day with the same signature — "ENOTEMPTY, Directory not empty: /tmp/omniroute-<test>-XXXXXX" — from combo-same-provider-cascade (Unit Tests fast-path 4/4, on a PR that touches only .github/) and auth-policy-embeddings-webfetch-7785 (the 20k-test TIA step). Both pass alone and on re-run: the cleanup races something still writing into the directory (SQLite WAL/-shm checkpoint, a worker, the backup) and under a loaded hosted runner the window opens. 1154 test files do their own cleanup with fs.rmSync(dir, { recursive: true, force: true }); 57 already asked for retries. One-shot codemod (scripts/ad-hoc/codemod-rm-maxretries.mjs, kept for the record): every rm / rmSync / rmdirSync option object with `recursive: true` and no `maxRetries` gains `maxRetries: 5, retryDelay: 100` — Node itself then retries ENOTEMPTY/EBUSY/EPERM for up to ~0.5 s before giving up. 2243 call sites in 1292 files under tests/, the shared tests/_setup/isolateDataDir.ts exit hook included. Only the option object changes: no call site, assertion or import is touched. Validation: prettier and ESLint (with the frozen suppressions) clean on all 1292 files; a random 20-file sample runs green (quota-redis-store hangs identically on the untouched tree — it needs a Redis on localhost, an environment matter). The four unit shards on this PR are the full run. * fix(quality): let check-forgotten-sibling-tests read a 1,000-file diff The gate shells out to `git diff` through execFileSync with Node's default 1 MB maxBuffer; the 1,292-file codemod in this PR is the first diff large enough to overflow it, and the gate died with `spawnSync git ENOBUFS` before comparing anything. 64 MB is far above any real PR and costs nothing when unused.
172 lines
6.8 KiB
TypeScript
172 lines
6.8 KiB
TypeScript
/**
|
|
* `resolvePortPid` output parsing and probe fallback (#10431).
|
|
*
|
|
* The regression these guard is that `resolvePortPid` used to shell out to
|
|
* `lsof` alone. On a host without it, `spawn` raises ENOENT, the handler turned
|
|
* that into `null`, and an adopted service silently kept `pid: null` forever.
|
|
*/
|
|
|
|
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { createServer } from "node:net";
|
|
import { execFileSync } from "node:child_process";
|
|
import { mkdtempSync, rmSync, symlinkSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import path from "node:path";
|
|
import {
|
|
parseLsofPid,
|
|
parseNetstatPid,
|
|
parseSsPid,
|
|
parseWindowsNetstatPid,
|
|
resolvePortPid,
|
|
} from "@/lib/services/portProbe";
|
|
|
|
/** Absolute path of `command`, or null when it is not on PATH. */
|
|
function which(command: string): string | null {
|
|
try {
|
|
return execFileSync("/bin/sh", ["-c", `command -v ${command}`], { encoding: "utf8" }).trim();
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
test("parseLsofPid reads the first pid line", () => {
|
|
assert.equal(parseLsofPid("596922\n"), 596922);
|
|
assert.equal(parseLsofPid("\n 596922 \n123\n"), 596922);
|
|
});
|
|
|
|
test("parseLsofPid returns null for empty or non-numeric output", () => {
|
|
assert.equal(parseLsofPid(""), null);
|
|
assert.equal(parseLsofPid("\n \n"), null);
|
|
assert.equal(parseLsofPid("lsof: command not found\n"), null);
|
|
});
|
|
|
|
test("parseSsPid reads the pid out of the users:(...) column", () => {
|
|
const line =
|
|
'LISTEN 0 511 127.0.0.1:20128 0.0.0.0:* users:(("node",pid=596922,fd=18))\n';
|
|
assert.equal(parseSsPid(line), 596922);
|
|
});
|
|
|
|
test("parseSsPid returns null when ss reports no process column", () => {
|
|
// Without ownership of the socket (or CAP_NET_ADMIN) ss prints the row but
|
|
// no users:(...) column, which must not be read as a match.
|
|
assert.equal(parseSsPid("LISTEN 0 511 127.0.0.1:20128 0.0.0.0:*\n"), null);
|
|
assert.equal(parseSsPid(""), null);
|
|
});
|
|
|
|
test("parseNetstatPid matches on the local address, not the foreign one", () => {
|
|
const stdout = [
|
|
"Active Internet connections (only servers)",
|
|
"Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name",
|
|
"tcp 0 0 127.0.0.1:9999 0.0.0.0:20128 LISTEN 111/other",
|
|
"tcp 0 0 127.0.0.1:20128 0.0.0.0:* LISTEN 596922/node",
|
|
"",
|
|
].join("\n");
|
|
assert.equal(parseNetstatPid(stdout, 20128), 596922);
|
|
});
|
|
|
|
test("parseNetstatPid reads macOS process:pid output", () => {
|
|
const stdout = "tcp4 0 0 127.0.0.1.20128 *.* LISTEN 0 0 131072 131072 node:596922 00100\n";
|
|
assert.equal(parseNetstatPid(stdout, 20128), 596922);
|
|
});
|
|
|
|
test("parseNetstatPid ignores non-listening rows and unknown ports", () => {
|
|
const stdout =
|
|
"tcp 0 0 127.0.0.1:20128 1.2.3.4:5555 ESTABLISHED 596922/node\n";
|
|
assert.equal(parseNetstatPid(stdout, 20128), null);
|
|
assert.equal(parseNetstatPid("", 20128), null);
|
|
});
|
|
|
|
/**
|
|
* Realistic `netstat -ano` sample from Windows 11 (#11236 bug 6): the pid is
|
|
* the last whitespace-separated column and only exists on rows whose state is
|
|
* LISTENING. This is the only pid probe available on a stock Windows host —
|
|
* neither lsof nor ss nor net-tools `netstat -tlnp` exist there, so a Windows
|
|
* service adopted by the supervisor reported `pid: null` while healthy.
|
|
*/
|
|
const WINDOWS_NETSTAT_ANO = [
|
|
"Active Connections",
|
|
"",
|
|
" Proto Local Address Foreign Address State PID",
|
|
" TCP 0.0.0.0:135 0.0.0.0:0 LISTENING 1244",
|
|
" TCP 0.0.0.0:20128 0.0.0.0:0 LISTENING 12345",
|
|
" TCP 127.0.0.1:8317 0.0.0.0:0 LISTENING 5678",
|
|
" TCP 192.168.1.10:52413 140.82.121.4:443 ESTABLISHED 9012",
|
|
" TCP [::]:20128 [::]:0 LISTENING 12345",
|
|
" UDP 0.0.0.0:5353 *:* 3460",
|
|
"",
|
|
].join("\r\n");
|
|
|
|
test("parseWindowsNetstatPid reads the pid from a LISTENING row (#11236)", () => {
|
|
assert.equal(parseWindowsNetstatPid(WINDOWS_NETSTAT_ANO, 20128), 12345);
|
|
assert.equal(parseWindowsNetstatPid(WINDOWS_NETSTAT_ANO, 8317), 5678);
|
|
});
|
|
|
|
test("parseWindowsNetstatPid matches the local address, not the foreign one", () => {
|
|
// 443 appears only as a foreign address on an ESTABLISHED row.
|
|
assert.equal(parseWindowsNetstatPid(WINDOWS_NETSTAT_ANO, 443), null);
|
|
// 5353 appears only on a UDP row, which has no LISTENING state.
|
|
assert.equal(parseWindowsNetstatPid(WINDOWS_NETSTAT_ANO, 5353), null);
|
|
// A port that shares a suffix with a listening one must not match: 0128 vs
|
|
// 20128 — the `:` anchor on the local address prevents the partial hit.
|
|
assert.equal(parseWindowsNetstatPid(WINDOWS_NETSTAT_ANO, 128), null);
|
|
assert.equal(parseWindowsNetstatPid("", 20128), null);
|
|
});
|
|
|
|
test("resolvePortPid finds the pid holding a port", async () => {
|
|
const server = createServer();
|
|
await new Promise<void>((resolve) => server.listen(29994, "127.0.0.1", resolve));
|
|
try {
|
|
assert.equal(await resolvePortPid(29994), process.pid);
|
|
} finally {
|
|
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
}
|
|
});
|
|
|
|
test("resolvePortPid returns null for a port nobody holds", async () => {
|
|
assert.equal(await resolvePortPid(29993), null);
|
|
});
|
|
|
|
test("resolvePortPid still resolves a pid on a host without lsof", async (t) => {
|
|
// The reported environment: ss and/or netstat present, lsof absent. Emulated
|
|
// by pointing PATH at a directory holding only the fallbacks, so `spawn`
|
|
// raises the same ENOENT for lsof that a slim image would.
|
|
const fallbacks = ["ss", "netstat"]
|
|
.map((command) => ({ command, real: which(command) }))
|
|
.filter((entry): entry is { command: string; real: string } => entry.real !== null);
|
|
|
|
if (fallbacks.length === 0) {
|
|
t.skip("neither ss nor netstat is installed");
|
|
return;
|
|
}
|
|
|
|
const shim = mkdtempSync(path.join(tmpdir(), "portprobe-"));
|
|
const originalPath = process.env.PATH;
|
|
const server = createServer();
|
|
|
|
try {
|
|
for (const { command, real } of fallbacks) {
|
|
symlinkSync(real, path.join(shim, command));
|
|
}
|
|
|
|
// Guard the guard: if lsof were still reachable the assertion below would
|
|
// pass for the wrong reason.
|
|
assert.throws(
|
|
() =>
|
|
execFileSync("/bin/sh", ["-c", "command -v lsof"], {
|
|
stdio: "ignore",
|
|
env: { PATH: shim },
|
|
}),
|
|
"lsof must not resolve on the shimmed PATH"
|
|
);
|
|
|
|
process.env.PATH = shim;
|
|
await new Promise<void>((resolve) => server.listen(29992, "127.0.0.1", resolve));
|
|
assert.equal(await resolvePortPid(29992), process.pid);
|
|
} finally {
|
|
process.env.PATH = originalPath;
|
|
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
rmSync(shim, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
}
|
|
});
|