Run tests against a throwaway MariaDB Docker container
- Add scripts/test.ts: starts a temporary MariaDB container on a random localhost port, waits for TCP readiness, runs all test files, and always removes the container (including on Ctrl+C) - `bun run test` uses the script; `bun run test:unit` runs without a DB - BUN_MARIADB_TEST_IMAGE selects the image; an existing BUN_MARIADB_TEST_DB skips Docker - Document in README Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -962,11 +962,14 @@ bun-mariadb/
|
|||||||
bun run test
|
bun run test
|
||||||
```
|
```
|
||||||
|
|
||||||
Unit tests need no database. The MariaDB integration tests run only when `BUN_MARIADB_TEST_DB` is set (plus the usual `BUN_MARIADB_SERVER_*` variables). Point it at a throwaway database: the tests create and drop tables.
|
Starts a throwaway MariaDB container (Docker required, image `mariadb:11.8` by default), runs every test file against it, then removes the container. Extra arguments are passed to `bun test`, e.g. `bun run test tests/db.test.ts`.
|
||||||
|
|
||||||
```bash
|
| Variable | Description |
|
||||||
BUN_MARIADB_TEST_DB=bun_mariadb_test bun run test
|
| ------------------------ | --------------------------------------------------------------------------- |
|
||||||
```
|
| `BUN_MARIADB_TEST_IMAGE` | Docker image to use (MariaDB 11.7+ for `VECTOR`) |
|
||||||
|
| `BUN_MARIADB_TEST_DB` | Skip Docker and use this existing database (with `BUN_MARIADB_SERVER_*`). The tests create and drop tables, so use a throwaway database. |
|
||||||
|
|
||||||
|
`bun run test:unit` (or plain `bun test`) runs without a database; the integration tests are skipped.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -29,7 +29,8 @@
|
|||||||
"dev": "tsc --watch",
|
"dev": "tsc --watch",
|
||||||
"compile": "rm -rf dist tsconfig.tsbuildinfo && tsc -p tsconfig.json && chmod +x dist/commands/index.js",
|
"compile": "rm -rf dist tsconfig.tsbuildinfo && tsc -p tsconfig.json && chmod +x dist/commands/index.js",
|
||||||
"typecheck": "tsc -p tsconfig.json --noEmit",
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||||
"test": "bun test tests",
|
"test": "bun run scripts/test.ts",
|
||||||
|
"test:unit": "bun test",
|
||||||
"prepublishOnly": "bun run compile"
|
"prepublishOnly": "bun run compile"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
+157
@@ -0,0 +1,157 @@
|
|||||||
|
#!/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<string, string>): Promise<number> {
|
||||||
|
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);
|
||||||
Reference in New Issue
Block a user