Compare commits

...

9 Commits

Author SHA1 Message Date
Jarred Sumner
832f06fef1 wip fix .only behavior 2024-10-04 23:59:20 -07:00
Jarred Sumner
59eb6abc44 Merge branch 'main' into jarred/describe 2024-10-04 22:29:02 -07:00
Jarred Sumner
4f31917b0b only run after* if any tests were run 2024-10-04 17:13:55 -07:00
Jarred Sumner
f81278a58d Fixes https://github.com/oven-sh/bun/issues/12250 2024-10-04 06:19:40 -07:00
Jarred Sumner
8ef6344271 Fixes #14135 2024-10-04 06:06:59 -07:00
Jarred Sumner
c60d090888 Run beforeAll later 2024-10-04 03:19:10 -07:00
Jarred-Sumner
d01fcb6cc4 bun run zig-format 2024-10-04 09:48:42 +00:00
Jarred Sumner
97a75b53f5 Add missing exception checks 2024-10-04 02:40:58 -07:00
Jarred Sumner
b4e81a4e53 [bun:test] Remove waitForPromise in describe(label, async () => {}) 2024-10-04 02:23:58 -07:00
21 changed files with 723 additions and 224 deletions

View File

@@ -4178,6 +4178,10 @@ GlobalObject::PromiseFunctions GlobalObject::promiseHandlerID(Zig::FFIFunction h
return GlobalObject::PromiseFunctions::Bun__onResolveEntryPointResult;
} else if (handler == Bun__onRejectEntryPointResult) {
return GlobalObject::PromiseFunctions::Bun__onRejectEntryPointResult;
} else if (handler == DescribeScope__onResolve) {
return GlobalObject::PromiseFunctions::DescribeScope__onResolve;
} else if (handler == DescribeScope__onReject) {
return GlobalObject::PromiseFunctions::DescribeScope__onReject;
} else {
RELEASE_ASSERT_NOT_REACHED();
}

View File

@@ -354,8 +354,10 @@ public:
Bun__BodyValueBufferer__onResolveStream,
Bun__onResolveEntryPointResult,
Bun__onRejectEntryPointResult,
DescribeScope__onResolve,
DescribeScope__onReject,
};
static constexpr size_t promiseFunctionsSize = 24;
static constexpr size_t promiseFunctionsSize = 26;
static PromiseFunctions promiseHandlerID(SYSV_ABI EncodedJSValue (*handler)(JSC__JSGlobalObject* arg0, JSC__CallFrame* arg1));

View File

@@ -3690,6 +3690,13 @@ JSC__JSValue JSC__JSValue__createObject2(JSC__JSGlobalObject* globalObject, cons
return JSC::JSValue::encode(object);
}
extern "C" JSC__JSValue JSC__JSValue__bind(JSC__JSValue JSValue0, JSC__JSGlobalObject* globalObject, JSC__JSValue this_value, JSC__JSValue* args, size_t arg_count)
{
auto& vm = globalObject->vm();
JSValue value = JSC::JSValue::decode(JSValue0);
return JSC::JSValue::encode(JSC::JSBoundFunction::create(vm, globalObject, value.getObject(), JSValue::decode(this_value), ArgList(args, arg_count), arg_count, nullptr));
}
JSC__JSValue JSC__JSValue__getIfPropertyExistsImpl(JSC__JSValue JSValue0,
JSC__JSGlobalObject* globalObject,
const unsigned char* arg1, uint32_t arg2)

View File

@@ -4207,6 +4207,11 @@ pub const JSValue = enum(JSValueReprInt) {
JSC__JSValue__putBunString(value, global, key, result);
}
extern fn JSC__JSValue__bind(value: JSValue, global: *JSGlobalObject, this_value: JSC.JSValue, args: [*]const JSC.JSValue, arg_count: usize) JSC.JSValue;
pub fn bind(value: JSValue, global: *JSGlobalObject, this_value: JSC.JSValue, args: []const JSC.JSValue) JSC.JSValue {
return JSC__JSValue__bind(value, global, this_value, args.ptr, args.len);
}
pub fn put(value: JSValue, global: *JSGlobalObject, key: anytype, result: JSC.JSValue) void {
const Key = @TypeOf(key);
if (comptime @typeInfo(Key) == .Pointer) {

View File

@@ -790,6 +790,9 @@ BUN_DECLARE_HOST_FUNCTION(Bun__HTTPRequestContext__onRejectStream);
BUN_DECLARE_HOST_FUNCTION(Bun__HTTPRequestContext__onResolve);
BUN_DECLARE_HOST_FUNCTION(Bun__HTTPRequestContext__onResolveStream);
BUN_DECLARE_HOST_FUNCTION(DescribeScope__onResolve);
BUN_DECLARE_HOST_FUNCTION(DescribeScope__onReject);
#endif
#ifdef __cplusplus

View File

@@ -1560,7 +1560,7 @@ pub const VirtualMachine = struct {
this.eventLoop().tick();
}
pub fn waitFor(this: *VirtualMachine, cond: *bool) void {
pub fn waitFor(this: *VirtualMachine, cond: anytype) void {
while (!cond.*) {
this.eventLoop().tick();

View File

@@ -702,6 +702,10 @@ pub const Expect = struct {
break;
}
}
if (globalThis.hasException()) {
return .zero;
}
} else if (list_value.isIterable(globalThis)) {
var expected_entry = ExpectedEntry{
.globalThis = globalThis,
@@ -728,6 +732,10 @@ pub const Expect = struct {
return .zero;
}
if (globalThis.hasException()) {
return .zero;
}
if (not) pass = !pass;
if (pass) return .undefined;
@@ -1496,6 +1504,10 @@ pub const Expect = struct {
const truthy = value.toBooleanSlow(globalThis);
if (truthy) pass = true;
if (globalThis.hasException()) {
return .zero;
}
if (not) pass = !pass;
if (pass) return .undefined;
@@ -1690,6 +1702,9 @@ pub const Expect = struct {
const not = this.flags.not;
var pass = value.jestDeepEquals(expected, globalThis);
if (globalThis.hasException()) {
return .zero;
}
if (not) pass = !pass;
if (pass) return .undefined;
@@ -1732,6 +1747,9 @@ pub const Expect = struct {
const not = this.flags.not;
var pass = value.jestStrictDeepEquals(expected, globalThis);
if (globalThis.hasException()) {
return .zero;
}
if (not) pass = !pass;
if (pass) return .undefined;
@@ -1785,11 +1803,17 @@ pub const Expect = struct {
if (pass) {
received_property = value.getIfPropertyExistsFromPath(globalThis, expected_property_path);
if (globalThis.hasException()) {
return .zero;
}
pass = !received_property.isEmpty();
}
if (pass and expected_property != null) {
pass = received_property.jestDeepEquals(expected_property.?, globalThis);
if (globalThis.hasException()) {
return .zero;
}
}
if (not) pass = !pass;
@@ -2724,6 +2748,9 @@ pub const Expect = struct {
const prop_matchers = _prop_matchers;
if (!value.jestDeepMatch(prop_matchers, globalThis, true)) {
if (globalThis.hasException()) {
return .zero;
}
// TODO: print diff with properties from propertyMatchers
const signature = comptime getSignature("toMatchSnapshot", "<green>propertyMatchers<r>", false);
const fmt = signature ++ "\n\nExpected <green>propertyMatchers<r> to match properties from received object" ++
@@ -2733,6 +2760,9 @@ pub const Expect = struct {
globalThis.throwPretty(fmt, .{value.toFmt(&formatter)});
return .zero;
}
if (globalThis.hasException()) {
return .zero;
}
}
const result = Jest.runner.?.snapshots.getOrPut(this, value, hint.slice(), globalThis) catch |err| {
@@ -4169,6 +4199,9 @@ pub const Expect = struct {
const property_matchers = args[0];
var pass = received_object.jestDeepMatch(property_matchers, globalThis, true);
if (globalThis.hasException()) {
return .zero;
}
if (not) pass = !pass;
if (pass) return .undefined;
@@ -4229,6 +4262,8 @@ pub const Expect = struct {
if (!callArg.jestDeepEquals(arguments[callItr.i - 1], globalThis)) {
match = false;
break;
} else if (globalThis.hasException()) {
return .zero;
}
}
@@ -4239,6 +4274,10 @@ pub const Expect = struct {
}
}
if (globalThis.hasException()) {
return .zero;
}
const not = this.flags.not;
if (not) pass = !pass;
if (pass) return .undefined;
@@ -4298,6 +4337,10 @@ pub const Expect = struct {
}
}
if (globalThis.hasException()) {
return .zero;
}
const not = this.flags.not;
if (not) pass = !pass;
if (pass) return .undefined;
@@ -4366,6 +4409,10 @@ pub const Expect = struct {
}
}
if (globalThis.hasException()) {
return .zero;
}
const not = this.flags.not;
if (not) pass = !pass;
if (pass) return .undefined;

View File

@@ -50,7 +50,7 @@ const is_bindgen: bool = false;
const ArrayIdentityContext = bun.ArrayIdentityContext;
pub const Tag = enum(u3) {
pub const Tag = enum {
pass,
fail,
only,
@@ -73,13 +73,15 @@ pub const TestRunner = struct {
drainer: JSC.AnyTask = undefined,
queue: std.fifo.LinearFifo(*TestRunnerTask, .{ .Dynamic = {} }) = std.fifo.LinearFifo(*TestRunnerTask, .{ .Dynamic = {} }).init(default_allocator),
describe_queue: std.fifo.LinearFifo(*DescribeScope, .{ .Dynamic = {} }) = std.fifo.LinearFifo(*DescribeScope, .{ .Dynamic = {} }).init(default_allocator),
has_pending_tests: bool = false,
pending_test: ?*TestRunnerTask = null,
pending_describe: ?*DescribeScope = null,
snapshots: Snapshots,
default_timeout_ms: u32,
tests_not_run_due_to_before_scope_error: usize = 0,
// from `setDefaultTimeout() or jest.setTimeout()`
default_timeout_override: u32 = std.math.maxInt(u32),
@@ -148,8 +150,11 @@ pub const TestRunner = struct {
pub fn runNextTest(this: *TestRunner) void {
this.has_pending_tests = false;
this.pending_test = null;
this.pending_describe = null;
const vm = JSC.VirtualMachine.get();
vm.onUnhandledRejectionCtx = null;
vm.auto_killer.clear();
vm.auto_killer.disable();
@@ -157,8 +162,22 @@ pub const TestRunner = struct {
vm.wakeup();
}
pub fn drain(this: *TestRunner) void {
if (this.pending_test != null) return;
pub fn drain(this: *TestRunner, vm: *VirtualMachine, globalObject: *JSC.JSGlobalObject) void {
if (this.pending_test != null or this.pending_describe != null) return;
if (this.describe_queue.readItem()) |scope| {
this.pending_describe = scope;
this.has_pending_tests = true;
switch (scope.dequeue(vm, globalObject)) {
.Error, .Sync => {
this.pending_describe = null;
this.has_pending_tests = false;
},
.Async => {},
}
return;
}
if (this.queue.readItem()) |task| {
this.pending_test = task;
@@ -175,6 +194,7 @@ pub const TestRunner = struct {
return;
}
this.only = true;
debug(".only was set!", .{});
const list = this.queue.readableSlice(0);
for (list) |task| {
@@ -605,6 +625,15 @@ pub const TestScope = struct {
actual: u32 = 0,
};
pub fn shouldEvaluateScope(this: *const TestScope, is_only_set: bool) bool {
return switch (this.tag) {
.only => true,
.skip => false,
.todo => Jest.runner.?.test_options.run_todo,
else => this.parent.shouldEvaluateScope(is_only_set),
};
}
pub fn call(globalThis: *JSGlobalObject, callframe: *CallFrame) JSValue {
return createScope(globalThis, callframe, "test()", true, .pass);
}
@@ -826,41 +855,115 @@ pub const DescribeScope = struct {
afterEach: std.ArrayListUnmanaged(JSValue) = .{},
afterAll: std.ArrayListUnmanaged(JSValue) = .{},
test_id_start: TestRunner.Test.ID = 0,
test_id_len: TestRunner.Test.ID = 0,
tests: std.ArrayListUnmanaged(TestScope) = .{},
pending_tests: std.DynamicBitSetUnmanaged = .{},
file_id: TestRunner.File.ID,
current_test_id: TestRunner.Test.ID = 0,
value: JSValue = .zero,
done: bool = false,
skip_count: u32 = 0,
tag: Tag = .pass,
function_value: JSC.Strong = .{},
child_describes_to_run: u32 = 0,
child_describes_to_enqueue: u32 = 0,
ref: JSC.Ref = .{},
flags: Flags = .{},
fn isWithinOnlyScope(this: *const DescribeScope) bool {
if (this.tag == .only) return true;
if (this.parent != null) return this.parent.?.isWithinOnlyScope();
pub const Flags = packed struct { done: bool = false, any_tests_ran: bool = false, before_scope_failed: bool = false, any_tests_scheduled: bool = false };
const Synchronousness = enum {
Sync,
Async,
Error,
};
pub fn setAnyTestsRan(this: *DescribeScope) void {
if (this.flags.any_tests_ran) return;
this.flags.any_tests_ran = true;
var scope = this;
while (scope.parent) |parent| {
parent.flags.any_tests_ran = true;
scope = parent;
}
}
pub fn dequeue(this: *DescribeScope, vm: *VirtualMachine, globalObject: *JSGlobalObject) Synchronousness {
const function = this.function_value.swap();
defer this.function_value.deinit();
return this.run(vm, globalObject, function, &.{});
}
pub fn shouldEvaluateScope(this: *const DescribeScope, is_only_set: bool) bool {
return switch (this.tag) {
.skip => false,
.todo => Jest.runner.?.test_options.run_todo,
.only => is_only_set,
else => if (this.parent) |parent|
parent.shouldEvaluateScope(is_only_set)
else
!is_only_set,
};
}
pub fn anyOnly(this: *const DescribeScope) bool {
var scope: ?*const DescribeScope = this;
while (scope) |s| {
if (s.tag == .only) {
return true;
}
scope = s.parent;
}
return false;
}
fn isWithinSkipScope(this: *const DescribeScope) bool {
if (this.tag == .skip) return true;
if (this.parent != null) return this.parent.?.isWithinSkipScope();
return false;
const AsyncDescribe = struct {
describe: *DescribeScope,
promise: JSC.Strong = .{},
pub usingnamespace bun.New(AsyncDescribe);
pub fn deinit(this: *AsyncDescribe) void {
this.promise.deinit();
this.destroy();
}
};
fn onResumeDescribe(this: *DescribeScope, vm: *VirtualMachine) void {
bun.debugAssert(Jest.runner.?.pending_describe == this);
this.ref.unref(vm);
Jest.runner.?.pending_describe = null;
Jest.runner.?.has_pending_tests = false;
this.pop();
}
fn isWithinTodoScope(this: *const DescribeScope) bool {
if (this.tag == .todo) return true;
if (this.parent != null) return this.parent.?.isWithinTodoScope();
return false;
}
pub fn onReject(globalThis: *JSGlobalObject, callframe: *CallFrame) JSValue {
debug("onReject", .{});
const arguments = callframe.arguments(2);
const err = arguments.ptr[0];
const vm = globalThis.bunVM();
const prev_ctx = vm.onUnhandledRejectionCtx;
defer vm.onUnhandledRejectionCtx = prev_ctx;
vm.onUnhandledRejectionCtx = null;
var task: *AsyncDescribe = arguments.ptr[1].asPromisePtr(AsyncDescribe);
defer task.deinit();
onResumeDescribe(task.describe, vm);
_ = vm.uncaughtException(globalThis, err, true);
vm.autoGarbageCollect();
pub fn shouldEvaluateScope(this: *const DescribeScope) bool {
if (this.tag == .skip or
this.tag == .todo) return false;
if (Jest.runner.?.only and this.tag == .only) return true;
if (this.parent != null) return this.parent.?.shouldEvaluateScope();
return true;
return JSValue.jsUndefined();
}
const jsOnReject = JSC.toJSHostFunction(onReject);
pub fn onResolve(globalThis: *JSGlobalObject, callframe: *CallFrame) JSValue {
debug("onResolve", .{});
const arguments = callframe.arguments(2);
const task: *AsyncDescribe = arguments.ptr[1].asPromisePtr(AsyncDescribe);
defer task.deinit();
task.describe.runTests(globalThis);
const vm = globalThis.bunVM();
onResumeDescribe(task.describe, vm);
vm.autoGarbageCollect();
return JSValue.jsUndefined();
}
const jsOnResolve = JSC.toJSHostFunction(onResolve);
pub fn push(new: *DescribeScope) void {
if (comptime is_bindgen) return;
@@ -935,7 +1038,7 @@ pub const DescribeScope = struct {
_ = ctx.bunVM().uncaughtException(ctx.bunVM().global, err, true);
}
}
scope.done = true;
scope.flags.done = true;
}
return JSValue.jsUndefined();
@@ -946,7 +1049,7 @@ pub const DescribeScope = struct {
pub const beforeAll = createCallback(.beforeAll);
pub const beforeEach = createCallback(.beforeEach);
pub fn execCallback(this: *DescribeScope, globalObject: *JSGlobalObject, comptime hook: LifecycleHook) ?JSValue {
pub fn execCallback(this: *DescribeScope, globalObject: *JSGlobalObject, ran_any_callbacks: *bool, comptime hook: LifecycleHook) ?JSValue {
var hooks = &@field(this, @tagName(hook));
defer {
if (comptime hook == .beforeAll or hook == .afterAll) {
@@ -964,12 +1067,14 @@ pub const DescribeScope = struct {
cb.unprotect();
}
}
debug("{s}()", .{@tagName(hook)});
const vm = VirtualMachine.get();
ran_any_callbacks.* = true;
var result: JSValue = switch (cb.getLength(globalObject)) {
0 => callJSFunctionForTestRunner(vm, globalObject, cb, &.{}),
else => brk: {
this.done = false;
this.flags.done = false;
const done_func = JSC.NewFunctionWithData(
globalObject,
ZigString.static("done"),
@@ -982,7 +1087,7 @@ pub const DescribeScope = struct {
if (result.toError()) |err| {
return err;
}
vm.waitFor(&this.done);
vm.waitFor(&this.flags.done);
break :brk result;
},
};
@@ -1042,32 +1147,71 @@ pub const DescribeScope = struct {
return null;
}
fn runBeforeCallbacks(this: *DescribeScope, globalObject: *JSGlobalObject, comptime hook: LifecycleHook) ?JSValue {
fn runBeforeCallbacks(this: *DescribeScope, globalObject: *JSGlobalObject, ran_any_callbacks: *bool, comptime hook: LifecycleHook) ?JSValue {
if (this.parent) |scope| {
if (scope.runBeforeCallbacks(globalObject, hook)) |err| {
if (scope.runBeforeCallbacks(globalObject, ran_any_callbacks, hook)) |err| {
return err;
}
}
return this.execCallback(globalObject, hook);
return this.execCallback(globalObject, ran_any_callbacks, hook);
}
pub fn runCallback(this: *DescribeScope, globalObject: *JSGlobalObject, comptime hook: LifecycleHook) ?JSValue {
if (comptime hook == .afterAll or hook == .afterEach) {
var parent: ?*DescribeScope = this;
while (parent) |scope| {
if (scope.execCallback(globalObject, hook)) |err| {
return err;
}
parent = scope.parent;
pub fn runCallbacksAndHandleRejections(this: *DescribeScope, globalObject: *JSGlobalObject, comptime hook: LifecycleHook) void {
var ran_any_callbacks = false;
if (this.runCallback(globalObject, &ran_any_callbacks, hook)) |err| {
_ = globalObject.bunVM().uncaughtException(globalObject, err, true);
if (comptime hook == .beforeAll or hook == .beforeEach) {
this.flags.before_scope_failed = true;
}
}
if (runGlobalCallbacks(globalObject, hook)) |err| {
return err;
if (ran_any_callbacks) {
var prev_counter: usize = undefined;
if (comptime hook == .beforeAll or hook == .beforeEach) {
prev_counter = globalObject.bunVM().unhandled_error_counter;
}
globalObject.handleRejectedPromises();
if (comptime hook == .beforeAll or hook == .beforeEach) {
if (globalObject.bunVM().unhandled_error_counter > prev_counter) {
this.flags.before_scope_failed = true;
}
}
}
}
fn runCallback(this: *DescribeScope, globalObject: *JSGlobalObject, ran_any_callbacks: *bool, comptime hook: LifecycleHook) ?JSValue {
if (comptime hook == .afterAll or hook == .afterEach) {
var parent: ?*DescribeScope = this;
if (hook == .afterAll) {
while (parent) |scope| {
if (scope.child_describes_to_run == 0 and scope.pending_tests.findFirstSet() == null) {
if (scope.flags.any_tests_ran) {
if (scope.execCallback(globalObject, ran_any_callbacks, hook)) |err| {
return err;
}
}
}
parent = scope.parent;
}
} else {
while (parent) |scope| {
if (scope.execCallback(globalObject, ran_any_callbacks, hook)) |err| {
return err;
}
parent = scope.parent;
}
}
}
if (hook != .afterAll) {
if (runGlobalCallbacks(globalObject, hook)) |err| {
return err;
}
}
if (comptime hook == .beforeAll or hook == .beforeEach) {
if (this.runBeforeCallbacks(globalObject, hook)) |err| {
if (this.runBeforeCallbacks(globalObject, ran_any_callbacks, hook)) |err| {
return err;
}
}
@@ -1107,43 +1251,69 @@ pub const DescribeScope = struct {
return createIfScope(globalThis, callframe, "describe.todoIf()", "todoIf", DescribeScope, .todo);
}
pub fn run(this: *DescribeScope, globalObject: *JSGlobalObject, callback: JSValue, args: []const JSValue) JSValue {
pub fn run(this: *DescribeScope, vm: *VirtualMachine, globalObject: *JSGlobalObject, callback: JSValue, args: []const JSValue) Synchronousness {
if (comptime is_bindgen) return undefined;
callback.protect();
defer callback.unprotect();
this.push();
defer this.pop();
debug("describe({})", .{bun.fmt.QuotedFormatter{ .text = this.label }});
if (callback == .zero) {
this.runTests(globalObject);
return .undefined;
}
{
JSC.markBinding(@src());
var result = callJSFunctionForTestRunner(VirtualMachine.get(), globalObject, callback, args);
if (result.asAnyPromise()) |prom| {
globalObject.bunVM().waitForPromise(prom);
switch (prom.status(globalObject.ptr().vm())) {
.fulfilled => {},
else => {
_ = globalObject.bunVM().unhandledRejection(globalObject, prom.result(globalObject.ptr().vm()), prom.asValue(globalObject));
return .undefined;
},
}
} else if (result.toError()) |err| {
_ = globalObject.bunVM().uncaughtException(globalObject, err, true);
return .undefined;
active = this;
var is_async = false;
defer {
if (!is_async) {
this.pop();
this.ref.unref(vm);
}
}
this.runTests(globalObject);
return .undefined;
debug("describe({})", .{bun.fmt.QuotedFormatter{ .text = this.label }});
if (callback != .zero) {
JSC.markBinding(@src());
var result = callJSFunctionForTestRunner(vm, globalObject, callback, args);
if (result.asAnyPromise()) |prom| {
switch (prom.status(globalObject.vm())) {
.fulfilled => {},
.pending => {
is_async = true;
const task = AsyncDescribe.new(.{ .describe = this, .promise = JSC.Strong.create(result, globalObject) });
result.then(
globalObject,
task,
jsOnResolve,
jsOnReject,
);
return .Async;
},
else => {
_ = vm.unhandledRejection(globalObject, prom.result(globalObject.ptr().vm()), prom.asValue(globalObject));
return .Sync;
},
}
} else if (result.toError()) |err| {
_ = vm.uncaughtException(globalObject, err, true);
return .Error;
}
}
if (this.child_describes_to_enqueue == 0 and !this.flags.any_tests_scheduled) {
this.runTests(globalObject);
}
return .Sync;
}
pub fn runTests(this: *DescribeScope, globalObject: *JSGlobalObject) void {
{
if (this.parent) |scope| {
if (!scope.flags.any_tests_scheduled and scope.child_describes_to_enqueue == 0) {
scope.runTests(globalObject);
}
scope.child_describes_to_enqueue -|= 1;
}
}
this.flags.any_tests_scheduled = true;
// Step 1. Initialize the test block
globalObject.clearTerminationException();
@@ -1152,6 +1322,7 @@ pub const DescribeScope = struct {
const tests: []TestScope = this.tests.items;
const end = @as(TestRunner.Test.ID, @truncate(tests.len));
this.pending_tests = std.DynamicBitSetUnmanaged.initFull(allocator, end) catch unreachable;
const vm = globalObject.bunVM();
// Step 2. Update the runner with the count of how many tests we have for this block
if (end > 0) this.test_id_start = Jest.runner.?.addTestCount(end);
@@ -1160,17 +1331,7 @@ pub const DescribeScope = struct {
var i: TestRunner.Test.ID = 0;
if (this.shouldEvaluateScope()) {
if (this.runCallback(globalObject, .beforeAll)) |err| {
_ = globalObject.bunVM().uncaughtException(globalObject, err, true);
while (i < end) {
Jest.runner.?.reportFailure(i + this.test_id_start, source.path.text, tests[i].label, 0, 0, this);
i += 1;
}
this.tests.clearAndFree(allocator);
this.pending_tests.deinit(allocator);
return;
}
if (this.shouldEvaluateScope(Jest.runner.?.only)) {
if (end == 0) {
var runner = allocator.create(TestRunnerTask) catch unreachable;
runner.* = .{
@@ -1179,7 +1340,7 @@ pub const DescribeScope = struct {
.globalThis = globalObject,
.source_file_path = source.path.text,
};
runner.ref.ref(globalObject.bunVM());
runner.ref.ref(vm);
Jest.runner.?.enqueue(runner);
return;
@@ -1194,7 +1355,7 @@ pub const DescribeScope = struct {
.globalThis = globalObject,
.source_file_path = source.path.text,
};
runner.ref.ref(globalObject.bunVM());
runner.ref.ref(vm);
Jest.runner.?.enqueue(runner);
}
@@ -1207,21 +1368,24 @@ pub const DescribeScope = struct {
globalThis.bunVM().onUnhandledRejectionCtx = null;
if (!skipped) {
if (this.runCallback(globalThis, .afterEach)) |err| {
_ = globalThis.bunVM().uncaughtException(globalThis, err, true);
}
this.setAnyTestsRan();
this.runCallbacksAndHandleRejections(globalThis, .afterEach);
}
if (this.pending_tests.findFirstSet() != null) {
return;
}
if (this.shouldEvaluateScope()) {
if (this.child_describes_to_run == 0) {
if (this.parent) |parent| {
parent.child_describes_to_run -|= 1;
}
}
if (this.child_describes_to_run == 0) {
// Run the afterAll callbacks, in reverse order
// unless there were no tests for this scope
if (this.execCallback(globalThis, .afterAll)) |err| {
_ = globalThis.bunVM().uncaughtException(globalThis, err, true);
}
this.runCallbacksAndHandleRejections(globalThis, .afterAll);
}
this.pending_tests.deinit(getAllocator(globalThis));
@@ -1245,6 +1409,10 @@ pub const DescribeScope = struct {
// // }
// }
comptime {
@export(jsOnResolve, .{ .name = "DescribeScope__onResolve" });
@export(jsOnReject, .{ .name = "DescribeScope__onReject" });
}
};
pub fn wrapTestFunction(comptime name: []const u8, comptime func: anytype) DescribeScope.CallbackFn {
@@ -1373,19 +1541,32 @@ pub const TestRunnerTask = struct {
jsc_vm.last_reported_error_for_dedupe = .zero;
const test_id = this.test_id;
const is_only_set = Jest.runner.?.only;
if (test_id == std.math.maxInt(TestRunner.Test.ID)) {
jsc_vm.onUnhandledRejectionCtx = null;
// Run beforeAll even if there were no tests for this scope.
if (describe.child_describes_to_run == 0 and
describe.pending_tests.findFirstSet() == null and
// jk maybe not
describe.shouldEvaluateScope(is_only_set))
{
describe.runCallbacksAndHandleRejections(globalThis, .beforeAll);
}
describe.onTestComplete(globalThis, test_id, true);
Jest.runner.?.runNextTest();
this.deinit();
return false;
}
var test_: TestScope = this.describe.tests.items[test_id];
describe.current_test_id = test_id;
var test_: TestScope = describe.tests.items[test_id];
if (test_.func == .zero or !describe.shouldEvaluateScope() or (test_.tag != .only and Jest.runner.?.only)) {
const tag = if (!describe.shouldEvaluateScope()) describe.tag else test_.tag;
if (test_.func == .zero or !test_.shouldEvaluateScope(is_only_set) or describe.flags.before_scope_failed) {
describe.current_test_id = test_id;
Jest.runner.?.tests_not_run_due_to_before_scope_error += @as(usize, @intFromBool(describe.flags.before_scope_failed));
const tag = if (!describe.shouldEvaluateScope(is_only_set)) describe.tag else test_.tag;
switch (tag) {
.todo => {
this.processTestResult(globalThis, .{ .todo = {} }, test_, test_id, describe);
@@ -1399,26 +1580,33 @@ pub const TestRunnerTask = struct {
return false;
}
jsc_vm.onUnhandledRejectionCtx = this;
jsc_vm.onUnhandledRejection = onUnhandledRejection;
if (this.needs_before_each) {
this.needs_before_each = false;
const label = test_.label;
if (this.describe.runCallback(globalThis, .beforeEach)) |err| {
_ = jsc_vm.uncaughtException(globalThis, err, true);
Jest.runner.?.reportFailure(test_id, this.source_file_path, label, 0, 0, this.describe);
jsc_vm.onUnhandledRejectionCtx = null;
globalThis.handleRejectedPromises();
describe.runCallbacksAndHandleRejections(globalThis, .beforeAll);
if (describe.flags.before_scope_failed) {
this.deinit();
Jest.runner.?.tests_not_run_due_to_before_scope_error += 1;
return false;
}
describe.runCallbacksAndHandleRejections(globalThis, .beforeEach);
if (describe.flags.before_scope_failed) {
this.deinit();
Jest.runner.?.tests_not_run_due_to_before_scope_error += 1;
return false;
}
}
jsc_vm.onUnhandledRejectionCtx = this;
jsc_vm.onUnhandledRejection = onUnhandledRejection;
describe.current_test_id = test_id;
this.sync_state = .pending;
jsc_vm.auto_killer.enable();
var result = TestScope.run(&test_, this);
if (this.describe.tests.items.len > test_id) {
this.describe.tests.items[test_id].timeout_millis = test_.timeout_millis;
if (describe.tests.items.len > test_id) {
describe.tests.items[test_id].timeout_millis = test_.timeout_millis;
}
// rejected promises should fail the test
@@ -1768,18 +1956,19 @@ inline fn createScope(
else
(description.toSlice(globalThis, allocator).cloneIfNeeded(allocator) catch unreachable).slice();
var tag_to_use = tag;
if (tag_to_use == .only or parent.tag == .only) {
if (tag == .only) {
Jest.runner.?.setOnly();
tag_to_use = .only;
} else if (is_test and Jest.runner.?.only and parent.tag != .only) {
} else if (is_test and Jest.runner.?.only and !parent.anyOnly()) {
// Silently skip non-only tests
return .undefined;
}
var is_skip = tag == .skip or
(tag == .todo and (function == .zero or !Jest.runner.?.run_todo)) or
(tag != .only and Jest.runner.?.only and parent.tag != .only);
var is_skip = switch (tag) {
.skip => true,
.todo => !Jest.runner.?.test_options.run_todo,
.only => false,
else => !parent.shouldEvaluateScope(Jest.runner.?.only),
};
if (is_test) {
if (!is_skip) {
@@ -1791,16 +1980,16 @@ inline fn createScope(
const str = bun.String.fromBytes(buffer.toOwnedSliceLeaky());
is_skip = !regex.matches(str);
if (is_skip) {
tag_to_use = .skip;
// Silently skip non-matching tests
return .undefined;
}
}
}
if (is_skip) {
parent.skip_count += 1;
function.unprotect();
} else {
function.protect();
if (comptime is_test) {
if (!is_skip) {
function.protect();
}
}
const func_params_length = function.getLength(globalThis);
@@ -1815,22 +2004,26 @@ inline fn createScope(
parent.tests.append(allocator, TestScope{
.label = label,
.parent = parent,
.tag = tag_to_use,
.tag = tag,
.func = if (is_skip) .zero else function,
.func_arg = function_args,
.func_has_callback = has_callback,
.timeout_millis = timeout_ms,
}) catch unreachable;
} else {
var scope = allocator.create(DescribeScope) catch unreachable;
const scope = allocator.create(DescribeScope) catch unreachable;
scope.* = .{
.label = label,
.parent = parent,
.file_id = parent.file_id,
.tag = tag_to_use,
.tag = tag,
.function_value = JSC.Strong.create(function, globalThis),
};
parent.child_describes_to_run +|= 1;
parent.child_describes_to_enqueue +|= 1;
return scope.run(globalThis, function, &.{});
Jest.runner.?.describe_queue.writeItem(scope) catch unreachable;
scope.ref.ref(globalThis.bunVM());
}
return this;
@@ -2028,6 +2221,15 @@ fn eachBind(
return .undefined;
}
const tag = parent.tag;
if (tag == .only) {
Jest.runner.?.setOnly();
} else if (each_data.is_test and Jest.runner.?.only and !parent.anyOnly()) {
// Silently skip non-only tests
return .undefined;
}
var iter = array.arrayIterator(globalThis);
var test_idx: usize = 0;
@@ -2072,54 +2274,52 @@ fn eachBind(
(description.toSlice(globalThis, allocator).cloneIfNeeded(allocator) catch unreachable).slice();
const formattedLabel = formatLabel(globalThis, label, function_args, test_idx) catch return .zero;
const tag = parent.tag;
var is_skip = switch (tag) {
.skip => true,
.todo => !Jest.runner.?.test_options.run_todo,
.only => false,
else => !parent.shouldEvaluateScope(Jest.runner.?.only),
};
if (tag == .only) {
Jest.runner.?.setOnly();
}
var is_skip = tag == .skip or
(tag == .todo and (function == .zero or !Jest.runner.?.run_todo)) or
(tag != .only and Jest.runner.?.only and parent.tag != .only);
if (Jest.runner.?.filter_regex) |regex| {
var buffer: bun.MutableString = Jest.runner.?.filter_buffer;
buffer.reset();
appendParentLabel(&buffer, parent) catch @panic("Bun ran out of memory while filtering tests");
buffer.append(formattedLabel) catch unreachable;
const str = bun.String.fromBytes(buffer.toOwnedSliceLeaky());
is_skip = !regex.matches(str);
}
if (is_skip) {
parent.skip_count += 1;
function.unprotect();
} else if (each_data.is_test) {
if (Jest.runner.?.only and tag != .only) {
return .undefined;
} else {
function.protect();
parent.tests.append(allocator, TestScope{
.label = formattedLabel,
.parent = parent,
.tag = tag,
.func = function,
.func_arg = function_args,
.func_has_callback = has_callback_function,
.timeout_millis = timeout_ms,
}) catch unreachable;
if (!is_skip) {
if (Jest.runner.?.filter_regex) |regex| {
var buffer: bun.MutableString = Jest.runner.?.filter_buffer;
buffer.reset();
appendParentLabel(&buffer, parent) catch @panic("Bun ran out of memory while filtering tests");
buffer.append(formattedLabel) catch unreachable;
const str = bun.String.fromBytes(buffer.toOwnedSliceLeaky());
is_skip = !regex.matches(str);
}
}
if (is_skip and each_data.is_test) {
// Silently skip non-matching tests
// a describe.only could happen as a child of this describe scope,
// so we cannot skip those here.
allocator.free(function_args);
} else if (each_data.is_test) {
function.protect();
parent.tests.append(allocator, TestScope{
.label = formattedLabel,
.parent = parent,
.tag = tag,
.func = function,
.func_arg = function_args,
.func_has_callback = has_callback_function,
.timeout_millis = timeout_ms,
}) catch unreachable;
} else {
var scope = allocator.create(DescribeScope) catch unreachable;
const scope = allocator.create(DescribeScope) catch unreachable;
scope.* = .{
.label = formattedLabel,
.parent = parent,
.file_id = parent.file_id,
.tag = tag,
.function_value = JSC.Strong.create(function.bind(globalThis, .undefined, function_args), globalThis),
};
const ret = scope.run(globalThis, function, function_args);
_ = ret;
parent.child_describes_to_run +|= 1;
parent.child_describes_to_enqueue +|= 1;
Jest.runner.?.describe_queue.writeItem(scope) catch unreachable;
allocator.free(function_args);
}
test_idx += 1;

View File

@@ -1036,7 +1036,19 @@ pub const TestCommand = struct {
Output.prettyError(" {d:5>} fail<r>\n", .{reporter.summary.fail});
if (reporter.jest.unhandled_errors_between_tests > 0) {
Output.prettyError(" <r><red>{d:5>} error{s}<r>\n", .{ reporter.jest.unhandled_errors_between_tests, if (reporter.jest.unhandled_errors_between_tests > 1) "s" else "" });
Output.prettyError(" <r><red>{d:5>} error{s}<r>", .{ reporter.jest.unhandled_errors_between_tests, if (reporter.jest.unhandled_errors_between_tests > 1) "s" else "" });
switch (reporter.jest.tests_not_run_due_to_before_scope_error) {
0 => {},
1 => {
Output.prettyError("<d>, causing 1 or more tests to not run<r>", .{});
},
else => {
Output.prettyError("<d>, causing {d}+ tests to not run<r>", .{reporter.jest.tests_not_run_due_to_before_scope_error});
},
}
Output.prettyError("\n", .{});
}
var print_expect_calls = reporter.summary.expectations > 0;
@@ -1234,19 +1246,20 @@ pub const TestCommand = struct {
}
const file_end = reporter.jest.files.len;
const global = vm.global;
for (file_start..file_end) |module_id| {
const module: *jest.DescribeScope = reporter.jest.files.items(.module_scope)[module_id];
vm.onUnhandledRejectionCtx = null;
vm.onUnhandledRejection = jest.TestRunnerTask.onUnhandledRejection;
module.runTests(vm.global);
module.runTests(global);
vm.eventLoop().tick();
var prev_unhandled_count = vm.unhandled_error_counter;
while (vm.active_tasks > 0) : (vm.eventLoop().flushImmediateQueue()) {
if (!jest.Jest.runner.?.has_pending_tests) {
jest.Jest.runner.?.drain();
jest.Jest.runner.?.drain(vm, global);
}
vm.eventLoop().tick();

View File

@@ -1,3 +1,7 @@
/**
* THIS TEST WILL NOT WORK IF YOU ARE ON AT&T INTERNET THAT OVERRIDES YOUR DNS RESOLVER
* TO ALWAYS RESOLVE TO THEIR SPAMMY, AD-RIDDEN, PERSONAL-DATA-MINING, DNS SERVER!
*/
import { SystemError, dns } from "bun";
import { describe, expect, test } from "bun:test";
import { withoutAggressiveGC } from "harness";

View File

@@ -1,5 +1,47 @@
// Bun Snapshot v1, https://goo.gl/fbAQLP
exports[`expect().toEqual() on objects with property indices doesn't print undefined 1`] = `
"expect(received).toEqual(expected)
{
+ "0": 0,
+ "1": 1,
+ "10": 10,
+ "11": 11,
+ "12": 12,
+ "13": 13,
+ "14": 14,
+ "15": 15,
+ "2": 2,
+ "3": 3,
+ "4": 4,
+ "5": 5,
+ "6": 6,
+ "7": 7,
+ "8": 8,
+ "9": 9,
- "0": 123,
- "1": 123,
- "10": 123,
- "11": 123,
- "12": 123,
- "13": 123,
- "14": 123,
- "15": 123,
- "2": 123,
- "3": 123,
- "4": 123,
- "5": 123,
- "6": 123,
- "7": 123,
- "8": 123,
- "9": 123,
}
- Expected - 16
+ Received + 16"
`;
exports[`unhandled errors between tests are reported in beforeAll 1`] = `
"
my-test.test.js:
@@ -28,6 +70,9 @@ Ran 1 tests across 1 files.
exports[`unhandled errors between tests are reported in beforeEach 1`] = `
"
my-test.test.js:
# Unhandled error between tests
-------------------------------
1 | import {test, beforeAll, expect, beforeEach, afterEach, afterAll, describe} from "bun:test";
2 |
3 | beforeEach(async () => {
@@ -36,10 +81,13 @@ my-test.test.js:
^
error: ## stage beforeEach ##
at <dir>/my-test.test.js:5:11
(fail) my-test
-------------------------------
0 pass
1 fail
(pass) my-test
1 pass
0 fail
1 error
Ran 1 tests across 1 files.
"
`;
@@ -108,7 +156,6 @@ my-test.test.js:
^
error: ## stage describe ##
at <dir>/my-test.test.js:5:11
at <dir>/my-test.test.js:3:1
-------------------------------
(pass) my-test
@@ -119,45 +166,3 @@ error: ## stage describe ##
Ran 1 tests across 1 files.
"
`;
exports[`expect().toEqual() on objects with property indices doesn't print undefined 1`] = `
"expect(received).toEqual(expected)
{
+ "0": 0,
+ "1": 1,
+ "10": 10,
+ "11": 11,
+ "12": 12,
+ "13": 13,
+ "14": 14,
+ "15": 15,
+ "2": 2,
+ "3": 3,
+ "4": 4,
+ "5": 5,
+ "6": 6,
+ "7": 7,
+ "8": 8,
+ "9": 9,
- "0": 123,
- "1": 123,
- "10": 123,
- "11": 123,
- "12": 123,
- "13": 123,
- "14": 123,
- "15": 123,
- "2": 123,
- "3": 123,
- "4": 123,
- "5": 123,
- "6": 123,
- "7": 123,
- "8": 123,
- "9": 123,
}
- Expected - 16
+ Received + 16"
`;

19
test/js/bun/test/bail-fixture.js generated Normal file
View File

@@ -0,0 +1,19 @@
import { afterAll, beforeAll, describe, expect, it } from "bun:test";
describe("test", () => {
beforeAll(async () => {
console.log(process.env.BEFORE);
});
afterAll(async () => {
console.log(process.env.AFTER);
});
it("should work", async () => {
expect(true).toBe(false);
});
it("should work2", async () => {
expect(true).toBe(true);
});
});

View File

@@ -0,0 +1,17 @@
describe("desc1", () => {
beforeAll(() => {
expect.unreachable();
});
test("test1", () => {
expect.unreachable();
});
});
describe.only("desc2", () => {
beforeAll(() => {
expect().pass();
});
test("test2", () => {
expect().pass();
});
});

View File

@@ -0,0 +1,19 @@
describe.only("Parent describe.only", () => {
describe.skip("skipped child describe", () => {
test("should not run", () => {
expect.unreachable();
});
});
describe.todo("todo child describe", () => {
test("should not run", () => {
expect.unreachable();
});
});
describe("non-only child describe", () => {
test("test should run", () => {
expect().pass();
});
});
});

View File

@@ -0,0 +1,7 @@
beforeAll(() => {
throw new Error("oops");
});
it("a test", () => {
expect(5).toBe(5);
});

View File

@@ -0,0 +1,11 @@
beforeAll(() => {
throw new Error("oops");
});
it("a test", () => {
expect(5).toBe(5);
});
it("b test", () => {
expect(5).toBe(5);
});

View File

@@ -0,0 +1,7 @@
beforeEach(() => {
throw new Error("oops");
});
it("a test", () => {
expect(5).toBe(5);
});

View File

@@ -0,0 +1,11 @@
beforeEach(() => {
throw new Error("oops");
});
it("a test", () => {
expect(5).toBe(5);
});
it("b test", () => {
expect(5).toBe(5);
});

View File

@@ -2,6 +2,71 @@ import { $ } from "bun";
import { expect, test } from "bun:test";
import { bunExe } from "harness";
test("error-in-beforeAll-1-fixture.js", async () => {
$.nothrow();
const result = await $.cwd(import.meta.dir)`${bunExe()} test ./error-in-beforeAll-1-fixture.js`;
const stderr = result.stderr.toUnixString().split("\n").filter(Boolean).slice(1).join("\n");
expect(stderr).toContain("Unhandled error between tests");
expect(stderr).toContain("1 error, causing 1 or more tests to not run");
expect(result.exitCode).toBe(1);
});
test("error-in-beforeAll-fixture.js", async () => {
$.nothrow();
const result = await $.cwd(import.meta.dir)`${bunExe()} test ./error-in-beforeAll-fixture.js`;
const stderr = result.stderr.toUnixString().split("\n").filter(Boolean).slice(1).join("\n");
expect(stderr).toContain("Unhandled error between tests");
expect(stderr).toContain("1 error, causing 2+ tests to not run");
expect(result.exitCode).toBe(1);
});
test("error-in-beforeEach-fixture-1.js", async () => {
$.nothrow();
const result = await $.cwd(import.meta.dir)`${bunExe()} test ./error-in-beforeEach-fixture-1.js`;
const stderr = result.stderr.toUnixString().split("\n").filter(Boolean).slice(1).join("\n");
expect(stderr).toContain("Unhandled error between tests");
expect(stderr).toContain("1 error, causing 1 or more tests to not run");
expect(result.exitCode).toBe(1);
});
test("error-in-beforeEach-fixture.js", async () => {
$.nothrow();
const result = await $.cwd(import.meta.dir)`${bunExe()} test ./error-in-beforeEach-fixture.js`;
const stderr = result.stderr.toUnixString().split("\n").filter(Boolean).slice(1).join("\n");
expect(stderr).toContain("Unhandled error between tests");
expect(stderr).toContain("1 error, causing 2+ tests to not run");
expect(result.exitCode).toBe(1);
});
test("describe-only-todo-fixture.js", async () => {
$.nothrow();
const result = await $.cwd(import.meta.dir)`${bunExe()} test ./describe-only-todo-fixture.js`;
const stderr = result.stderr.toUnixString().split("\n").filter(Boolean).slice(1);
expect(stderr).toEqual([
expect.stringContaining("(pass) Parent describe.only > non-only child describe > test should run"),
" 1 pass",
" 0 fail",
" 1 expect() calls",
expect.stringContaining("Ran 1 tests across 1 files"),
]);
});
test("describe.only + beforeAll", async () => {
const result = await $.cwd(import.meta.dir)`${bunExe()} test ./describe-only-fixture.js`;
const stderr = result.stderr.toUnixString().split("\n").filter(Boolean).slice(1);
expect(stderr).toEqual([
expect.stringContaining("(pass) desc2 > test2"),
" 1 pass",
" 0 fail",
" 2 expect() calls",
expect.stringContaining("Ran 1 tests across 1 files"),
]);
});
test.each(["./only-fixture-1.ts", "./only-fixture-2.ts", "./only-fixture-3.ts"])(
`test.only shouldn't need --only for %s`,
async (file: string) => {

View File

@@ -570,6 +570,54 @@ it("expect().toEqual() on objects with property indices doesn't print undefined"
expect(err).not.toContain("undefined");
});
it("https://github.com/oven-sh/bun/issues/14135", async () => {
const { stdout, exitCode } = spawnSync({
cmd: [bunExe(), "test", join(import.meta.dir, "describe-only-fixture.js")],
stdout: "pipe",
stderr: "inherit",
env: bunEnv,
cwd: import.meta.dir,
});
expect(exitCode).toBe(0);
expect(stdout.toUnixString().trim().split("\n")).toEqual([
`bun test ${Bun.version_with_sha}`,
"beforeAll 2",
"test 2",
]);
});
it("https://github.com/oven-sh/bun/issues/12250 with --bail", async () => {
const { stdout, exitCode } = spawnSync({
cmd: [bunExe(), "test", join(import.meta.dir, "bail-fixture.js"), "--bail"],
stderr: "inherit",
stdout: "pipe",
env: { ...bunEnv, BEFORE: "NOT IN THE TEXT - WITH BAIL", "AFTER": "BAIL!" },
cwd: import.meta.dir,
});
expect(exitCode).toBe(1);
const err = stdout!.toUnixString().trim();
expect(err).toContain("NOT IN THE TEXT - WITH BAIL");
expect(err).not.toContain("BAIL!");
});
it("https://github.com/oven-sh/bun/issues/12250 without --bail", async () => {
const { stdout, exitCode } = spawnSync({
cmd: [bunExe(), "test", join(import.meta.dir, "bail-fixture.js")],
stdout: "pipe",
env: { ...bunEnv, BEFORE: "NOT IN THE TEXT - WITHOUT BAIL", "AFTER": "BAIL!" },
cwd: import.meta.dir,
});
expect(exitCode).toBe(1);
const err = stdout!.toUnixString().trim();
expect(err).toContain("NOT IN THE TEXT - WITHOUT BAIL");
expect(err).toContain("BAIL!");
});
// https://jestjs.io/docs/setup-teardown
it("test --preload supports global lifecycle hooks", () => {
const preloadedPath = join(tmp, "test-fixture-preload-global-lifecycle-hook-preloaded.js");
const path = join(tmp, "test-fixture-preload-global-lifecycle-hook-test.test.js");
@@ -588,6 +636,13 @@ bun test ${Bun.version_with_sha}
beforeAll: #1
beforeAll: #2
beforeAll: TEST-FILE
beforeEach: #1
beforeEach: #2
beforeEach: TEST-FILE
-- the top-level test --
afterEach: TEST-FILE
afterEach: #1
afterEach: #2
beforeAll: one describe scope
beforeEach: #1
beforeEach: #2
@@ -599,13 +654,6 @@ afterEach: TEST-FILE
afterEach: #1
afterEach: #2
afterAll: one describe scope
beforeEach: #1
beforeEach: #2
beforeEach: TEST-FILE
-- the top-level test --
afterEach: TEST-FILE
afterEach: #1
afterEach: #2
afterAll: TEST-FILE
afterAll: #1
afterAll: #2
@@ -700,7 +748,7 @@ test("my-test", () => {
"my-test.test.js": code,
"package.json": "{}",
});
console.log({ test_dir });
const { stderr, exited } = spawnSync({
cmd: [bunExe(), "test", "my-test.test.js"],
cwd: test_dir,
@@ -723,6 +771,8 @@ test("my-test", () => {
// On Linux, it might not run in time, but on macOS it will
if (a.includes(" expect() calls")) return false;
if (a.includes("promiseReactionJob")) return false;
return true;
})
.map(a =>

View File

@@ -1,4 +1,4 @@
import { afterAll, describe, test } from "bun:test";
// import { afterAll, afterEach, describe, test } from "bun:test";
var expected: number[] = [];
var runs: number[] = [];
@@ -6,6 +6,9 @@ var count = 0;
function makeTest(yes = false) {
const thisCount = count++;
if (yes) expected.push(thisCount);
if (yes) {
console.log("expected: test " + thisCount);
}
test("test " + thisCount, () => {
runs.push(thisCount);
});