From 9b4566712bbb90fb74455395e552b51efbf5529a Mon Sep 17 00:00:00 2001 From: Vivek Date: Thu, 3 Sep 2026 01:12:00 +0530 Subject: [PATCH] fix(test): base the dev-server test ports past the fetch bad-ports list test/bun/dev-public-before-warm.mjs failed CI on #1460 with a bare [TypeError: fetch failed] whose cause was "bad port", naming nothing in the code under test. The port is derived as base + (process.pid % n), and the range was 10000-10255. 10080 is the last entry on the WHATWG Fetch bad-ports list, so fetch() rejects it before opening a socket and no server is involved in the failure at all. A run whose pid was 80 mod 256 therefore failed every request in the file. That is about one run in 256, and it is deterministic given the pid rather than timing-dependent, which is exactly what makes it read as flake and survive a re-run. dev-morph-verdict.mjs had the same exposure on 9990-10229 and had simply not been unlucky yet. Both are now based past 10080: 10100-10355 and 10400-10639. Scanning 9400-10700 confirms 10080 is the only blocked port in the neighbourhood, so any base above it is permanently safe. The ranges stay disjoint from each other and remain entirely above the 9989 ceiling that the existing comment in dev-public-before-warm reasons about, so its collision argument is preserved. That comment was not wrong, it just predated the blocklist constraint, and it now records both. --- test/bun/dev-morph-verdict.mjs | 8 +++++++- test/bun/dev-public-before-warm.mjs | 19 ++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/test/bun/dev-morph-verdict.mjs b/test/bun/dev-morph-verdict.mjs index e7a469d02..feeedfc7a 100644 --- a/test/bun/dev-morph-verdict.mjs +++ b/test/bun/dev-morph-verdict.mjs @@ -38,7 +38,13 @@ const __dirname = dirname(fileURLToPath(import.meta.url)); const ROOT = resolve(__dirname, '../..'); const CLI = join(ROOT, 'packages/cli/bin/webjs.js'); const runtime = process.versions.bun ? `bun ${process.versions.bun}` : `node ${process.versions.node}`; -const PORT = 9990 + (process.pid % 240); +// Based past 10080, the last entry on the WHATWG Fetch bad-ports list, which +// `fetch()` rejects with "bad port" before opening a socket. The range was +// 9990-10229, which contains it, so a run whose pid landed there failed for a +// reason unrelated to anything this file tests. See the longer account in +// dev-public-before-warm.mjs, where it actually fired. Nothing above 10080 is +// blocked, and 10400-10639 also stays clear of that file's 10100-10355. +const PORT = 10400 + (process.pid % 240); const BASE = `http://localhost:${PORT}`; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); diff --git a/test/bun/dev-public-before-warm.mjs b/test/bun/dev-public-before-warm.mjs index f6aadf8cc..d6825b891 100644 --- a/test/bun/dev-public-before-warm.mjs +++ b/test/bun/dev-public-before-warm.mjs @@ -40,14 +40,27 @@ const runtime = process.versions.bun ? `bun ${process.versions.bun}` : `node ${p // 9700-9949, dev-extra-watch 9750-9989 and dev-overlay-scope 9800-9979, which // overlap each other freely. That is pre-existing and not this file's to fix; // what this file can do is sit entirely ABOVE all of them. 9989 is the highest -// port any of them reaches, so 10000-10255 cannot collide with any of the four -// for any pair of pids. The per-pid offset is NOT doing that work and should +// port any of them reaches, so 10100-10355 cannot collide with any of the four +// for any pair of pids. +// +// The base ALSO has to clear 10080, and that is the constraint this file +// learned the hard way. 10080 is the last entry on the WHATWG Fetch bad-ports +// list, so `fetch()` rejects it with "bad port" before it opens a socket, and +// no server is involved in the failure at all. The range used to be +// 10000-10255, which contains it, so a run whose pid happened to be 80 mod 256 +// failed every request in this file with a TypeError that named nothing in the +// code under test. It is about one run in 256 and deterministic given the pid, +// which is exactly what makes it read as flake. Nothing above 10080 is blocked +// (verified by scanning 9400-10700: 10080 is the only one), so any base past it +// is permanently safe. +// +// The per-pid offset is NOT doing that work and should // not be credited with it: the node and bun runs are sequential steps and each // runner runs a given file once, so nothing here races for a port. It is only // defensive against a leftover socket from a prior run lingering in TIME_WAIT, // which is the same account `dev-hot-reload.mjs` gives of the identical // `base + pid % n` construct. -const PORT = 10000 + (process.pid % 256); +const PORT = 10100 + (process.pid % 256); const BASE = `http://localhost:${PORT}`; const sleep = (ms) => new Promise((r) => setTimeout(r, ms));