mirror of
https://github.com/oven-sh/bun
synced 2026-02-18 06:41:50 +00:00
Implements the ability to run specific tests by line number using file.ts:lineNumber syntax. This allows targeting individual tests or describe blocks within a test file. Features: - Parse file:line and file:line:column syntax (column is ignored) - Filter tests based on line numbers - Support filtering describe blocks by line number - Line numbers of parent describe blocks are considered for matching
513 lines
24 KiB
Zig
513 lines
24 KiB
Zig
const Mode = enum { describe, @"test" };
|
|
mode: Mode,
|
|
cfg: bun_test.BaseScopeCfg,
|
|
/// typically `.zero`. not Strong.Optional because codegen adds it to the visit function.
|
|
each: jsc.JSValue,
|
|
|
|
pub const strings = struct {
|
|
pub const describe = bun.String.static("describe");
|
|
pub const xdescribe = bun.String.static("xdescribe");
|
|
pub const @"test" = bun.String.static("test");
|
|
pub const xtest = bun.String.static("xtest");
|
|
pub const skip = bun.String.static("skip");
|
|
pub const todo = bun.String.static("todo");
|
|
pub const failing = bun.String.static("failing");
|
|
pub const concurrent = bun.String.static("concurrent");
|
|
pub const only = bun.String.static("only");
|
|
pub const @"if" = bun.String.static("if");
|
|
pub const skipIf = bun.String.static("skipIf");
|
|
pub const todoIf = bun.String.static("todoIf");
|
|
pub const failingIf = bun.String.static("failingIf");
|
|
pub const concurrentIf = bun.String.static("concurrentIf");
|
|
pub const each = bun.String.static("each");
|
|
};
|
|
|
|
pub fn getSkip(this: *ScopeFunctions, globalThis: *JSGlobalObject) bun.JSError!JSValue {
|
|
return genericExtend(this, globalThis, .{ .self_mode = .skip }, "get .skip", strings.skip);
|
|
}
|
|
pub fn getTodo(this: *ScopeFunctions, globalThis: *JSGlobalObject) bun.JSError!JSValue {
|
|
return genericExtend(this, globalThis, .{ .self_mode = .todo }, "get .todo", strings.todo);
|
|
}
|
|
pub fn getFailing(this: *ScopeFunctions, globalThis: *JSGlobalObject) bun.JSError!JSValue {
|
|
return genericExtend(this, globalThis, .{ .self_mode = .failing }, "get .failing", strings.failing);
|
|
}
|
|
pub fn getConcurrent(this: *ScopeFunctions, globalThis: *JSGlobalObject) bun.JSError!JSValue {
|
|
return genericExtend(this, globalThis, .{ .self_concurrent = true }, "get .concurrent", strings.concurrent);
|
|
}
|
|
pub fn getOnly(this: *ScopeFunctions, globalThis: *JSGlobalObject) bun.JSError!JSValue {
|
|
return genericExtend(this, globalThis, .{ .self_only = true }, "get .only", strings.only);
|
|
}
|
|
pub fn fnIf(this: *ScopeFunctions, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
|
return genericIf(this, globalThis, callFrame, .{ .self_mode = .skip }, "call .if()", true, strings.@"if");
|
|
}
|
|
pub fn fnSkipIf(this: *ScopeFunctions, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
|
return genericIf(this, globalThis, callFrame, .{ .self_mode = .skip }, "call .skipIf()", false, strings.skipIf);
|
|
}
|
|
pub fn fnTodoIf(this: *ScopeFunctions, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
|
return genericIf(this, globalThis, callFrame, .{ .self_mode = .todo }, "call .todoIf()", false, strings.todoIf);
|
|
}
|
|
pub fn fnFailingIf(this: *ScopeFunctions, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
|
return genericIf(this, globalThis, callFrame, .{ .self_mode = .failing }, "call .failingIf()", false, strings.failingIf);
|
|
}
|
|
pub fn fnConcurrentIf(this: *ScopeFunctions, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
|
return genericIf(this, globalThis, callFrame, .{ .self_concurrent = true }, "call .concurrentIf()", false, strings.concurrentIf);
|
|
}
|
|
pub fn fnEach(this: *ScopeFunctions, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
|
groupLog.begin(@src());
|
|
defer groupLog.end();
|
|
|
|
const array = callFrame.argumentsAsArray(1)[0];
|
|
if (array.isUndefinedOrNull() or !array.isArray()) {
|
|
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis };
|
|
defer formatter.deinit();
|
|
return globalThis.throw("Expected array, got {}", .{array.toFmt(&formatter)});
|
|
}
|
|
|
|
if (this.each != .zero) return globalThis.throw("Cannot {s} on {f}", .{ "each", this });
|
|
return createBound(globalThis, this.mode, array, this.cfg, strings.each);
|
|
}
|
|
|
|
pub fn callAsFunction(globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
|
groupLog.begin(@src());
|
|
defer groupLog.end();
|
|
|
|
const this = ScopeFunctions.fromJS(callFrame.this()) orelse return globalThis.throw("Expected callee to be ScopeFunctions", .{});
|
|
const line_no = jsc.Jest.captureTestLineNumber(callFrame, globalThis);
|
|
|
|
var buntest_strong = try bun_test.js_fns.cloneActiveStrong(globalThis, .{ .signature = .{ .scope_functions = this }, .allow_in_preload = false });
|
|
defer buntest_strong.deinit();
|
|
const bunTest = buntest_strong.get();
|
|
|
|
const callback_mode: CallbackMode = switch (this.cfg.self_mode) {
|
|
.skip, .todo => .allow,
|
|
else => .require,
|
|
};
|
|
|
|
var args = try parseArguments(globalThis, callFrame, .{ .scope_functions = this }, bunTest.gpa, .{ .callback = callback_mode });
|
|
defer args.deinit(bunTest.gpa);
|
|
|
|
const callback_length = if (args.callback) |callback| try callback.getLength(globalThis) else 0;
|
|
|
|
if (this.each != .zero) {
|
|
if (this.each.isUndefinedOrNull() or !this.each.isArray()) {
|
|
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis };
|
|
defer formatter.deinit();
|
|
return globalThis.throw("Expected array, got {}", .{this.each.toFmt(&formatter)});
|
|
}
|
|
var iter = try this.each.arrayIterator(globalThis);
|
|
var test_idx: usize = 0;
|
|
while (try iter.next()) |item| : (test_idx += 1) {
|
|
if (item == .zero) break;
|
|
|
|
var args_list: std.ArrayList(Strong) = .init(bunTest.gpa);
|
|
defer args_list.deinit();
|
|
defer for (args_list.items) |*arg| arg.deinit();
|
|
|
|
if (item.isArray()) {
|
|
// Spread array as args_list (matching Jest & Vitest)
|
|
bun.handleOom(args_list.ensureUnusedCapacity(try item.getLength(globalThis)));
|
|
|
|
var item_iter = try item.arrayIterator(globalThis);
|
|
var idx: usize = 0;
|
|
while (try item_iter.next()) |array_item| : (idx += 1) {
|
|
bun.handleOom(args_list.append(.init(bunTest.gpa, array_item)));
|
|
}
|
|
} else {
|
|
bun.handleOom(args_list.append(.init(bunTest.gpa, item)));
|
|
}
|
|
|
|
var args_list_raw = bun.handleOom(std.ArrayList(jsc.JSValue).initCapacity(bunTest.gpa, args_list.items.len)); // safe because the items are held strongly in args_list
|
|
defer args_list_raw.deinit();
|
|
for (args_list.items) |arg| bun.handleOom(args_list_raw.append(arg.get()));
|
|
|
|
const formatted_label: ?[]const u8 = if (args.description) |desc| try jsc.Jest.formatLabel(globalThis, desc, args_list_raw.items, test_idx, bunTest.gpa) else null;
|
|
defer if (formatted_label) |label| bunTest.gpa.free(label);
|
|
|
|
const bound = if (args.callback) |cb| try cb.bind(globalThis, item, &bun.String.static("cb"), 0, args_list_raw.items) else null;
|
|
try this.enqueueDescribeOrTestCallback(bunTest, globalThis, callFrame, bound, formatted_label, args.options.timeout, callback_length -| args_list.items.len, line_no);
|
|
}
|
|
} else {
|
|
try this.enqueueDescribeOrTestCallback(bunTest, globalThis, callFrame, args.callback, args.description, args.options.timeout, callback_length, line_no);
|
|
}
|
|
|
|
return .js_undefined;
|
|
}
|
|
|
|
const Measure = struct {
|
|
len: usize,
|
|
fn writeEnd(this: *Measure, write: []const u8) void {
|
|
this.len += write.len;
|
|
}
|
|
};
|
|
const Write = struct {
|
|
buf: []u8,
|
|
fn writeEnd(this: *Write, write: []const u8) void {
|
|
if (this.buf.len < write.len) {
|
|
bun.debugAssert(false);
|
|
return;
|
|
}
|
|
@memcpy(this.buf[this.buf.len - write.len ..], write);
|
|
this.buf = this.buf[0 .. this.buf.len - write.len];
|
|
}
|
|
};
|
|
fn filterNames(comptime Rem: type, rem: *Rem, description: ?[]const u8, parent_in: ?*bun_test.DescribeScope) void {
|
|
const sep = " ";
|
|
rem.writeEnd(description orelse "");
|
|
var parent = parent_in;
|
|
while (parent) |scope| : (parent = scope.base.parent) {
|
|
if (scope.base.name == null) continue;
|
|
rem.writeEnd(sep);
|
|
rem.writeEnd(scope.base.name orelse "");
|
|
}
|
|
}
|
|
|
|
fn enqueueDescribeOrTestCallback(this: *ScopeFunctions, bunTest: *bun_test.BunTest, globalThis: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame, callback: ?jsc.JSValue, description: ?[]const u8, timeout: u32, callback_length: usize, line_no: u32) bun.JSError!void {
|
|
groupLog.begin(@src());
|
|
defer groupLog.end();
|
|
|
|
// only allow in collection phase
|
|
switch (bunTest.phase) {
|
|
.collection => {}, // ok
|
|
.execution => return globalThis.throw("Cannot call {}() inside a test. Call it inside describe() instead.", .{this}),
|
|
.done => return globalThis.throw("Cannot call {}() after the test run has completed", .{this}),
|
|
}
|
|
|
|
// handle test reporter agent for debugger
|
|
const vm = globalThis.bunVM();
|
|
var test_id_for_debugger: i32 = 0;
|
|
if (vm.debugger) |*debugger| {
|
|
if (debugger.test_reporter_agent.isEnabled()) {
|
|
const globals = struct {
|
|
var max_test_id_for_debugger: i32 = 0;
|
|
};
|
|
globals.max_test_id_for_debugger += 1;
|
|
var name = bun.String.init(description orelse "(unnamed)");
|
|
const parent = bunTest.collection.active_scope;
|
|
const parent_id = if (parent.base.test_id_for_debugger != 0) parent.base.test_id_for_debugger else -1;
|
|
debugger.test_reporter_agent.reportTestFound(callFrame, globals.max_test_id_for_debugger, &name, switch (this.mode) {
|
|
.describe => .describe,
|
|
.@"test" => .@"test",
|
|
}, parent_id);
|
|
test_id_for_debugger = globals.max_test_id_for_debugger;
|
|
}
|
|
}
|
|
const has_done_parameter = if (callback != null) callback_length >= 1 else false;
|
|
|
|
var base = this.cfg;
|
|
base.line_no = line_no;
|
|
base.test_id_for_debugger = test_id_for_debugger;
|
|
if (bun.jsc.Jest.Jest.runner) |runner| if (runner.concurrent) {
|
|
base.self_concurrent = true;
|
|
};
|
|
|
|
switch (this.mode) {
|
|
.describe => {
|
|
// Check line filter for describe blocks too
|
|
var matches_line_filter = true;
|
|
if (bunTest.reporter != null) {
|
|
const reporter = bunTest.reporter.?;
|
|
if (reporter.jest.hasTestLineFilter()) {
|
|
// Get the current file path
|
|
const file_path = if (vm.main.len > 0) vm.main else "";
|
|
|
|
// For describe blocks, we need to check if any line filter matches this file
|
|
// If there are no filters for this file, we proceed normally
|
|
// If there are filters for this file, we check if this describe block or its parents match
|
|
var parent_lines = std.ArrayList(u32).init(bunTest.gpa);
|
|
defer parent_lines.deinit();
|
|
|
|
var parent_scope = bunTest.collection.active_scope;
|
|
while (parent_scope.base.parent) |parent| {
|
|
if (parent.base.line_no > 0) {
|
|
bun.handleOom(parent_lines.append(parent.base.line_no));
|
|
}
|
|
parent_scope = parent;
|
|
}
|
|
|
|
matches_line_filter = reporter.jest.matchesLineFilter(file_path, line_no, parent_lines.items);
|
|
|
|
// If describe doesn't match but has a specific line filter for this file,
|
|
// still create it but mark as potentially filtered
|
|
if (!matches_line_filter) {
|
|
base.self_mode = .filtered_out;
|
|
}
|
|
}
|
|
}
|
|
|
|
const new_scope = try bunTest.collection.active_scope.appendDescribe(bunTest.gpa, description, base);
|
|
try bunTest.collection.enqueueDescribeCallback(new_scope, callback);
|
|
},
|
|
.@"test" => {
|
|
|
|
// check for filter match
|
|
var matches_filter = true;
|
|
if (bunTest.reporter) |reporter| if (reporter.jest.filter_regex) |filter_regex| {
|
|
groupLog.log("matches_filter begin", .{});
|
|
bun.assert(bunTest.collection.filter_buffer.items.len == 0);
|
|
defer bunTest.collection.filter_buffer.clearRetainingCapacity();
|
|
|
|
var len: Measure = .{ .len = 0 };
|
|
filterNames(Measure, &len, description, bunTest.collection.active_scope);
|
|
const slice = try bunTest.collection.filter_buffer.addManyAsSlice(len.len);
|
|
var rem: Write = .{ .buf = slice };
|
|
filterNames(Write, &rem, description, bunTest.collection.active_scope);
|
|
bun.debugAssert(rem.buf.len == 0);
|
|
|
|
const str = bun.String.fromBytes(bunTest.collection.filter_buffer.items);
|
|
groupLog.log("matches_filter \"{}\"", .{std.zig.fmtEscapes(bunTest.collection.filter_buffer.items)});
|
|
matches_filter = filter_regex.matches(str);
|
|
};
|
|
|
|
// Check line filter as well
|
|
if (matches_filter and bunTest.reporter != null) {
|
|
const reporter = bunTest.reporter.?;
|
|
if (reporter.jest.hasTestLineFilter()) {
|
|
// Get the current file path
|
|
const file_path = if (vm.main.len > 0) vm.main else "";
|
|
|
|
// Collect parent line numbers for describe blocks
|
|
var parent_lines = std.ArrayList(u32).init(bunTest.gpa);
|
|
defer parent_lines.deinit();
|
|
|
|
var parent_scope = bunTest.collection.active_scope;
|
|
while (parent_scope.base.parent) |parent| {
|
|
if (parent.base.line_no > 0) {
|
|
bun.handleOom(parent_lines.append(parent.base.line_no));
|
|
}
|
|
parent_scope = parent;
|
|
}
|
|
|
|
matches_filter = reporter.jest.matchesLineFilter(file_path, line_no, parent_lines.items);
|
|
}
|
|
}
|
|
|
|
if (!matches_filter) {
|
|
base.self_mode = .filtered_out;
|
|
}
|
|
|
|
bun.assert(!bunTest.collection.locked);
|
|
groupLog.log("enqueueTestCallback / {s} / in scope: {s}", .{ description orelse "(unnamed)", bunTest.collection.active_scope.base.name orelse "(unnamed)" });
|
|
|
|
_ = try bunTest.collection.active_scope.appendTest(bunTest.gpa, description, if (matches_filter) callback else null, .{
|
|
.has_done_parameter = has_done_parameter,
|
|
.timeout = timeout,
|
|
}, base);
|
|
},
|
|
}
|
|
}
|
|
|
|
fn genericIf(this: *ScopeFunctions, globalThis: *JSGlobalObject, callFrame: *CallFrame, conditional_cfg: bun_test.BaseScopeCfg, name: []const u8, invert: bool, fn_name: bun.String) bun.JSError!JSValue {
|
|
groupLog.begin(@src());
|
|
defer groupLog.end();
|
|
|
|
const condition = callFrame.argumentsAsArray(1)[0];
|
|
if (callFrame.arguments().len == 0) return globalThis.throw("Expected condition to be a boolean", .{});
|
|
const cond = condition.toBoolean();
|
|
if (cond != invert) {
|
|
return genericExtend(this, globalThis, conditional_cfg, name, fn_name);
|
|
} else {
|
|
return createBound(globalThis, this.mode, this.each, this.cfg, fn_name);
|
|
}
|
|
}
|
|
fn genericExtend(this: *ScopeFunctions, globalThis: *JSGlobalObject, cfg: bun_test.BaseScopeCfg, name: []const u8, fn_name: bun.String) bun.JSError!JSValue {
|
|
groupLog.begin(@src());
|
|
defer groupLog.end();
|
|
|
|
if (cfg.self_mode == .failing and this.mode == .describe) return globalThis.throw("Cannot {s} on {f}", .{ name, this });
|
|
if (cfg.self_only) try errorInCI(globalThis, ".only");
|
|
const extended = this.cfg.extend(cfg) orelse return globalThis.throw("Cannot {s} on {f}", .{ name, this });
|
|
return createBound(globalThis, this.mode, this.each, extended, fn_name);
|
|
}
|
|
|
|
fn errorInCI(globalThis: *jsc.JSGlobalObject, signature: []const u8) bun.JSError!void {
|
|
if (!bun.FeatureFlags.breaking_changes_1_3) return; // this is a breaking change for version 1.3
|
|
if (bun.detectCI()) |_| {
|
|
return globalThis.throwPretty("{s} is not allowed in CI environments.\nIf this is not a CI environment, set the environment variable CI=false to force allow.", .{signature});
|
|
}
|
|
}
|
|
|
|
const ParseArgumentsResult = struct {
|
|
description: ?[]const u8,
|
|
callback: ?jsc.JSValue,
|
|
options: struct {
|
|
timeout: u32 = 0,
|
|
retry: ?f64 = null,
|
|
repeats: ?f64 = null,
|
|
},
|
|
pub fn deinit(this: *ParseArgumentsResult, gpa: std.mem.Allocator) void {
|
|
if (this.description) |str| gpa.free(str);
|
|
}
|
|
};
|
|
pub const CallbackMode = enum { require, allow };
|
|
|
|
fn getDescription(gpa: std.mem.Allocator, globalThis: *jsc.JSGlobalObject, description: jsc.JSValue, signature: Signature) bun.JSError![]const u8 {
|
|
const is_valid_description =
|
|
description.isClass(globalThis) or
|
|
(description.isFunction() and !description.getName(globalThis).isEmpty()) or
|
|
description.isNumber() or
|
|
description.isString();
|
|
|
|
if (!is_valid_description) {
|
|
return globalThis.throwPretty("{s}() expects first argument to be a named class, named function, number, or string", .{signature});
|
|
}
|
|
|
|
if (description == .zero) {
|
|
return "";
|
|
}
|
|
|
|
if (description.isClass(globalThis)) {
|
|
const name_str = if ((try description.className(globalThis)).toSlice(gpa).length() == 0)
|
|
description.getName(globalThis).toSlice(gpa).slice()
|
|
else
|
|
(try description.className(globalThis)).toSlice(gpa).slice();
|
|
return try gpa.dupe(u8, name_str);
|
|
}
|
|
if (description.isFunction()) {
|
|
var slice = description.getName(globalThis).toSlice(gpa);
|
|
defer slice.deinit();
|
|
return try gpa.dupe(u8, slice.slice());
|
|
}
|
|
var slice = try description.toSlice(globalThis, gpa);
|
|
defer slice.deinit();
|
|
return try gpa.dupe(u8, slice.slice());
|
|
}
|
|
|
|
pub fn parseArguments(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame, signature: Signature, gpa: std.mem.Allocator, cfg: struct { callback: CallbackMode }) bun.JSError!ParseArgumentsResult {
|
|
var a1, var a2, var a3 = callframe.argumentsAsArray(3);
|
|
|
|
const len: enum { three, two, one, zero } = if (!a3.isUndefinedOrNull()) .three else if (!a2.isUndefinedOrNull()) .two else if (!a1.isUndefinedOrNull()) .one else .zero;
|
|
const DescriptionCallbackOptions = struct { description: JSValue = .js_undefined, callback: JSValue = .js_undefined, options: JSValue = .js_undefined };
|
|
const items: DescriptionCallbackOptions = switch (len) {
|
|
// description, callback(fn), options(!fn)
|
|
// description, options(!fn), callback(fn)
|
|
.three => if (a2.isFunction()) .{ .description = a1, .callback = a2, .options = a3 } else .{ .description = a1, .callback = a3, .options = a2 },
|
|
// description, callback(fn)
|
|
.two => .{ .description = a1, .callback = a2 },
|
|
// description
|
|
// callback(fn)
|
|
.one => if (a1.isFunction()) .{ .callback = a1 } else .{ .description = a1 },
|
|
.zero => .{},
|
|
};
|
|
const description, const callback, const options = .{ items.description, items.callback, items.options };
|
|
|
|
const result_callback: ?jsc.JSValue = if (cfg.callback != .require and callback.isUndefinedOrNull()) blk: {
|
|
break :blk null;
|
|
} else if (callback.isFunction()) blk: {
|
|
break :blk callback.withAsyncContextIfNeeded(globalThis);
|
|
} else {
|
|
return globalThis.throw("{s} expects a function as the second argument", .{signature});
|
|
};
|
|
|
|
var result: ParseArgumentsResult = .{
|
|
.description = null,
|
|
.callback = result_callback,
|
|
.options = .{},
|
|
};
|
|
errdefer result.deinit(gpa);
|
|
|
|
var timeout_option: ?f64 = null;
|
|
|
|
if (options.isNumber()) {
|
|
timeout_option = options.asNumber();
|
|
} else if (options.isFunction()) {
|
|
return globalThis.throw("{}() expects options to be a number or object, not a function", .{signature});
|
|
} else if (options.isObject()) {
|
|
if (try options.get(globalThis, "timeout")) |timeout| {
|
|
if (!timeout.isNumber()) {
|
|
return globalThis.throwPretty("{}() expects timeout to be a number", .{signature});
|
|
}
|
|
timeout_option = timeout.asNumber();
|
|
}
|
|
if (try options.get(globalThis, "retry")) |retries| {
|
|
if (!retries.isNumber()) {
|
|
return globalThis.throwPretty("{}() expects retry to be a number", .{signature});
|
|
}
|
|
result.options.retry = retries.asNumber();
|
|
}
|
|
if (try options.get(globalThis, "repeats")) |repeats| {
|
|
if (!repeats.isNumber()) {
|
|
return globalThis.throwPretty("{}() expects repeats to be a number", .{signature});
|
|
}
|
|
result.options.repeats = repeats.asNumber();
|
|
}
|
|
} else if (options.isUndefinedOrNull()) {
|
|
// no options
|
|
} else {
|
|
return globalThis.throw("{}() expects a number, object, or undefined as the third argument", .{signature});
|
|
}
|
|
|
|
result.description = if (description.isUndefinedOrNull()) null else try getDescription(gpa, globalThis, description, signature);
|
|
|
|
const default_timeout_ms: ?u32 = if (bun.jsc.Jest.Jest.runner) |runner| if (runner.default_timeout_ms != 0) runner.default_timeout_ms else null else null;
|
|
const override_timeout_ms: ?u32 = if (bun.jsc.Jest.Jest.runner) |runner| if (runner.default_timeout_override != std.math.maxInt(u32)) runner.default_timeout_override else null else null;
|
|
const timeout_option_ms: ?u32 = if (timeout_option) |timeout| std.math.lossyCast(u32, timeout) else null;
|
|
result.options.timeout = timeout_option_ms orelse override_timeout_ms orelse default_timeout_ms orelse 0;
|
|
|
|
return result;
|
|
}
|
|
|
|
pub const js = jsc.Codegen.JSScopeFunctions;
|
|
pub const toJS = js.toJS;
|
|
pub const fromJS = js.fromJS;
|
|
pub const fromJSDirect = js.fromJSDirect;
|
|
|
|
pub fn format(this: ScopeFunctions, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
|
try writer.print("{s}", .{@tagName(this.mode)});
|
|
if (this.cfg.self_concurrent) try writer.print(".concurrent", .{});
|
|
if (this.cfg.self_mode != .normal) try writer.print(".{s}", .{@tagName(this.cfg.self_mode)});
|
|
if (this.cfg.self_only) try writer.print(".only", .{});
|
|
if (this.each != .zero) try writer.print(".each()", .{});
|
|
}
|
|
|
|
pub fn finalize(
|
|
this: *ScopeFunctions,
|
|
) callconv(.C) void {
|
|
groupLog.begin(@src());
|
|
defer groupLog.end();
|
|
|
|
VirtualMachine.get().allocator.destroy(this);
|
|
}
|
|
|
|
pub fn createUnbound(globalThis: *JSGlobalObject, mode: Mode, each: jsc.JSValue, cfg: bun_test.BaseScopeCfg) JSValue {
|
|
groupLog.begin(@src());
|
|
defer groupLog.end();
|
|
|
|
var scope_functions = bun.handleOom(globalThis.bunVM().allocator.create(ScopeFunctions));
|
|
scope_functions.* = .{ .mode = mode, .cfg = cfg, .each = each };
|
|
|
|
const value = scope_functions.toJS(globalThis);
|
|
value.ensureStillAlive();
|
|
return value;
|
|
}
|
|
|
|
pub fn bind(value: JSValue, globalThis: *JSGlobalObject, name: *const bun.String) bun.JSError!JSValue {
|
|
const callFn = jsc.host_fn.NewFunction(globalThis, &name.toZigString(), 1, callAsFunction, false);
|
|
const bound = try callFn.bind(globalThis, value, name, 1, &.{});
|
|
try bound.setPrototypeDirect(value.getPrototype(globalThis), globalThis);
|
|
return bound;
|
|
}
|
|
|
|
pub fn createBound(globalThis: *JSGlobalObject, mode: Mode, each: jsc.JSValue, cfg: bun_test.BaseScopeCfg, name: bun.String) bun.JSError!JSValue {
|
|
groupLog.begin(@src());
|
|
defer groupLog.end();
|
|
|
|
const value = createUnbound(globalThis, mode, each, cfg);
|
|
return bind(value, globalThis, &name);
|
|
}
|
|
|
|
const bun = @import("bun");
|
|
const std = @import("std");
|
|
|
|
const jsc = bun.jsc;
|
|
const CallFrame = jsc.CallFrame;
|
|
const JSGlobalObject = jsc.JSGlobalObject;
|
|
const JSValue = jsc.JSValue;
|
|
const VirtualMachine = jsc.VirtualMachine;
|
|
const Strong = jsc.Strong.Deprecated;
|
|
|
|
const bun_test = jsc.Jest.bun_test;
|
|
const BunTest = bun_test.BunTest;
|
|
const ScopeFunctions = bun_test.ScopeFunctions;
|
|
const Signature = bun_test.js_fns.Signature;
|
|
const groupLog = bun_test.debug.group;
|