mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
Add --pass-with-no-tests flag to test runner (#23424)
## Summary This PR adds support for the `--pass-with-no-tests` CLI flag to the test runner, addressing issue #20814. With the latest v1.2.8 release, the test runner now fails when no tests match a filter. While this is useful for agentic coding workflows, there are legitimate cases where the previous behavior is preferred, such as in monorepos where a standard test file pattern is used as a filter but not all packages contain tests. This flag makes the test runner behave like Jest and Vitest, exiting with code 0 when no tests are found. ## Changes - Added `--pass-with-no-tests` flag to CLI arguments in `src/cli/Arguments.zig` - Added `pass_with_no_tests` field to `TestOptions` struct in `src/cli.zig` - Updated test runner logic in `src/cli/test_command.zig` to respect the flag - Added comprehensive tests in `test/cli/test/pass-with-no-tests.test.ts` ## Test Plan All new tests pass: - ✅ `--pass-with-no-tests` exits with 0 when no test files found - ✅ `--pass-with-no-tests` exits with 0 when filters match no tests - ✅ Without flag, still exits with 1 when no tests found (preserves existing behavior) - ✅ `--pass-with-no-tests` still fails when actual tests fail Closes #20814 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Bot <claude-bot@bun.sh> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: pfg <pfg@pfg.pw>
This commit is contained in:
@@ -342,6 +342,7 @@ pub const Command = struct {
|
||||
repeat_count: u32 = 0,
|
||||
run_todo: bool = false,
|
||||
only: bool = false,
|
||||
pass_with_no_tests: bool = false,
|
||||
concurrent: bool = false,
|
||||
randomize: bool = false,
|
||||
seed: ?u32 = null,
|
||||
|
||||
@@ -197,6 +197,7 @@ pub const test_only_params = [_]ParamType{
|
||||
clap.parseParam("--rerun-each <NUMBER> Re-run each test file <NUMBER> times, helps catch certain bugs") catch unreachable,
|
||||
clap.parseParam("--todo Include tests that are marked with \"test.todo()\"") catch unreachable,
|
||||
clap.parseParam("--only Run only tests that are marked with \"test.only()\" or \"describe.only()\"") catch unreachable,
|
||||
clap.parseParam("--pass-with-no-tests Exit with code 0 when no tests are found") catch unreachable,
|
||||
clap.parseParam("--concurrent Treat all tests as `test.concurrent()` tests") catch unreachable,
|
||||
clap.parseParam("--randomize Run tests in random order") catch unreachable,
|
||||
clap.parseParam("--seed <INT> Set the random seed for test randomization") catch unreachable,
|
||||
@@ -509,6 +510,7 @@ pub fn parse(allocator: std.mem.Allocator, ctx: Command.Context, comptime cmd: C
|
||||
ctx.test_options.update_snapshots = args.flag("--update-snapshots");
|
||||
ctx.test_options.run_todo = args.flag("--todo");
|
||||
ctx.test_options.only = args.flag("--only");
|
||||
ctx.test_options.pass_with_no_tests = args.flag("--pass-with-no-tests");
|
||||
ctx.test_options.concurrent = args.flag("--concurrent");
|
||||
ctx.test_options.randomize = args.flag("--randomize");
|
||||
|
||||
|
||||
@@ -1766,7 +1766,8 @@ pub const TestCommand = struct {
|
||||
}
|
||||
const summary = reporter.summary();
|
||||
|
||||
if (failed_to_find_any_tests or summary.didLabelFilterOutAllTests() or summary.fail > 0 or (coverage_options.enabled and coverage_options.fractions.failing and coverage_options.fail_on_low_coverage) or !write_snapshots_success) {
|
||||
const should_fail_on_no_tests = !ctx.test_options.pass_with_no_tests and (failed_to_find_any_tests or summary.didLabelFilterOutAllTests());
|
||||
if (should_fail_on_no_tests or summary.fail > 0 or (coverage_options.enabled and coverage_options.fractions.failing and coverage_options.fail_on_low_coverage) or !write_snapshots_success) {
|
||||
vm.exit_handler.exit_code = 1;
|
||||
} else if (reporter.jest.unhandled_errors_between_tests > 0) {
|
||||
vm.exit_handler.exit_code = 1;
|
||||
|
||||
99
test/cli/test/pass-with-no-tests.test.ts
Normal file
99
test/cli/test/pass-with-no-tests.test.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { bunEnv, bunExe, tempDir } from "harness";
|
||||
|
||||
test("--pass-with-no-tests exits with 0 when no test files found", async () => {
|
||||
using dir = tempDir("pass-with-no-tests", {
|
||||
"not-a-test.ts": `console.log("hello");`,
|
||||
});
|
||||
|
||||
const { exited, stderr } = Bun.spawn({
|
||||
cmd: [bunExe(), "test", "--pass-with-no-tests"],
|
||||
cwd: String(dir),
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
stdin: "ignore",
|
||||
env: bunEnv,
|
||||
});
|
||||
|
||||
const [err, exitCode] = await Promise.all([stderr.text(), exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(err).toContain("No tests found!");
|
||||
});
|
||||
|
||||
test("--pass-with-no-tests exits with 0 when filters match no tests", async () => {
|
||||
using dir = tempDir("pass-with-no-tests-filter", {
|
||||
"some.test.ts": `import { test } from "bun:test"; test("example", () => {});`,
|
||||
});
|
||||
|
||||
const { exited, stderr } = Bun.spawn({
|
||||
cmd: [bunExe(), "test", "--pass-with-no-tests", "-t", "nonexistent"],
|
||||
cwd: String(dir),
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
stdin: "ignore",
|
||||
env: bunEnv,
|
||||
});
|
||||
|
||||
const [err, exitCode] = await Promise.all([stderr.text(), exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test("without --pass-with-no-tests, exits with 1 when no test files found", async () => {
|
||||
using dir = tempDir("fail-with-no-tests", {
|
||||
"not-a-test.ts": `console.log("hello");`,
|
||||
});
|
||||
|
||||
const { exited, stderr } = Bun.spawn({
|
||||
cmd: [bunExe(), "test"],
|
||||
cwd: String(dir),
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
stdin: "ignore",
|
||||
env: bunEnv,
|
||||
});
|
||||
|
||||
const [err, exitCode] = await Promise.all([stderr.text(), exited]);
|
||||
|
||||
expect(exitCode).toBe(1);
|
||||
expect(err).toContain("No tests found!");
|
||||
});
|
||||
|
||||
test("without --pass-with-no-tests, exits with 1 when filters match no tests", async () => {
|
||||
using dir = tempDir("fail-with-no-tests-filter", {
|
||||
"some.test.ts": `import { test } from "bun:test"; test("example", () => {});`,
|
||||
});
|
||||
|
||||
const { exited } = Bun.spawn({
|
||||
cmd: [bunExe(), "test", "-t", "nonexistent"],
|
||||
cwd: String(dir),
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
stdin: "ignore",
|
||||
env: bunEnv,
|
||||
});
|
||||
|
||||
const exitCode = await exited;
|
||||
|
||||
expect(exitCode).toBe(1);
|
||||
});
|
||||
|
||||
test("--pass-with-no-tests still fails when tests fail", async () => {
|
||||
using dir = tempDir("pass-with-no-tests-but-fail", {
|
||||
"test.test.ts": `import { test, expect } from "bun:test"; test("failing", () => { expect(1).toBe(2); });`,
|
||||
});
|
||||
|
||||
const { exited } = Bun.spawn({
|
||||
cmd: [bunExe(), "test", "--pass-with-no-tests"],
|
||||
cwd: String(dir),
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
stdin: "ignore",
|
||||
env: bunEnv,
|
||||
});
|
||||
|
||||
const exitCode = await exited;
|
||||
|
||||
expect(exitCode).toBe(1);
|
||||
});
|
||||
Reference in New Issue
Block a user