Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/bun.js/ConsoleObject.zig
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ fn messageWithTypeAndLevel_(
const Writer = @TypeOf(writer);

if (bun.jsc.Jest.Jest.runner) |runner| {
runner.bun_test_root.onBeforePrint();
runner.bun_test_root.onBeforePrint(null);
}

var print_length = len;
Expand Down
65 changes: 52 additions & 13 deletions src/bun.js/test/bun_test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ pub const BunTestPtr = bun.ptr.shared.WithOptions(*BunTest, .{
pub const BunTestRoot = struct {
gpa: std.mem.Allocator,
active_file: BunTestPtr.Optional,
/// Set during BunTest.run() so that describe callbacks in parallel mode
/// can find their BunTest via cloneActiveFile() even after active_file
/// has been detached.
running_file: BunTestPtr.Optional = .initNull(),

hook_scope: *DescribeScope,

Expand Down Expand Up @@ -170,33 +174,58 @@ pub const BunTestRoot = struct {
this.active_file.deinit();
this.active_file = .initNull();
}
/// Detach the active file without nullifying its reporter.
/// Used for file parallelism: the file is removed from the active slot
/// (allowing another file to be loaded) but keeps its reporter alive
/// so that test results can still be reported while execution continues.
pub fn detachFile(this: *BunTestRoot) void {
group.begin(@src());
defer group.end();

bun.assert(this.active_file.get() != null);
this.active_file.deinit();
this.active_file = .initNull();
}
pub fn getActiveFileUnlessInPreload(this: *BunTestRoot, vm: *jsc.VirtualMachine) ?*BunTest {
if (vm.is_in_preload) {
return null;
}
return this.active_file.get();
return this.active_file.get() orelse this.running_file.get();
}
pub fn cloneActiveFile(this: *BunTestRoot) ?BunTestPtr {
var clone = this.active_file.clone();
return clone.take();
if (clone.take()) |ptr| return ptr;
// Fallback: check running_file (set during BunTest.run() for parallel mode)
var running_clone = this.running_file.clone();
return running_clone.take();
}

pub const FirstLast = struct {
first: bool,
last: bool,
};

pub fn onBeforePrint(this: *BunTestRoot) void {
if (this.active_file.get()) |active_file| {
if (active_file.reporter) |reporter| {
if (reporter.reporters.dots and reporter.last_printed_dot) {
bun.Output.prettyError("<r>\n", .{});
bun.Output.flush();
reporter.last_printed_dot = false;
}
if (bun.jsc.Jest.Jest.runner) |runner| {
runner.current_file.printIfNeeded();
pub fn onBeforePrint(this: *BunTestRoot, buntest: ?*BunTest) void {
const file = buntest orelse if (this.active_file.get()) |af| af else return;
if (file.reporter) |reporter| {
if (reporter.reporters.dots and reporter.last_printed_dot) {
bun.Output.prettyError("<r>\n", .{});
bun.Output.flush();
reporter.last_printed_dot = false;
}
if (bun.jsc.Jest.Jest.runner) |runner| {
// If the file has changed (or this is the first print), update the current_file header
if (runner.current_file_id == null or runner.current_file_id.? != file.file_id) {
const file_path = runner.files.items(.source)[file.file_id].path.text;
const file_title = bun.path.relative(bun.fs.FileSystem.instance.top_level_dir, file_path);
const file_prefix: []const u8 = if (bun.Output.is_github_action) "::group::" else "";
runner.current_file.has_printed_filename = false;
runner.current_file.freeAndClear();
runner.current_file.title = bun.handleOom(bun.default_allocator.dupe(u8, file_title));
runner.current_file.prefix = bun.handleOom(bun.default_allocator.dupe(u8, file_prefix));
runner.current_file_id = file.file_id;
}
runner.current_file.printIfNeeded();
}
}
}
Expand Down Expand Up @@ -531,6 +560,16 @@ pub const BunTest = struct {
this.in_run_loop = true;
defer this.in_run_loop = false;

// Make this BunTest findable via cloneActiveFile() during collection,
// even if active_file has been detached (parallel mode).
const prev_running = this.bun_test_root.running_file;
var cloned = this_strong.clone();
this.bun_test_root.running_file = cloned.toOptional();
defer {
this.bun_test_root.running_file.deinit();
this.bun_test_root.running_file = prev_running;
}

var min_timeout: bun.timespec = .epoch;

while (this.result_queue.readItem()) |result| {
Expand Down Expand Up @@ -732,7 +771,7 @@ pub const BunTest = struct {
if (handle_status == .hide_error) return; // do not print error, it was already consumed
if (exception == null) return; // the exception should not be visible (eg m_terminationException)

this.bun_test_root.onBeforePrint();
this.bun_test_root.onBeforePrint(this);
if (handle_status == .show_unhandled_error_between_tests or handle_status == .show_unhandled_error_in_describe) {
this.reporter.?.jest.unhandled_errors_between_tests += 1;
bun.Output.prettyErrorln(
Expand Down
3 changes: 2 additions & 1 deletion src/bun.js/test/jest.zig
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const CurrentFile = struct {
print(title, prefix, repeat_count, repeat_index);
}

fn freeAndClear(this: *CurrentFile) void {
pub fn freeAndClear(this: *CurrentFile) void {
bun.default_allocator.free(this.title);
bun.default_allocator.free(this.prefix);
}
Expand Down Expand Up @@ -63,6 +63,7 @@ const CurrentFile = struct {

pub const TestRunner = struct {
current_file: CurrentFile = CurrentFile{},
current_file_id: ?File.ID = null,
files: File.List = .{},
index: File.Map = File.Map{},
only: bool = false,
Expand Down
1 change: 1 addition & 0 deletions src/cli.zig
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ pub const Command = struct {
test_filter_pattern: ?[]const u8 = null,
test_filter_regex: ?*RegularExpression = null,
max_concurrency: u32 = 20,
file_parallelism: u32 = 1,

reporters: struct {
dots: bool = false,
Expand Down
14 changes: 14 additions & 0 deletions src/cli/Arguments.zig
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ pub const test_only_params = [_]ParamType{
clap.parseParam("--dots Enable dots reporter. Shorthand for --reporter=dots.") catch unreachable,
clap.parseParam("--only-failures Only display test failures, hiding passing tests.") catch unreachable,
clap.parseParam("--max-concurrency <NUMBER> Maximum number of concurrent tests to execute at once. Default is 20.") catch unreachable,
clap.parseParam("--file-parallelism <NUMBER> Number of test files to run in parallel. Default is 1 (sequential).") catch unreachable,
clap.parseParam("--path-ignore-patterns <STR>... Glob patterns for test file paths to ignore.") catch unreachable,
};
pub const test_params = test_only_params ++ runtime_params_ ++ transpiler_params_ ++ base_params_;
Expand Down Expand Up @@ -497,6 +498,19 @@ pub fn parse(allocator: std.mem.Allocator, ctx: Command.Context, comptime cmd: C
}
}

if (args.option("--file-parallelism")) |file_parallelism| {
if (file_parallelism.len > 0) {
ctx.test_options.file_parallelism = std.fmt.parseInt(u32, file_parallelism, 10) catch {
Output.prettyErrorln("<r><red>error<r>: Invalid file-parallelism: \"{s}\"", .{file_parallelism});
Global.exit(1);
};
if (ctx.test_options.file_parallelism == 0) {
Output.prettyErrorln("<r><red>error<r>: --file-parallelism must be greater than 0", .{});
Global.exit(1);
}
}
}

if (!ctx.test_options.coverage.enabled) {
ctx.test_options.coverage.enabled = args.flag("--coverage");
}
Expand Down
Loading