test(row 333): hold the port a refusal test uses instead of probing and closing it (Vikunja #782)
control-board serve.test: the non-loopback refusal test took a port from a probe-and-close helper and then called startServer on it. It now binds its own listener, keeps it for the whole test, and closes it in finally, so the port cannot be taken by another process in between. stack-release.test T-I4: the free port goes only to webui-service.sh install rows that refuse before any listen, so it is now a closed lease (leaseClosedPort), which refuses connects and blocks other binds. The 22 other freePort callers hand the number to `stack-release.sh rehearse`, which probes it and starts a Console later. That needs a script change and is left as a design, with a comment above freePort naming the window.
This commit is contained in:
1 parent
0e7e203db8
commit
5fbf3d2b51
2 files changed
+19
-17
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { leasePort } from '../../../scripts/testing/lane-fixtures.mjs';
|
||||
import { leaseClosedPort, leasePort } from '../../../scripts/testing/lane-fixtures.mjs';
|
||||
import { after as laneAfter } from 'node:test';
|
||||
// scripts/stack-release.sh (D1, #1659). Design, the T table and the mutants:
|
||||
// agents/darkwing/work/queue-162/BUILD.md. Every run uses a temp data root, temp units and stand-in
|
||||
@@ -2051,6 +2051,10 @@ for(const [id,v,froms] of [[B,'3g',['3f']],[C,'3h',['3f','3g']],[A,'3g',['3f']],
|
||||
}
|
||||
|
||||
const TESTER='7499';
|
||||
// Vikunja #782: this leases a port, closes it and hands the number to `stack-release.sh rehearse`, which
|
||||
// probes it and starts a Console on it later. Another process can take the port in between. The script
|
||||
// takes no port 0 and learns no bound port, so that window stays here; a rehearsal that listens on its
|
||||
// own is the only form that closes it. A caller that never listens takes leaseClosedPort(t) instead.
|
||||
const freePort=async()=>String(await leasePort({after:laneAfter}));
|
||||
const rh=(e,...p)=>join(e.env.HOME,...p);
|
||||
const rfault=(e,name)=>{mkdirSync(rh(e,'faults'),{recursive:true});writeFileSync(rh(e,'faults',name),'')};
|
||||
@@ -2455,7 +2459,7 @@ test('T56 row 190: named instances side by side on one unit directory, $BIN and
|
||||
|
||||
await t.test('T-I4 a Console port another installed Console takes refuses port-claimed, and a named instance keeps off the default ports',async(t)=>{
|
||||
const e=await mk(t),a=await named(e,'alpha'),b=await named(e,'beta');await seed(a);
|
||||
const held=`port ${a.port} is taken by ${join(e.units,a.con)}`,free=await freePort();
|
||||
const held=`port ${a.port} is taken by ${join(e.units,a.con)}`,free=String(await leaseClosedPort(t));
|
||||
const install=(x,...flags)=>shOut(x.env,'webui-service.sh','install',...x.flags,'--copy',B,'--copies',COPIES,...flags,'--business','acme');
|
||||
const unitsBefore=stamp(e.units),binBefore=stamp(e.bin);
|
||||
for(const [label,x,flags,code,msg] of [
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { leasePort } from '../../../scripts/testing/lane-fixtures.mjs';
|
||||
import { after as laneAfter } from 'node:test';
|
||||
import { test, after } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
@@ -73,10 +71,6 @@ function closeServer(server) {
|
||||
return new Promise((resolvePromise) => server.close(resolvePromise));
|
||||
}
|
||||
|
||||
// Grab an ephemeral free port, then hand it back immediately so a caller can
|
||||
// try to bind it themselves (used to prove startServer never opened a socket).
|
||||
function getFreePort() { return leasePort({after:laneAfter}); }
|
||||
|
||||
function runCli(args) {
|
||||
return spawnSync(process.execPath, [cli, ...args], { encoding: "utf8", timeout: 15000 });
|
||||
}
|
||||
@@ -106,7 +100,16 @@ test("isLoopbackHost: rejects non-loopback hosts", () => {
|
||||
|
||||
test("startServer: refuses a non-loopback host with ConfigError, never opens a socket", async () => {
|
||||
const root = makeRoot();
|
||||
const port = await getFreePort();
|
||||
// Vikunja #782: the test's own listener allocates the port and holds it for the whole test, so
|
||||
// no number is probed, closed and bound later. A startServer that called listen() on this port,
|
||||
// on 127.0.0.1 or on the wildcard address, would fail with EADDRINUSE instead of the ConfigError
|
||||
// asserted below, which also holds when it closes the socket again before it throws.
|
||||
const holder = createNetServer();
|
||||
await new Promise((resolvePromise, reject) => {
|
||||
holder.once("error", reject);
|
||||
holder.listen(0, "127.0.0.1", resolvePromise);
|
||||
});
|
||||
const { port } = holder.address();
|
||||
let caught = null;
|
||||
try {
|
||||
// startServer is async, so the host refusal surfaces as a rejection
|
||||
@@ -120,15 +123,10 @@ test("startServer: refuses a non-loopback host with ConfigError, never opens a s
|
||||
});
|
||||
} catch (err) {
|
||||
caught = err;
|
||||
} finally {
|
||||
await closeServer(holder);
|
||||
}
|
||||
assert.ok(caught instanceof ConfigError, "expected a ConfigError");
|
||||
|
||||
// The port must still be free: startServer must never have called listen().
|
||||
await new Promise((resolvePromise, reject) => {
|
||||
const probe = createNetServer();
|
||||
probe.once("error", reject);
|
||||
probe.listen(port, "127.0.0.1", () => probe.close(resolvePromise));
|
||||
});
|
||||
assert.ok(caught instanceof ConfigError, `expected a ConfigError, got ${caught}`);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in new issue
Block a user