From 0a4f6ff9fb9d980cf6cf011bf72a88503e01b50e Mon Sep 17 00:00:00 2001 From: Hampus Date: Mon, 31 Aug 2026 15:16:53 +0200 Subject: [PATCH] fix(test): surface docker errors when a test container fails (#2242) --- .../database/PostgresKvNonSelectDrift.test.ts | 41 +++++++--------- .../PostgresKvNonSelectMatrix.test.ts | 41 +++++++--------- .../database/PostgresKvOrderingAttack.test.ts | 49 +++++++++---------- .../PostgresKvPagingAdversarial.test.ts | 45 ++++++++--------- .../PostgresKvQueryDifferential.test.ts | 49 +++++++++---------- .../database/PostgresKvStatementNames.test.ts | 41 +++++++--------- .../database/PostgresKvUpgradeSafety.test.ts | 45 ++++++++--------- .../src/api/test/DockerTestContainer.ts | 28 +++++++++++ 8 files changed, 173 insertions(+), 166 deletions(-) create mode 100644 fluxer_api/src/api/test/DockerTestContainer.ts diff --git a/fluxer_api/src/api/database/PostgresKvNonSelectDrift.test.ts b/fluxer_api/src/api/database/PostgresKvNonSelectDrift.test.ts index 404bc10f1..4bba4d25a 100644 --- a/fluxer_api/src/api/database/PostgresKvNonSelectDrift.test.ts +++ b/fluxer_api/src/api/database/PostgresKvNonSelectDrift.test.ts @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -import {execFileSync, spawnSync} from 'node:child_process'; +import {spawnSync} from 'node:child_process'; import {createServer} from 'node:net'; import { getDefaultPostgresClient, @@ -10,6 +10,7 @@ import { shutdownPostgres, } from '@pkgs/postgres/src/Client'; import {afterAll, beforeAll, describe, expect, it} from 'vitest'; +import {startDockerContainer} from '../test/DockerTestContainer'; import {LegacyPostgresKvQueryExecutor} from './__testref__/LegacyPostgresKvQueryExecutor'; import type {CassandraParams, KvQueryMeta, KvTableSpec, WhereExpr} from './CassandraTypes'; import {ensurePostgresKvSchema, PostgresKvQueryExecutor} from './PostgresKvQueryExecutor'; @@ -120,27 +121,23 @@ describe.skipIf(!dockerAvailable)('postgres kv non-select drift', () => { beforeAll(async () => { const port = await freePort(); - execFileSync( - 'docker', - [ - 'run', - '-d', - '--name', - CONTAINER, - '-e', - 'POSTGRES_USER=fluxer', - '-e', - 'POSTGRES_PASSWORD=fluxer', - '-e', - 'POSTGRES_DB=fluxer', - '-p', - `127.0.0.1:${port}:5432`, - 'postgres:16-alpine', - '-c', - 'fsync=off', - ], - {stdio: 'ignore'}, - ); + startDockerContainer([ + 'run', + '-d', + '--name', + CONTAINER, + '-e', + 'POSTGRES_USER=fluxer', + '-e', + 'POSTGRES_PASSWORD=fluxer', + '-e', + 'POSTGRES_DB=fluxer', + '-p', + `127.0.0.1:${port}:5432`, + 'postgres:16-alpine', + '-c', + 'fsync=off', + ]); let ready = false; for (let attempt = 0; attempt < 180 && !ready; attempt += 1) { await sleep(500); diff --git a/fluxer_api/src/api/database/PostgresKvNonSelectMatrix.test.ts b/fluxer_api/src/api/database/PostgresKvNonSelectMatrix.test.ts index 7f0be5b83..6f8074347 100644 --- a/fluxer_api/src/api/database/PostgresKvNonSelectMatrix.test.ts +++ b/fluxer_api/src/api/database/PostgresKvNonSelectMatrix.test.ts @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -import {execFileSync, spawnSync} from 'node:child_process'; +import {spawnSync} from 'node:child_process'; import {createServer} from 'node:net'; import { getDefaultPostgresClient, @@ -11,6 +11,7 @@ import { } from '@pkgs/postgres/src/Client'; import cassandra from 'cassandra-driver'; import {afterAll, beforeAll, describe, expect, it} from 'vitest'; +import {startDockerContainer} from '../test/DockerTestContainer'; import {LegacyPostgresKvQueryExecutor} from './__testref__/LegacyPostgresKvQueryExecutor'; import type {CassandraParams, KvQueryMeta, KvTableSpec, WhereExpr} from './CassandraTypes'; import {buildCandidatePlan, ensurePostgresKvSchema, PostgresKvQueryExecutor} from './PostgresKvQueryExecutor'; @@ -136,27 +137,23 @@ describe.skipIf(!dockerAvailable)('postgres kv non-select matrix', () => { beforeAll(async () => { const port = await freePort(); - execFileSync( - 'docker', - [ - 'run', - '-d', - '--name', - CONTAINER, - '-e', - 'POSTGRES_USER=fluxer', - '-e', - 'POSTGRES_PASSWORD=fluxer', - '-e', - 'POSTGRES_DB=fluxer', - '-p', - `127.0.0.1:${port}:5432`, - 'postgres:16-alpine', - '-c', - 'fsync=off', - ], - {stdio: 'ignore'}, - ); + startDockerContainer([ + 'run', + '-d', + '--name', + CONTAINER, + '-e', + 'POSTGRES_USER=fluxer', + '-e', + 'POSTGRES_PASSWORD=fluxer', + '-e', + 'POSTGRES_DB=fluxer', + '-p', + `127.0.0.1:${port}:5432`, + 'postgres:16-alpine', + '-c', + 'fsync=off', + ]); let ready = false; for (let attempt = 0; attempt < 180 && !ready; attempt += 1) { await sleep(500); diff --git a/fluxer_api/src/api/database/PostgresKvOrderingAttack.test.ts b/fluxer_api/src/api/database/PostgresKvOrderingAttack.test.ts index 627f12efb..201f5a454 100644 --- a/fluxer_api/src/api/database/PostgresKvOrderingAttack.test.ts +++ b/fluxer_api/src/api/database/PostgresKvOrderingAttack.test.ts @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -import {execFileSync, spawnSync} from 'node:child_process'; +import {spawnSync} from 'node:child_process'; import {createServer} from 'node:net'; import { getDefaultPostgresClient, @@ -11,6 +11,7 @@ import { } from '@pkgs/postgres/src/Client'; import cassandra from 'cassandra-driver'; import {afterAll, beforeAll, describe, expect, it} from 'vitest'; +import {startDockerContainer} from '../test/DockerTestContainer'; import {LegacyPostgresKvQueryExecutor} from './__testref__/LegacyPostgresKvQueryExecutor'; import {defineTable} from './CassandraTableDsl'; import type {CassandraParam, CassandraParams, KvQueryMeta, KvTableSpec, WhereExpr} from './CassandraTypes'; @@ -196,31 +197,27 @@ suite('PostgresKvQueryExecutor ordering and limit attack', () => { beforeAll(async () => { const port = await freePort(); - execFileSync( - 'docker', - [ - 'run', - '-d', - '--name', - CONTAINER, - '-e', - 'POSTGRES_USER=fluxer', - '-e', - 'POSTGRES_PASSWORD=fluxer', - '-e', - 'POSTGRES_DB=fluxer', - '-p', - `127.0.0.1:${port}:5432`, - POSTGRES_IMAGE, - '-c', - 'fsync=off', - '-c', - 'synchronous_commit=off', - '-c', - 'full_page_writes=off', - ], - {stdio: 'ignore'}, - ); + startDockerContainer([ + 'run', + '-d', + '--name', + CONTAINER, + '-e', + 'POSTGRES_USER=fluxer', + '-e', + 'POSTGRES_PASSWORD=fluxer', + '-e', + 'POSTGRES_DB=fluxer', + '-p', + `127.0.0.1:${port}:5432`, + POSTGRES_IMAGE, + '-c', + 'fsync=off', + '-c', + 'synchronous_commit=off', + '-c', + 'full_page_writes=off', + ]); let ready = false; for (let attempt = 0; attempt < 180 && !ready; attempt += 1) { await sleep(500); diff --git a/fluxer_api/src/api/database/PostgresKvPagingAdversarial.test.ts b/fluxer_api/src/api/database/PostgresKvPagingAdversarial.test.ts index dd7f2cbd0..26a734784 100644 --- a/fluxer_api/src/api/database/PostgresKvPagingAdversarial.test.ts +++ b/fluxer_api/src/api/database/PostgresKvPagingAdversarial.test.ts @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -import {execFileSync, spawnSync} from 'node:child_process'; +import {spawnSync} from 'node:child_process'; import {createServer} from 'node:net'; import { getDefaultPostgresClient, @@ -10,6 +10,7 @@ import { shutdownPostgres, } from '@pkgs/postgres/src/Client'; import {afterAll, beforeAll, describe, expect, it} from 'vitest'; +import {startDockerContainer} from '../test/DockerTestContainer'; import {LegacyPostgresKvQueryExecutor} from './__testref__/LegacyPostgresKvQueryExecutor'; import type {CassandraParams, KvQueryMeta, KvTableSpec, WhereExpr} from './CassandraTypes'; import {ensurePostgresKvSchema, PostgresKvQueryExecutor} from './PostgresKvQueryExecutor'; @@ -141,29 +142,25 @@ describe.skipIf(!dockerAvailable)('postgres kv paging adversarial', () => { beforeAll(async () => { const port = await freePort(); - execFileSync( - 'docker', - [ - 'run', - '-d', - '--name', - CONTAINER, - '-e', - 'POSTGRES_USER=fluxer', - '-e', - 'POSTGRES_PASSWORD=fluxer', - '-e', - 'POSTGRES_DB=fluxer', - '-p', - `127.0.0.1:${port}:5432`, - POSTGRES_IMAGE, - '-c', - 'fsync=off', - '-c', - 'synchronous_commit=off', - ], - {stdio: 'ignore'}, - ); + startDockerContainer([ + 'run', + '-d', + '--name', + CONTAINER, + '-e', + 'POSTGRES_USER=fluxer', + '-e', + 'POSTGRES_PASSWORD=fluxer', + '-e', + 'POSTGRES_DB=fluxer', + '-p', + `127.0.0.1:${port}:5432`, + POSTGRES_IMAGE, + '-c', + 'fsync=off', + '-c', + 'synchronous_commit=off', + ]); let ready = false; for (let attempt = 0; attempt < 180 && !ready; attempt += 1) { await sleep(500); diff --git a/fluxer_api/src/api/database/PostgresKvQueryDifferential.test.ts b/fluxer_api/src/api/database/PostgresKvQueryDifferential.test.ts index e27bafdcd..60dbed0c9 100644 --- a/fluxer_api/src/api/database/PostgresKvQueryDifferential.test.ts +++ b/fluxer_api/src/api/database/PostgresKvQueryDifferential.test.ts @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -import {execFileSync, spawnSync} from 'node:child_process'; +import {spawnSync} from 'node:child_process'; import {createServer} from 'node:net'; import { getDefaultPostgresClient, @@ -12,6 +12,7 @@ import { import cassandra from 'cassandra-driver'; import {afterAll, beforeAll, describe, expect, it} from 'vitest'; import * as Tables from '../Tables'; +import {startDockerContainer} from '../test/DockerTestContainer'; import {LegacyPostgresKvQueryExecutor} from './__testref__/LegacyPostgresKvQueryExecutor'; import {defineTable} from './CassandraTableDsl'; import type {CassandraParam, CassandraParams, KvQueryMeta, KvTableSpec, WhereExpr} from './CassandraTypes'; @@ -1001,31 +1002,27 @@ suite('PostgresKvQueryExecutor differential', () => { beforeAll(async () => { const port = await freePort(); - execFileSync( - 'docker', - [ - 'run', - '-d', - '--name', - CONTAINER, - '-e', - 'POSTGRES_USER=fluxer', - '-e', - 'POSTGRES_PASSWORD=fluxer', - '-e', - 'POSTGRES_DB=fluxer', - '-p', - `127.0.0.1:${port}:5432`, - POSTGRES_IMAGE, - '-c', - 'fsync=off', - '-c', - 'synchronous_commit=off', - '-c', - 'full_page_writes=off', - ], - {stdio: 'ignore'}, - ); + startDockerContainer([ + 'run', + '-d', + '--name', + CONTAINER, + '-e', + 'POSTGRES_USER=fluxer', + '-e', + 'POSTGRES_PASSWORD=fluxer', + '-e', + 'POSTGRES_DB=fluxer', + '-p', + `127.0.0.1:${port}:5432`, + POSTGRES_IMAGE, + '-c', + 'fsync=off', + '-c', + 'synchronous_commit=off', + '-c', + 'full_page_writes=off', + ]); let ready = false; for (let attempt = 0; attempt < 180 && !ready; attempt += 1) { await sleep(500); diff --git a/fluxer_api/src/api/database/PostgresKvStatementNames.test.ts b/fluxer_api/src/api/database/PostgresKvStatementNames.test.ts index 1f59f85e4..76f8527ff 100644 --- a/fluxer_api/src/api/database/PostgresKvStatementNames.test.ts +++ b/fluxer_api/src/api/database/PostgresKvStatementNames.test.ts @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -import {execFileSync, spawnSync} from 'node:child_process'; +import {spawnSync} from 'node:child_process'; import {createServer} from 'node:net'; import { getDefaultPostgresClient, @@ -9,6 +9,7 @@ import { shutdownPostgres, } from '@pkgs/postgres/src/Client'; import {afterAll, beforeAll, describe, expect, it} from 'vitest'; +import {startDockerContainer} from '../test/DockerTestContainer'; import type {CassandraParams, KvQueryMeta, KvTableSpec, WhereExpr} from './CassandraTypes'; import {ensurePostgresKvSchema, PostgresKvQueryExecutor} from './PostgresKvQueryExecutor'; @@ -176,27 +177,23 @@ describe.skipIf(!dockerAvailable)('PostgresKvQueryExecutor statement names again beforeAll(async () => { const port = await freePort(); - execFileSync( - 'docker', - [ - 'run', - '-d', - '--name', - CONTAINER, - '-e', - 'POSTGRES_USER=fluxer', - '-e', - 'POSTGRES_PASSWORD=fluxer', - '-e', - 'POSTGRES_DB=fluxer', - '-p', - `127.0.0.1:${port}:5432`, - 'postgres:16-alpine', - '-c', - 'fsync=off', - ], - {stdio: 'ignore'}, - ); + startDockerContainer([ + 'run', + '-d', + '--name', + CONTAINER, + '-e', + 'POSTGRES_USER=fluxer', + '-e', + 'POSTGRES_PASSWORD=fluxer', + '-e', + 'POSTGRES_DB=fluxer', + '-p', + `127.0.0.1:${port}:5432`, + 'postgres:16-alpine', + '-c', + 'fsync=off', + ]); let ready = false; for (let attempt = 0; attempt < 180 && !ready; attempt += 1) { await sleep(500); diff --git a/fluxer_api/src/api/database/PostgresKvUpgradeSafety.test.ts b/fluxer_api/src/api/database/PostgresKvUpgradeSafety.test.ts index 6e9a8e452..dcec88fd9 100644 --- a/fluxer_api/src/api/database/PostgresKvUpgradeSafety.test.ts +++ b/fluxer_api/src/api/database/PostgresKvUpgradeSafety.test.ts @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -import {execFileSync, spawnSync} from 'node:child_process'; +import {spawnSync} from 'node:child_process'; import {createServer} from 'node:net'; import { getDefaultPostgresClient, @@ -11,6 +11,7 @@ import { } from '@pkgs/postgres/src/Client'; import {afterAll, beforeAll, describe, expect, it} from 'vitest'; import {GuildMembers, ReadStates, Users} from '../Tables'; +import {startDockerContainer} from '../test/DockerTestContainer'; import {LegacyPostgresKvQueryExecutor, legacyEnsurePostgresKvSchema} from './__testref__/LegacyPostgresKvQueryExecutor'; import {ensurePostgresKvSchema, POSTGRES_KV_MIGRATION_TABLE, PostgresKvQueryExecutor} from './PostgresKvQueryExecutor'; @@ -46,29 +47,25 @@ async function freePort(): Promise { if (dockerUp) { beforeAll(async () => { PORT = await freePort(); - execFileSync( - 'docker', - [ - 'run', - '-d', - '--name', - CONTAINER, - '-e', - 'POSTGRES_USER=fluxer', - '-e', - 'POSTGRES_PASSWORD=fluxer', - '-e', - 'POSTGRES_DB=fluxer', - '-p', - `127.0.0.1:${PORT}:5432`, - POSTGRES_IMAGE, - '-c', - 'fsync=off', - '-c', - 'synchronous_commit=off', - ], - {stdio: 'ignore'}, - ); + startDockerContainer([ + 'run', + '-d', + '--name', + CONTAINER, + '-e', + 'POSTGRES_USER=fluxer', + '-e', + 'POSTGRES_PASSWORD=fluxer', + '-e', + 'POSTGRES_DB=fluxer', + '-p', + `127.0.0.1:${PORT}:5432`, + POSTGRES_IMAGE, + '-c', + 'fsync=off', + '-c', + 'synchronous_commit=off', + ]); for (let attempt = 0; attempt < 180; attempt += 1) { await sleep(500); const probe = spawnSync('docker', ['exec', CONTAINER, 'pg_isready', '-U', 'fluxer', '-d', 'fluxer'], { diff --git a/fluxer_api/src/api/test/DockerTestContainer.ts b/fluxer_api/src/api/test/DockerTestContainer.ts new file mode 100644 index 000000000..c398e2bc9 --- /dev/null +++ b/fluxer_api/src/api/test/DockerTestContainer.ts @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import {spawnSync} from 'node:child_process'; + +function tail(value: string, lines: number): string { + return value + .split('\n') + .map((line) => line.trimEnd()) + .filter((line) => line.length > 0) + .slice(-lines) + .join('\n'); +} + +export function startDockerContainer(args: ReadonlyArray): void { + const result = spawnSync('docker', args, {encoding: 'utf8'}); + if (result.error) { + throw new Error(`failed to run docker: ${result.error.message}\n docker ${args.join(' ')}`); + } + if (result.status === 0) { + return; + } + const stderr = tail(result.stderr ?? '', 5); + const stdout = tail(result.stdout ?? '', 3); + const detail = stderr || stdout || 'no output'; + throw new Error( + `docker exited ${result.status} while starting a test container.\n docker ${args.join(' ')}\n ${detail}`, + ); +}