conductor

CI task system
git clone git://git.finwo.net/app/conductor
Log | Files | Refs | README | LICENSE

commit 1672c2ededd2f28938f40d3decac3481375e24c8
parent 048d4fed616c6848bfbf090593a22196d27b14bc
Author: finwo <finwo@pm.me>
Date:   Sat, 19 Sep 2026 17:11:52 +0200

Wait for test containers on a deadline, not a count of attempts

Diffstat:
Mtest/helpers/minio.js | 24+++++++++++++++++++++---
Mtest/helpers/oidc.js | 16+++++++++++++---
2 files changed, 34 insertions(+), 6 deletions(-)

diff --git a/test/helpers/minio.js b/test/helpers/minio.js @@ -19,6 +19,10 @@ const execFileAsync = promisify(execFile); // Pinned, so a MinIO release cannot change what the suite is testing // against without the change being visible here. export const IMAGE = 'quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z'; +// How long to wait for the container to answer. Generous, because it +// competes with image builds for the machine. +const READY_TIMEOUT = 120 * 1000; + const NAME_PREFIX = 'conductor-minio-test-'; const ACCESS_KEY = 'conductortest'; @@ -129,8 +133,14 @@ export async function startMinio() { IMAGE, 'server', '/data', ], { timeout: 180000 }); + // Deadline rather than a count of attempts. A refused connection comes + // back instantly, so sixty attempts spaced by half a second gave up + // after thirty seconds regardless of the interval, and a machine busy + // pulling or building images needs longer than that. Failing there + // looked like a broken test rather than a slow start. let ready = false; - for (let i = 0; i < 60; i += 1) { + const deadline = Date.now() + READY_TIMEOUT; + while (Date.now() < deadline) { try { const res = await fetch(`${endpoint}/minio/health/live`, { signal: AbortSignal.timeout(2000) }); if (res.ok) { @@ -140,12 +150,20 @@ export async function startMinio() { } catch { // Still starting. } - await new Promise((r) => { setTimeout(r, 500).unref?.(); }); + await new Promise((r) => { setTimeout(r, 500); }); } if (!ready) { + // Whatever the container said on its way down, so a failure here is + // diagnosable from the test output rather than needing a rerun that + // may well not reproduce it. + const diagnosis = await execFileAsync('docker', ['logs', '--tail', '20', name], { timeout: 10000 }) + .then(({ stdout, stderr }) => `${stdout}${stderr}`.trim()) + .catch((e) => `could not read container logs: ${e.message}`); await execFileAsync('docker', ['rm', '-f', name]).catch(() => {}); - throw new Error(`MinIO did not become ready at ${endpoint}`); + throw new Error( + `MinIO did not become ready at ${endpoint} within ${READY_TIMEOUT / 1000}s\n${diagnosis}`, + ); } return { diff --git a/test/helpers/oidc.js b/test/helpers/oidc.js @@ -99,6 +99,10 @@ async function freePort() { }); } +// How long to wait for the container to answer. Generous, because it +// competes with image builds for the machine. +const READY_TIMEOUT = 120 * 1000; + const NAME_PREFIX = 'conductor-oidc-test-'; // A test run that crashes hard, or is killed, never reaches its cleanup @@ -145,8 +149,14 @@ export async function startOidc() { ], { timeout: 120000 }); const discovery = `${issuer}/.well-known/openid-configuration`; + // Deadline rather than a count of attempts. A refused connection comes + // back instantly, so sixty attempts spaced by half a second gave up + // after thirty seconds regardless of the interval, and a machine busy + // pulling or building images needs longer than that. Failing there + // looked like a broken test rather than a slow start. let ready = false; - for (let i = 0; i < 60; i += 1) { + const deadline = Date.now() + READY_TIMEOUT; + while (Date.now() < deadline) { try { const res = await fetch(discovery, { signal: AbortSignal.timeout(2000) }); if (res.ok) { @@ -156,12 +166,12 @@ export async function startOidc() { } catch { // Still starting. } - await new Promise((r) => { setTimeout(r, 500).unref?.(); }); + await new Promise((r) => { setTimeout(r, 500); }); } if (!ready) { await execFileAsync('docker', ['rm', '-f', name]).catch(() => {}); - throw new Error(`the OIDC provider did not become ready at ${discovery}`); + throw new Error(`the OIDC provider did not become ready at ${discovery} within ${READY_TIMEOUT / 1000}s`); } return {