mirror of
https://github.com/oven-sh/bun
synced 2026-02-16 13:51:47 +00:00
Compare commits
2 Commits
claude/fix
...
cursor/imp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83d0b91d4e | ||
|
|
da92222307 |
@@ -1931,6 +1931,19 @@ pub noinline fn runErrorHandler(this: *VirtualMachine, result: JSValue, exceptio
|
||||
this.had_errors = false;
|
||||
defer this.had_errors = prev_had_errors;
|
||||
|
||||
// In test mode with --quiet flag, suppress error output during test execution
|
||||
// The errors will still be collected and shown in the summary
|
||||
if (isBunTest) {
|
||||
const jest = @import("test/jest.zig");
|
||||
if (jest.Jest.runner) |runner| {
|
||||
if (runner.test_options.quiet) {
|
||||
// Still need to process the error for collection purposes
|
||||
// but don't print it to the console
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const error_writer = Output.errorWriter();
|
||||
var buffered_writer = std.io.bufferedWriter(error_writer);
|
||||
defer {
|
||||
|
||||
@@ -1315,19 +1315,27 @@ pub const TestRunnerTask = struct {
|
||||
deduped = true;
|
||||
} else {
|
||||
if (is_unhandled and Jest.runner != null) {
|
||||
Output.prettyErrorln(
|
||||
\\<r>
|
||||
\\<b><d>#<r> <red><b>Unhandled error<r><d> between tests<r>
|
||||
\\<d>-------------------------------<r>
|
||||
\\
|
||||
, .{});
|
||||
// Check if we're in quiet mode before printing unhandled error headers
|
||||
const should_print = if (Jest.runner) |runner| !runner.test_options.quiet else true;
|
||||
if (should_print) {
|
||||
Output.prettyErrorln(
|
||||
\\<r>
|
||||
\\<b><d>#<r> <red><b>Unhandled error<r><d> between tests<r>
|
||||
\\<d>-------------------------------<r>
|
||||
\\
|
||||
, .{});
|
||||
|
||||
Output.flush();
|
||||
Output.flush();
|
||||
}
|
||||
}
|
||||
jsc_vm.runErrorHandlerWithDedupe(rejection, jsc_vm.onUnhandledRejectionExceptionList);
|
||||
if (is_unhandled and Jest.runner != null) {
|
||||
Output.prettyError("<r><d>-------------------------------<r>\n\n", .{});
|
||||
Output.flush();
|
||||
// Check if we're in quiet mode before printing unhandled error footers
|
||||
const should_print = if (Jest.runner) |runner| !runner.test_options.quiet else true;
|
||||
if (should_print) {
|
||||
Output.prettyError("<r><d>-------------------------------<r>\n\n", .{});
|
||||
Output.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1597,7 +1605,9 @@ pub const TestRunnerTask = struct {
|
||||
describe,
|
||||
),
|
||||
.fail_because_failing_test_passed => |count| {
|
||||
Output.prettyErrorln(" <d>^<r> <red>this test is marked as failing but it passed.<r> <d>Remove `.failing` if tested behavior now works", .{});
|
||||
if (!Jest.runner.?.test_options.quiet) {
|
||||
Output.prettyErrorln(" <d>^<r> <red>this test is marked as failing but it passed.<r> <d>Remove `.failing` if tested behavior now works", .{});
|
||||
}
|
||||
Jest.runner.?.reportFailure(
|
||||
test_id,
|
||||
this.source_file_path,
|
||||
@@ -1608,8 +1618,10 @@ pub const TestRunnerTask = struct {
|
||||
);
|
||||
},
|
||||
.fail_because_expected_has_assertions => {
|
||||
Output.err(error.AssertionError, "received <red>0 assertions<r>, but expected <green>at least one assertion<r> to be called\n", .{});
|
||||
Output.flush();
|
||||
if (!Jest.runner.?.test_options.quiet) {
|
||||
Output.err(error.AssertionError, "received <red>0 assertions<r>, but expected <green>at least one assertion<r> to be called\n", .{});
|
||||
Output.flush();
|
||||
}
|
||||
Jest.runner.?.reportFailure(
|
||||
test_id,
|
||||
this.source_file_path,
|
||||
@@ -1620,11 +1632,13 @@ pub const TestRunnerTask = struct {
|
||||
);
|
||||
},
|
||||
.fail_because_expected_assertion_count => |counter| {
|
||||
Output.err(error.AssertionError, "expected <green>{d} assertions<r>, but test ended with <red>{d} assertions<r>\n", .{
|
||||
counter.expected,
|
||||
counter.actual,
|
||||
});
|
||||
Output.flush();
|
||||
if (!Jest.runner.?.test_options.quiet) {
|
||||
Output.err(error.AssertionError, "expected <green>{d} assertions<r>, but test ended with <red>{d} assertions<r>\n", .{
|
||||
counter.expected,
|
||||
counter.actual,
|
||||
});
|
||||
Output.flush();
|
||||
}
|
||||
Jest.runner.?.reportFailure(
|
||||
test_id,
|
||||
this.source_file_path,
|
||||
@@ -1638,7 +1652,9 @@ pub const TestRunnerTask = struct {
|
||||
.skipped_because_label => Jest.runner.?.reportFilteredOut(test_id, this.source_file_path, test_.label, describe),
|
||||
.todo => Jest.runner.?.reportTodo(test_id, this.source_file_path, test_.label, describe),
|
||||
.fail_because_todo_passed => |count| {
|
||||
Output.prettyErrorln(" <d>^<r> <red>this test is marked as todo but passes.<r> <d>Remove `.todo` or check that test is correct.<r>", .{});
|
||||
if (!Jest.runner.?.test_options.quiet) {
|
||||
Output.prettyErrorln(" <d>^<r> <red>this test is marked as todo but passes.<r> <d>Remove `.todo` or check that test is correct.<r>", .{});
|
||||
}
|
||||
Jest.runner.?.reportFailure(
|
||||
test_id,
|
||||
this.source_file_path,
|
||||
|
||||
@@ -362,6 +362,7 @@ pub const Command = struct {
|
||||
|
||||
file_reporter: ?TestCommand.FileReporter = null,
|
||||
reporter_outfile: ?[]const u8 = null,
|
||||
quiet: bool = false,
|
||||
};
|
||||
|
||||
pub const Debugger = union(enum) {
|
||||
|
||||
@@ -191,6 +191,7 @@ pub const test_only_params = [_]ParamType{
|
||||
clap.parseParam("-t, --test-name-pattern <STR> Run only tests with a name that matches the given regex.") catch unreachable,
|
||||
clap.parseParam("--reporter <STR> Specify the test reporter. Currently --reporter=junit is the only supported format.") catch unreachable,
|
||||
clap.parseParam("--reporter-outfile <STR> The output file used for the format from --reporter.") catch unreachable,
|
||||
clap.parseParam("--quiet Print each test as a single dot") catch unreachable,
|
||||
};
|
||||
pub const test_params = test_only_params ++ runtime_params_ ++ transpiler_params_ ++ base_params_;
|
||||
|
||||
@@ -480,6 +481,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.quiet = args.flag("--quiet");
|
||||
}
|
||||
|
||||
ctx.args.absolute_working_dir = cwd;
|
||||
|
||||
@@ -609,9 +609,17 @@ pub const CommandLineReporter = struct {
|
||||
|
||||
var this: *CommandLineReporter = @fieldParentPtr("callback", cb);
|
||||
|
||||
writeTestStatusLine(.pass, &writer);
|
||||
|
||||
printTestLine(.pass, label, elapsed_ns, parent, expectations, false, writer, file, this.file_reporter);
|
||||
if (this.jest.test_options.quiet) {
|
||||
// In quiet mode, print a green dot for passed tests
|
||||
if (Output.enable_ansi_colors_stderr) {
|
||||
writer.writeAll(Output.prettyFmt("<green>.<r>", true)) catch unreachable;
|
||||
} else {
|
||||
writer.writeAll(".") catch unreachable;
|
||||
}
|
||||
} else {
|
||||
writeTestStatusLine(.pass, &writer);
|
||||
printTestLine(.pass, label, elapsed_ns, parent, expectations, false, writer, file, this.file_reporter);
|
||||
}
|
||||
|
||||
this.jest.tests.items(.status)[id] = TestRunner.Test.Status.pass;
|
||||
this.summary().pass += 1;
|
||||
@@ -622,20 +630,42 @@ pub const CommandLineReporter = struct {
|
||||
var writer_ = Output.errorWriter();
|
||||
var this: *CommandLineReporter = @fieldParentPtr("callback", cb);
|
||||
|
||||
if (this.jest.test_options.quiet) {
|
||||
// In quiet mode, print a red dot and then a newline to separate from error output
|
||||
if (Output.enable_ansi_colors_stderr) {
|
||||
writer_.writeAll(Output.prettyFmt("<red>.<r>\n", true)) catch unreachable;
|
||||
} else {
|
||||
writer_.writeAll(".\n") catch unreachable;
|
||||
}
|
||||
}
|
||||
|
||||
// when the tests fail, we want to repeat the failures at the end
|
||||
// so that you can see them better when there are lots of tests that ran
|
||||
const initial_length = this.failures_to_repeat_buf.items.len;
|
||||
var writer = this.failures_to_repeat_buf.writer(bun.default_allocator);
|
||||
|
||||
writeTestStatusLine(.fail, &writer);
|
||||
printTestLine(.fail, label, elapsed_ns, parent, expectations, false, writer, file, this.file_reporter);
|
||||
if (!this.jest.test_options.quiet) {
|
||||
writeTestStatusLine(.fail, &writer);
|
||||
printTestLine(.fail, label, elapsed_ns, parent, expectations, false, writer, file, this.file_reporter);
|
||||
|
||||
// We must always reset the colors because (skip) will have set them to <d>
|
||||
if (Output.enable_ansi_colors_stderr) {
|
||||
writer.writeAll(Output.prettyFmt("<r>", true)) catch unreachable;
|
||||
// We must always reset the colors because (skip) will have set them to <d>
|
||||
if (Output.enable_ansi_colors_stderr) {
|
||||
writer.writeAll(Output.prettyFmt("<r>", true)) catch unreachable;
|
||||
}
|
||||
} else {
|
||||
// In quiet mode, still add failure info to the repeat buffer for the summary
|
||||
writeTestStatusLine(.fail, &writer);
|
||||
printTestLine(.fail, label, elapsed_ns, parent, expectations, false, writer, file, this.file_reporter);
|
||||
|
||||
// We must always reset the colors because (skip) will have set them to <d>
|
||||
if (Output.enable_ansi_colors_stderr) {
|
||||
writer.writeAll(Output.prettyFmt("<r>", true)) catch unreachable;
|
||||
}
|
||||
}
|
||||
|
||||
writer_.writeAll(this.failures_to_repeat_buf.items[initial_length..]) catch unreachable;
|
||||
if (!this.jest.test_options.quiet) {
|
||||
writer_.writeAll(this.failures_to_repeat_buf.items[initial_length..]) catch unreachable;
|
||||
}
|
||||
|
||||
Output.flush();
|
||||
|
||||
@@ -655,6 +685,15 @@ pub const CommandLineReporter = struct {
|
||||
var writer_ = Output.errorWriter();
|
||||
var this: *CommandLineReporter = @fieldParentPtr("callback", cb);
|
||||
|
||||
if (this.jest.test_options.quiet) {
|
||||
// In quiet mode, print a yellow dot for skipped tests
|
||||
if (Output.enable_ansi_colors_stderr) {
|
||||
writer_.writeAll(Output.prettyFmt("<yellow>.<r>", true)) catch unreachable;
|
||||
} else {
|
||||
writer_.writeAll(".") catch unreachable;
|
||||
}
|
||||
}
|
||||
|
||||
// If you do it.only, don't report the skipped tests because its pretty noisy
|
||||
if (jest.Jest.runner != null and !jest.Jest.runner.?.only) {
|
||||
// when the tests skip, we want to repeat the failures at the end
|
||||
@@ -665,8 +704,10 @@ pub const CommandLineReporter = struct {
|
||||
writeTestStatusLine(.skip, &writer);
|
||||
printTestLine(.skip, label, elapsed_ns, parent, expectations, true, writer, file, this.file_reporter);
|
||||
|
||||
writer_.writeAll(this.skips_to_repeat_buf.items[initial_length..]) catch unreachable;
|
||||
Output.flush();
|
||||
if (!this.jest.test_options.quiet) {
|
||||
writer_.writeAll(this.skips_to_repeat_buf.items[initial_length..]) catch unreachable;
|
||||
Output.flush();
|
||||
}
|
||||
}
|
||||
|
||||
// this.updateDots();
|
||||
@@ -678,6 +719,16 @@ pub const CommandLineReporter = struct {
|
||||
pub fn handleTestFilteredOut(cb: *TestRunner.Callback, id: Test.ID, _: string, _: string, expectations: u32, _: u64, _: ?*jest.DescribeScope) void {
|
||||
var this: *CommandLineReporter = @fieldParentPtr("callback", cb);
|
||||
|
||||
if (this.jest.test_options.quiet) {
|
||||
// In quiet mode, print a dim dot for filtered out tests
|
||||
const writer_ = Output.errorWriter();
|
||||
if (Output.enable_ansi_colors_stderr) {
|
||||
writer_.writeAll(Output.prettyFmt("<d>.<r>", true)) catch unreachable;
|
||||
} else {
|
||||
writer_.writeAll(".") catch unreachable;
|
||||
}
|
||||
}
|
||||
|
||||
// this.updateDots();
|
||||
this.summary().skipped_because_label += 1;
|
||||
this.summary().skip += 1;
|
||||
@@ -690,6 +741,15 @@ pub const CommandLineReporter = struct {
|
||||
|
||||
var this: *CommandLineReporter = @fieldParentPtr("callback", cb);
|
||||
|
||||
if (this.jest.test_options.quiet) {
|
||||
// In quiet mode, print a magenta dot for todo tests
|
||||
if (Output.enable_ansi_colors_stderr) {
|
||||
writer_.writeAll(Output.prettyFmt("<magenta>.<r>", true)) catch unreachable;
|
||||
} else {
|
||||
writer_.writeAll(".") catch unreachable;
|
||||
}
|
||||
}
|
||||
|
||||
// when the tests skip, we want to repeat the failures at the end
|
||||
// so that you can see them better when there are lots of tests that ran
|
||||
const initial_length = this.todos_to_repeat_buf.items.len;
|
||||
@@ -698,8 +758,10 @@ pub const CommandLineReporter = struct {
|
||||
writeTestStatusLine(.todo, &writer);
|
||||
printTestLine(.todo, label, elapsed_ns, parent, expectations, true, writer, file, this.file_reporter);
|
||||
|
||||
writer_.writeAll(this.todos_to_repeat_buf.items[initial_length..]) catch unreachable;
|
||||
Output.flush();
|
||||
if (!this.jest.test_options.quiet) {
|
||||
writer_.writeAll(this.todos_to_repeat_buf.items[initial_length..]) catch unreachable;
|
||||
Output.flush();
|
||||
}
|
||||
|
||||
// this.updateDots();
|
||||
this.summary().todo += 1;
|
||||
@@ -712,6 +774,11 @@ pub const CommandLineReporter = struct {
|
||||
const tests = summary_.fail + summary_.pass + summary_.skip + summary_.todo;
|
||||
const files = summary_.files;
|
||||
|
||||
// In quiet mode, add a newline after the dots to separate from the summary
|
||||
if (this.jest.test_options.quiet and tests > 0) {
|
||||
Output.prettyError("\n", .{});
|
||||
}
|
||||
|
||||
Output.prettyError("Ran {d} test{s} across {d} file{s}. ", .{
|
||||
tests,
|
||||
if (tests == 1) "" else "s",
|
||||
|
||||
@@ -974,6 +974,172 @@ describe("bun test", () => {
|
||||
expect(stderr).not.toContain("test #1");
|
||||
expect(stderr).toContain("index.ts");
|
||||
});
|
||||
|
||||
describe("--quiet", () => {
|
||||
test("should print dots instead of verbose output", () => {
|
||||
const stderr = runTest({
|
||||
args: ["--quiet"],
|
||||
input: `
|
||||
import { test, expect } from "bun:test";
|
||||
test("pass", () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
test("fail", () => {
|
||||
expect(true).toBe(false);
|
||||
});
|
||||
test.skip("skip", () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
test.todo("todo");
|
||||
`,
|
||||
expectExitCode: 1,
|
||||
});
|
||||
|
||||
// Should not contain verbose test names
|
||||
expect(stderr).not.toContain("pass");
|
||||
expect(stderr).not.toContain("skip");
|
||||
expect(stderr).not.toContain("todo");
|
||||
|
||||
// Should contain dots (we can't easily test colors in tests, but we can test the structure)
|
||||
expect(stderr).toMatch(/\./); // Should contain dots
|
||||
|
||||
// Should still contain summary
|
||||
expect(stderr).toContain("1 pass");
|
||||
expect(stderr).toContain("1 fail");
|
||||
expect(stderr).toContain("1 skip");
|
||||
expect(stderr).toContain("1 todo");
|
||||
});
|
||||
|
||||
test("should show failure details in summary", () => {
|
||||
const stderr = runTest({
|
||||
args: ["--quiet"],
|
||||
input: `
|
||||
import { test, expect } from "bun:test";
|
||||
test("failing test", () => {
|
||||
expect(true).toBe(false);
|
||||
});
|
||||
`,
|
||||
expectExitCode: 1,
|
||||
});
|
||||
|
||||
// Should not show test name immediately
|
||||
expect(stderr).not.toMatch(/^.*failing test/m);
|
||||
|
||||
// Should show failure details in summary
|
||||
expect(stderr).toContain("expect(received).toBe(expected)");
|
||||
expect(stderr).toContain("Expected: false");
|
||||
expect(stderr).toContain("Received: true");
|
||||
});
|
||||
|
||||
test("should work with multiple test files", () => {
|
||||
const stderr = runTest({
|
||||
args: ["--quiet"],
|
||||
input: [
|
||||
`
|
||||
import { test, expect } from "bun:test";
|
||||
test("test1", () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
`,
|
||||
`
|
||||
import { test, expect } from "bun:test";
|
||||
test("test2", () => {
|
||||
expect(true).toBe(false);
|
||||
});
|
||||
`,
|
||||
],
|
||||
expectExitCode: 1,
|
||||
});
|
||||
|
||||
// Should not contain verbose test names
|
||||
expect(stderr).not.toContain("test1");
|
||||
expect(stderr).not.toContain("test2");
|
||||
|
||||
// Should contain dots
|
||||
expect(stderr).toMatch(/\./);
|
||||
|
||||
// Should contain summary
|
||||
expect(stderr).toContain("1 pass");
|
||||
expect(stderr).toContain("1 fail");
|
||||
});
|
||||
|
||||
test("should work with passing tests only", () => {
|
||||
const stderr = runTest({
|
||||
args: ["--quiet"],
|
||||
input: `
|
||||
import { test, expect } from "bun:test";
|
||||
test("test1", () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
test("test2", () => {
|
||||
expect(1 + 1).toBe(2);
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
// Should not contain verbose test names
|
||||
expect(stderr).not.toContain("test1");
|
||||
expect(stderr).not.toContain("test2");
|
||||
|
||||
// Should contain dots
|
||||
expect(stderr).toMatch(/\./);
|
||||
|
||||
// Should contain summary
|
||||
expect(stderr).toContain("2 pass");
|
||||
});
|
||||
|
||||
test("should work with filtered tests", () => {
|
||||
const stderr = runTest({
|
||||
args: ["--quiet", "-t", "match"],
|
||||
input: `
|
||||
import { test, expect } from "bun:test";
|
||||
test("match this", () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
test("not this", () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
// Should not contain verbose test names
|
||||
expect(stderr).not.toContain("match this");
|
||||
expect(stderr).not.toContain("not this");
|
||||
|
||||
// Should contain dots
|
||||
expect(stderr).toMatch(/\./);
|
||||
|
||||
// Should contain summary for only matched tests
|
||||
expect(stderr).toContain("1 pass");
|
||||
expect(stderr).not.toContain("2 pass");
|
||||
});
|
||||
|
||||
test("should work with --bail", () => {
|
||||
const stderr = runTest({
|
||||
args: ["--quiet", "--bail"],
|
||||
input: `
|
||||
import { test, expect } from "bun:test";
|
||||
test("test1", () => {
|
||||
expect(true).toBe(false);
|
||||
});
|
||||
test("test2", () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
`,
|
||||
expectExitCode: 1,
|
||||
});
|
||||
|
||||
// Should not contain verbose test names
|
||||
expect(stderr).not.toContain("test1");
|
||||
expect(stderr).not.toContain("test2");
|
||||
|
||||
// Should contain dots
|
||||
expect(stderr).toMatch(/\./);
|
||||
|
||||
// Should bail after first failure
|
||||
expect(stderr).toContain("Bailed out after 1 failure");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createTest(input?: string | (string | { filename: string; contents: string })[], filename?: string): string {
|
||||
|
||||
Reference in New Issue
Block a user