mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
Compare commits
9 Commits
claude/spa
...
jarred/des
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
832f06fef1 | ||
|
|
59eb6abc44 | ||
|
|
4f31917b0b | ||
|
|
f81278a58d | ||
|
|
8ef6344271 | ||
|
|
c60d090888 | ||
|
|
d01fcb6cc4 | ||
|
|
97a75b53f5 | ||
|
|
b4e81a4e53 |
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
3
src/bun.js/bindings/headers.h
generated
3
src/bun.js/bindings/headers.h
generated
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
19
test/js/bun/test/bail-fixture.js
generated
Normal 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);
|
||||
});
|
||||
});
|
||||
17
test/js/bun/test/describe-only-fixture.js
generated
Normal file
17
test/js/bun/test/describe-only-fixture.js
generated
Normal file
@@ -0,0 +1,17 @@
|
||||
describe("desc1", () => {
|
||||
beforeAll(() => {
|
||||
expect.unreachable();
|
||||
});
|
||||
test("test1", () => {
|
||||
expect.unreachable();
|
||||
});
|
||||
});
|
||||
|
||||
describe.only("desc2", () => {
|
||||
beforeAll(() => {
|
||||
expect().pass();
|
||||
});
|
||||
test("test2", () => {
|
||||
expect().pass();
|
||||
});
|
||||
});
|
||||
19
test/js/bun/test/describe-only-todo-fixture.js
generated
Normal file
19
test/js/bun/test/describe-only-todo-fixture.js
generated
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
7
test/js/bun/test/error-in-beforeAll-1-fixture.js
generated
Normal file
7
test/js/bun/test/error-in-beforeAll-1-fixture.js
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
beforeAll(() => {
|
||||
throw new Error("oops");
|
||||
});
|
||||
|
||||
it("a test", () => {
|
||||
expect(5).toBe(5);
|
||||
});
|
||||
11
test/js/bun/test/error-in-beforeAll-fixture.js
generated
Normal file
11
test/js/bun/test/error-in-beforeAll-fixture.js
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
beforeAll(() => {
|
||||
throw new Error("oops");
|
||||
});
|
||||
|
||||
it("a test", () => {
|
||||
expect(5).toBe(5);
|
||||
});
|
||||
|
||||
it("b test", () => {
|
||||
expect(5).toBe(5);
|
||||
});
|
||||
7
test/js/bun/test/error-in-beforeEach-fixture-1.js
generated
Normal file
7
test/js/bun/test/error-in-beforeEach-fixture-1.js
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
beforeEach(() => {
|
||||
throw new Error("oops");
|
||||
});
|
||||
|
||||
it("a test", () => {
|
||||
expect(5).toBe(5);
|
||||
});
|
||||
11
test/js/bun/test/error-in-beforeEach-fixture.js
generated
Normal file
11
test/js/bun/test/error-in-beforeEach-fixture.js
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
beforeEach(() => {
|
||||
throw new Error("oops");
|
||||
});
|
||||
|
||||
it("a test", () => {
|
||||
expect(5).toBe(5);
|
||||
});
|
||||
|
||||
it("b test", () => {
|
||||
expect(5).toBe(5);
|
||||
});
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
Reference in New Issue
Block a user