[object Object]

← back to Photo Cleanup

test: use ephemeral port + verify own server so stale processes never collide

a8c7c07ff9365c0d3c3cdca6a09d306712c8c335 · 2026-05-18 20:23:43 -0700 · SteveStudio2

Files touched

Diff

commit a8c7c07ff9365c0d3c3cdca6a09d306712c8c335
Author: SteveStudio2 <steve@designerwallcoverings.com>
Date:   Mon May 18 20:23:43 2026 -0700

    test: use ephemeral port + verify own server so stale processes never collide
---
 test.js | 39 +++++++++++++++++++++++++++++----------
 1 file changed, 29 insertions(+), 10 deletions(-)

diff --git a/test.js b/test.js
index eb448be..a6dcee5 100644
--- a/test.js
+++ b/test.js
@@ -11,7 +11,19 @@ const os = require('os');
 const path = require('path');
 const cp = require('child_process');
 
-const PORT = 9778;
+// Grab a free ephemeral port so the test never collides with a stale server
+// from a prior run (a hardcoded port silently routes POSTs to a stranger).
+function freePort() {
+  const srv = http.createServer();
+  return new Promise((resolve, reject) => {
+    srv.once('error', reject);
+    srv.listen(0, '127.0.0.1', () => {
+      const p = srv.address().port;
+      srv.close(() => resolve(p));
+    });
+  });
+}
+
 const TMP = fs.mkdtempSync(path.join(os.tmpdir(), 'photo-cleanup-test-'));
 const SANDBOX = fs.mkdtempSync(path.join(os.homedir(), '.photo-cleanup-sandbox-'));
 const CAND_FILE = path.join(TMP, 'candidates.txt');
@@ -29,13 +41,9 @@ const fakeC = path.join(SANDBOX, 'steve-test-c.png');
 [fakeA, fakeB, fakeC].forEach(makeFakeImage);
 fs.writeFileSync(CAND_FILE, [fakeA, fakeB, fakeC].join('\n') + '\n');
 
-const child = cp.spawn(process.execPath, [path.join(__dirname, 'server.js')], {
-  env: { ...process.env, PORT: String(PORT), CANDIDATES: CAND_FILE },
-  stdio: ['ignore', 'pipe', 'pipe'],
-});
+let PORT;        // resolved to a free ephemeral port before the server spawns
+let child;       // server child process
 let serverOut = '';
-child.stdout.on('data', (c) => { serverOut += c.toString(); });
-child.stderr.on('data', (c) => { serverOut += c.toString(); });
 
 function get(p) {
   return new Promise((resolve, reject) => {
@@ -61,10 +69,14 @@ function post(p, payload) {
 
 async function waitReady(retries = 20) {
   for (let i = 0; i < retries; i++) {
-    try { const r = await get('/api/list'); if (r.status === 200) return; } catch (_) {}
+    try {
+      const r = await get('/api/list');
+      // Confirm it is OUR server: it must serve the 3-file sandbox, not a stranger.
+      if (r.status === 200 && JSON.parse(r.body).rows.length === 3) return;
+    } catch (_) {}
     await new Promise((r) => setTimeout(r, 150));
   }
-  throw new Error('server never came up\n' + serverOut);
+  throw new Error('server never came up (or wrong server on port)\n' + serverOut);
 }
 
 const tests = [];
@@ -167,6 +179,13 @@ t('Name sort produces deterministic ordering for the sandbox', async () => {
 
 (async () => {
   try {
+    PORT = await freePort();
+    child = cp.spawn(process.execPath, [path.join(__dirname, 'server.js')], {
+      env: { ...process.env, PORT: String(PORT), CANDIDATES: CAND_FILE },
+      stdio: ['ignore', 'pipe', 'pipe'],
+    });
+    child.stdout.on('data', (c) => { serverOut += c.toString(); });
+    child.stderr.on('data', (c) => { serverOut += c.toString(); });
     await waitReady();
     let pass = 0, fail = 0;
     for (const test of tests) {
@@ -176,7 +195,7 @@ t('Name sort produces deterministic ordering for the sandbox', async () => {
     console.log(`\n${pass}/${tests.length} photo-cleanup tests passed`);
     process.exitCode = fail ? 1 : 0;
   } finally {
-    child.kill();
+    if (child) child.kill();
     try { fs.rmSync(SANDBOX, { recursive: true, force: true }); } catch (_) {}
     try { fs.rmSync(TMP, { recursive: true, force: true }); } catch (_) {}
   }

← 04d578c photo-cleanup: web reviewer for 42-file Steve/Abrams candida  ·  back to Photo Cleanup  ·  server: add timeout to sips dimension probe + always clean u f1d9287 →