[pending-judge] fix(r4/polish1): server-assigned port — no freePort release-rebind race
The e2e child now calls app.listen(0) and reports the OS-assigned port on stdout (PORT:<n>); the parent reads it. The probe-then-release helper is deleted, so no other process can take the port between probe and bind.
This commit is contained in:
1 file changed
+11
-20
@@ -19,7 +19,6 @@
|
||||
* assertion compares against that live 404 baseline on the same instance.
|
||||
*/
|
||||
const { spawn } = require('child_process');
|
||||
const net = require('net');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const fs = require('fs');
|
||||
@@ -30,21 +29,9 @@ let child;
|
||||
let PORT;
|
||||
let JWT;
|
||||
|
||||
// OS-assigned ephemeral port (judge polish: not a random 30000-49999 guess).
|
||||
function freePort() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const s = net.createServer();
|
||||
s.listen(0, '127.0.0.1', () => {
|
||||
const p = s.address().port;
|
||||
s.close(() => resolve(p));
|
||||
});
|
||||
s.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
// Boot the REAL app via its REAL entry contract (createApp + listen). The
|
||||
// child mints a JWT through the same authManager singleton the app wires.
|
||||
function boot(port) {
|
||||
function boot() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(process.execPath, ['-e', `
|
||||
process.env.DATA_DIR = ${JSON.stringify(DATA_DIR)};
|
||||
@@ -60,8 +47,9 @@ function boot(port) {
|
||||
} catch (e) {
|
||||
jwt = '';
|
||||
}
|
||||
const server = app.listen(${port}, '127.0.0.1', () => {
|
||||
const server = app.listen(0, '127.0.0.1', () => {
|
||||
console.log('JWT:' + jwt);
|
||||
console.log('PORT:' + server.address().port);
|
||||
console.log('READY');
|
||||
});
|
||||
process.on('SIGTERM', () => server.close(() => process.exit(0)));
|
||||
@@ -80,11 +68,13 @@ function boot(port) {
|
||||
}, 30000);
|
||||
child.stdout.on('data', (d) => {
|
||||
buf += String(d);
|
||||
const m = buf.match(/JWT:(\S*)/);
|
||||
if (!settled && m && buf.includes('READY')) {
|
||||
const mj = buf.match(/JWT:(\S*)/);
|
||||
const mp = buf.match(/PORT:(\d+)/);
|
||||
if (!settled && mj && mp && buf.includes('READY')) {
|
||||
settled = true;
|
||||
clearTimeout(to);
|
||||
child.jwt = m[1];
|
||||
child.jwt = mj[1];
|
||||
child.port = Number(mp[1]);
|
||||
resolve(child);
|
||||
}
|
||||
});
|
||||
@@ -117,9 +107,10 @@ async function expectMounted(p, allowed) {
|
||||
|
||||
describe('composition-root e2e (real createApp on ephemeral port)', () => {
|
||||
beforeAll(async () => {
|
||||
PORT = await freePort();
|
||||
child = await boot(PORT);
|
||||
child = await boot(); // child calls listen(0); no probe/rebind race
|
||||
PORT = child.port;
|
||||
JWT = child.jwt || '';
|
||||
if (!Number.isInteger(PORT) || PORT <= 0) throw new Error('child did not report a bound port');
|
||||
const u = await get('/api/v1/__definitely_not_mounted__');
|
||||
unknownStatus = u.status;
|
||||
}, 45000);
|
||||
|
||||
Reference in new issue
Block a user