#!/usr/bin/env bun /** * Run the full test suite against a throwaway MariaDB Docker container. * * bun run test # start container, run all tests, remove container * bun run test tests/db # extra args are passed to `bun test` * * Env: * BUN_MARIADB_TEST_IMAGE Docker image (default: mariadb:11.8, needs 11.7+ for VECTOR) * BUN_MARIADB_TEST_DB If already set, skip Docker and use the configured * BUN_MARIADB_SERVER_* connection instead */ import { SQL } from "bun"; const IMAGE = process.env.BUN_MARIADB_TEST_IMAGE || "mariadb:11.8"; const DB_NAME = "bun_mariadb_test"; const PASSWORD = "bun_mariadb_test_pw"; const CONTAINER = `bun-mariadb-test-${process.pid}`; const READY_TIMEOUT_MS = 120_000; const testArgs = process.argv.slice(2); async function run(cmd: string[], opts: { quiet?: boolean } = {}) { const proc = Bun.spawn(cmd, { stdout: opts.quiet ? "pipe" : "inherit", stderr: opts.quiet ? "pipe" : "inherit", }); const stdout = opts.quiet ? await new Response(proc.stdout).text() : ""; const exitCode = await proc.exited; return { exitCode, stdout: stdout.trim() }; } async function runTests(env: Record): Promise { const proc = Bun.spawn(["bun", "test", ...testArgs], { stdout: "inherit", stderr: "inherit", env: { ...process.env, ...env }, }); return proc.exited; } // Already pointed at a database: just run the tests if (process.env.BUN_MARIADB_TEST_DB) { process.exit(await runTests({})); } if ((await run(["docker", "info"], { quiet: true })).exitCode !== 0) { console.error( "Docker is not available. Start Docker, or set BUN_MARIADB_TEST_DB and BUN_MARIADB_SERVER_* to use an existing database.", ); process.exit(1); } let removed = false; async function removeContainer() { if (removed) return; removed = true; await run(["docker", "rm", "-f", CONTAINER], { quiet: true }); } for (const signal of ["SIGINT", "SIGTERM"] as const) { process.on(signal, async () => { await removeContainer(); process.exit(130); }); } let exitCode = 1; try { console.log(`Starting ${IMAGE} (${CONTAINER}) ...`); const started = await run( [ "docker", "run", "-d", "--rm", "--name", CONTAINER, "-e", `MARIADB_ROOT_PASSWORD=${PASSWORD}`, "-e", `MARIADB_DATABASE=${DB_NAME}`, // Random free host port, bound to localhost only "-p", "127.0.0.1::3306", IMAGE, ], { quiet: true }, ); if (started.exitCode !== 0) { throw new Error(`docker run failed (exit ${started.exitCode})`); } const portOut = await run(["docker", "port", CONTAINER, "3306/tcp"], { quiet: true, }); const port = portOut.stdout.split("\n")[0]?.split(":").pop(); if (!port) { throw new Error(`Could not read the container's mapped port`); } /** * Wait for a TCP connection. The image's init phase runs a temporary * server with networking disabled, so a successful TCP query means the * real server is up. */ const deadline = Date.now() + READY_TIMEOUT_MS; let ready = false; while (Date.now() < deadline) { const client = new SQL({ adapter: "mariadb", hostname: "127.0.0.1", port: Number(port), username: "root", password: PASSWORD, database: DB_NAME, connectionTimeout: 2, }); try { await client.unsafe("SELECT 1"); ready = true; } catch { await Bun.sleep(1000); } finally { await client.close().catch(() => {}); } if (ready) break; } if (!ready) { throw new Error(`MariaDB did not become ready within ${READY_TIMEOUT_MS / 1000}s`); } console.log(`MariaDB ready on 127.0.0.1:${port}\n`); exitCode = await runTests({ BUN_MARIADB_TEST_DB: DB_NAME, BUN_MARIADB_SERVER_HOST: "127.0.0.1", BUN_MARIADB_SERVER_PORT: port, BUN_MARIADB_SERVER_USERNAME: "root", BUN_MARIADB_SERVER_PASSWORD: PASSWORD, }); } catch (error: any) { console.error(`Test setup failed: ${error.message}`); exitCode = 1; } finally { await removeContainer(); } process.exit(exitCode);