/** * 覆盖网络 S3 · 实例端口区间单测(不连外网、只在本机回环上 bind)。 * 运行:node --test test/instance-port.test.mjs(已含在 npm test / npm verify 中) * * ## 这个测试防的是什么 * `listen(0)` 让**每台 worker 各自**随机取端口,而跨机实例的隧道落点全挤在 Manager 的 * `127.0.0.1` 上 ⇒ 两台 worker 取到同号就撞号,`ssh -R` 失败还是**静默**的, * Manager 会按该端口拨到**别人的实例**(2026-09-16 实测)。 * * 所以这里要钉死两件事: * ① 取到的端口**必须落在配置区间内**(区间隔离的前提) * ② 起点被占 ⇒ **跳到下一个**,绝不"回落到 `listen(0)`" */ import { test } from 'node:test' import assert from 'node:assert/strict' import { createServer } from 'node:net' import { findFreePortInRange, findInstancePort } from '../lib/supervisor/spawn.js' /** 占住一个回环端口,返回 `{ port, release }`。 */ function holdPort() { return new Promise((resolve, reject) => { const server = createServer() server.once('error', reject) server.listen(0, '127.0.0.1', () => { const address = server.address() const port = typeof address === 'object' && address !== null ? address.port : 0 resolve({ port, release: () => new Promise((r) => server.close(() => r())) }) }) }) } test('端口区间:取到的端口必须落在区间内', async () => { // 用打洞拿到的空闲端口当区间起点 ⇒ 测试不依赖"20000 一定空着"这种环境假设 const { port, release } = await holdPort() try { const got = await findFreePortInRange(port, 200) assert.ok(got >= port && got < port + 200, `越界:${got} 不在 [${port}, ${port + 200})`) } finally { await release() } }) test('端口区间:起点被占用 ⇒ 跳过它,不回落到 listen(0)', async () => { const { port, release } = await holdPort() try { const got = await findFreePortInRange(port, 200) assert.notEqual(got, port, '起点被占却仍返回了它 ⇒ 会撞号') assert.ok(got >= port && got < port + 200) } finally { await release() } }) test('端口区间:区间耗尽 ⇒ 抛错(禁止静默退回 listen(0))', async () => { await assert.rejects(() => findFreePortInRange(20000, 0), /区间已耗尽/) }) test('findInstancePort:未配区间(base<=0)⇒ 退回旧的 listen(0) 行为', async () => { const got = await findInstancePort(0, 1000) assert.ok(got > 0 && got < 65536, `应拿到合法端口,实际 ${got}`) }) test('findInstancePort:配了区间 ⇒ 结果落在区间内', async () => { const { port, release } = await holdPort() try { const got = await findInstancePort(port, 128) assert.ok(got >= port && got < port + 128, `越界:${got}`) } finally { await release() } })