Skip to content

Commit

Permalink
feat: support default test timeout in bunfig.toml
Browse files Browse the repository at this point in the history
  • Loading branch information
treyturner committed Sep 23, 2024
1 parent 3d68a94 commit a1bbae5
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 0 deletions.
9 changes: 9 additions & 0 deletions docs/runtime/bunfig.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,15 @@ Same as the top-level `smol` field, but only applies to `bun test`.
smol = true
```

### `test.timeout`

A default timeout for each test in milliseconds. Default `5000`. Use `--timeout` to override.

```toml
[test]
timeout = 15000
```

### `test.coverage`

Enables coverage reporting. Default `false`. Use `--coverage` to override.
Expand Down
8 changes: 8 additions & 0 deletions src/bunfig.zig
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,14 @@ pub const Bunfig = struct {
this.ctx.runtime_options.smol = expr.data.e_boolean.value;
}

if (test_.get("timeout")) |expr| {
try this.expect(expr, .e_number);
// Is there a better way to only use bunfig value if --timeout wasn't passed as a CLI argument?
if (this.ctx.test_options.default_timeout_ms == (Command.TestOptions{}).default_timeout_ms) {
this.ctx.test_options.default_timeout_ms = expr.data.e_number.toU32();
}
}

if (test_.get("coverage")) |expr| {
try this.expect(expr, .e_boolean);
this.ctx.test_options.coverage.enabled = expr.data.e_boolean.value;
Expand Down
129 changes: 129 additions & 0 deletions test/cli/test/bun-test-bunfig.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { spawnSync } from "bun";
import { afterEach, describe, expect, test } from "bun:test";
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
import { rmSync } from "node:fs";

describe("bunfig test options", () => {
describe("timeout", () => {
let cwd: string;

afterEach(() => {
if (cwd) rmSync(cwd, { recursive: true });
});

test("bunfig timeout overrides default", async () => {
const bunfigTimeout = 500;
const bunfig = `[test]\ntimeout = ${bunfigTimeout}\n`;
const testFile = `
import { test, expect } from "bun:test";
test("takes 2 seconds", async () => {
const p = new Promise(r => setTimeout(r, 2000, true));
expect(await p).toBeTrue();
});
`;
cwd = tempDirWithFiles("test.bunfig.timeout", {
"bunfig.toml": bunfig,
"bun-test-timeout.test.ts": testFile,
});

const result = spawnSync({
cmd: [bunExe(), "-c=bunfig.toml", "test"],
env: bunEnv,
stderr: "pipe",
cwd,
});
const stderr = result.stderr.toString().trim();

const errorPtn = /timed out after (\d+)ms/;
const errorMatch = stderr.match(errorPtn);
expect(errorMatch, "test didn't report timeout error to stderr").not.toBeNull();
const errorTimeout = parseInt(errorMatch!.at(1)!);
expect(errorTimeout, "test timeout error doesn't reflect bunfig value").toEqual(bunfigTimeout);

const durationPtn = /\(fail\) .* \[(\d+)(?:\.\d+)?ms\]/;
const durationMatch = stderr.match(durationPtn);
expect(durationMatch, "test didn't output failing result with actual duration to stderr").not.toBeNull();
const duration = parseInt(durationMatch!.at(1)!);
expect(duration, "test timed out before bunfig timeout value").toBeGreaterThanOrEqual(bunfigTimeout);
expect(duration, "test didn't honor bunfig timeout value").toBeLessThanOrEqual(5000);
});

test("cli timeout overrides bunfig", async () => {
const cliTimeout = 500;
const bunfigTimeout = 1000;
const bunfig = `[test]\ntimeout = ${bunfigTimeout}\n`;
const testFile = `
import { test, expect } from "bun:test";
test("takes 2 seconds", async () => {
const p = new Promise(r => setTimeout(r, 2000, true));
expect(await p).toBeTrue();
});
`;
cwd = tempDirWithFiles("test.cli.timeout.wins", {
"bunfig.toml": bunfig,
"bun-test-cli-timeout-wins.test.ts": testFile,
});

const result = spawnSync({
cmd: [bunExe(), "-c=bunfig.toml", "test", "--timeout", `${cliTimeout}`],
env: bunEnv,
stderr: "pipe",
cwd,
});
const stderr = result.stderr.toString().trim();

const errorPtn = /timed out after (\d+)ms/;
const errorMatch = stderr.match(errorPtn);
expect(errorMatch, "test didn't report timeout error to stderr").not.toBeNull();
const errorTimeout = parseInt(errorMatch!.at(1)!);
expect(errorTimeout, "test timeout error doesn't reflect cli value").toEqual(cliTimeout);

const durationPtn = /\(fail\) .* \[(\d+)(?:\.\d+)?ms\]/;
const durationMatch = stderr.match(durationPtn);
expect(durationMatch, "test didn't output failing result with actual duration to stderr").not.toBeNull();
const duration = parseInt(durationMatch!.at(1)!);
expect(duration, "test timed out before cli value").toBeGreaterThanOrEqual(cliTimeout);
expect(duration, "test honored bunfig timeout instead of cli").toBeLessThan(bunfigTimeout);
});

test("default timeout specified via cli overrides bunfig", async () => {
// This addresses a corner case identified by a weak initial implementation.
// By necessity it requires the default test timeout to prove (5s) so it should
// be removed after passing in the PR.
const cliTimeout = 5000;
const bunfigTimeout = 500;
const bunfig = `[test]\ntimeout = ${bunfigTimeout}\n`;
const testFile = `
import { test, expect } from "bun:test";
test("takes 7 seconds", async () => {
const p = new Promise(r => setTimeout(r, 7000, true));
expect(await p).toBeTrue();
});
`;
cwd = tempDirWithFiles("test.cli.timeout.wins.using.default", {
"bunfig.toml": bunfig,
"bun-test-cli-timeout-wins-using-default.test.ts": testFile,
});

const result = spawnSync({
cmd: [bunExe(), "-c=bunfig.toml", "test", "--timeout", `${cliTimeout}`],
env: bunEnv,
stderr: "pipe",
cwd,
});
const stderr = result.stderr.toString().trim();

const errorPtn = /timed out after (\d+)ms/;
const errorMatch = stderr.match(errorPtn);
expect(errorMatch, "test didn't report timeout error to stderr").not.toBeNull();
const errorTimeout = parseInt(errorMatch!.at(1)!);
expect(errorTimeout, "test timeout error doesn't reflect cli value").toEqual(cliTimeout);

const durationPtn = /\(fail\) .* \[(\d+)(?:\.\d+)?ms\]/;
const durationMatch = stderr.match(durationPtn);
expect(durationMatch, "test didn't output failing result with actual duration to stderr").not.toBeNull();
const duration = parseInt(durationMatch!.at(1)!);
expect(duration, "test timed out before cli value").toBeGreaterThanOrEqual(cliTimeout);
});
});
});

0 comments on commit a1bbae5

Please sign in to comment.