Skip to content

Commit

Permalink
move some code around & support describe
Browse files Browse the repository at this point in the history
  • Loading branch information
jecquas committed Aug 7, 2023
1 parent 9a57f6f commit 5df318d
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 95 deletions.
19 changes: 19 additions & 0 deletions packages/bun-types/bun-test.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,25 @@ declare module "bun:test" {
* @param condition if these tests should be skipped
*/
skipIf(condition: boolean): (label: string, fn: () => void) => void;
/**
* Returns a function that runs for each item in `table`.
*
* @param table Array of Arrays with the arguments that are passed into the test fn for each row.
*/
each<T extends ReadonlyArray<unknown>>(
table: ReadonlyArray<T>,
): (
label: string,
fn: (...args: T) => void | Promise<unknown>,
options?: number | TestOptions,
) => void;
each<T>(
table: ReadonlyArray<T>,
): (
label: string,
fn: (arg: T) => void | Promise<unknown>,
options?: number | TestOptions,
) => void;
};
/**
* Describes a group of related tests.
Expand Down
220 changes: 128 additions & 92 deletions src/bun.js/test/jest.zig
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,11 @@ pub const Jest = struct {
ZigString.static("skipIf"),
JSC.NewFunction(globalObject, ZigString.static("skipIf"), 2, DescribeScope.skipIf, false),
);
describe.put(
globalObject,
ZigString.static("each"),
JSC.NewFunction(globalObject, ZigString.static("each"), 2, DescribeScope.each, false),
);

module.put(
globalObject,
Expand Down Expand Up @@ -547,8 +552,9 @@ pub const TestScope = struct {
parent: *DescribeScope,

func: JSC.JSValue,
func_arg: JSC.JSValue,
func_has_args: bool = false,
func_arg: []JSC.JSValue,
func_has_callback: bool = false,
func_arg_size: usize,

id: TestRunner.Test.ID = 0,
promise: ?*JSInternalPromise = null,
Expand Down Expand Up @@ -582,7 +588,7 @@ pub const TestScope = struct {
}

pub fn each(globalThis: *JSGlobalObject, callframe: *CallFrame) callconv(.C) JSValue {
return createEach(globalThis, callframe, "test.each()", "each", TestScope);
return createEach(globalThis, callframe, "test.each()", "each", true);
}

pub fn callIf(globalThis: *JSGlobalObject, callframe: *CallFrame) callconv(.C) JSValue {
Expand Down Expand Up @@ -647,11 +653,15 @@ pub const TestScope = struct {
const func = this.func;
Jest.runner.?.did_pending_test_fail = false;
defer {
var idx: u32 = 0;
while (idx < this.func_arg_size) {
this.func_arg[idx].unprotect();
idx += 1;
}
func.unprotect();
this.func = .zero;
this.func_has_args = false;
this.func_arg.unprotect();
this.func_arg = .zero;
this.func_arg_size = 0;
this.func_has_callback = false;
vm.autoGarbageCollect();
}
JSC.markBinding(@src());
Expand All @@ -667,67 +677,21 @@ pub const TestScope = struct {
task.test_id,
);

const func_params_length = func.getLength(vm.global);
var has_callback_function = false;

if (func_params_length > 0) {
var func_args_length: usize = 0;

if (this.func_has_args) {
// If the func arg is an array, we will spread it as the arguments, so assume its length
if (!this.func_arg.isEmptyOrUndefinedOrNull() and this.func_arg.jsType().isArray()) {
func_args_length = this.func_arg.getLength(vm.global);
} else {
func_args_length = 1;
}
}

const allocator = getAllocator(vm.global);
const should_add_callback_function: bool = !this.func_has_args or (func_params_length > func_args_length);

var argSize: usize = func_args_length;
if (should_add_callback_function) {
argSize += 1;
}

const function_args = allocator.alloc(JSC.JSValue, argSize) catch @panic("can't create function_args");
var idx: u32 = 0;

// Spread the array as arguments
if (this.func_has_args) {
if (!this.func_arg.isEmptyOrUndefinedOrNull() and this.func_arg.jsType().isArray()) {
const length = this.func_arg.getLength(vm.global);

while (idx < length) : (idx += 1) {
function_args[idx] = this.func_arg.getIndex(vm.global, idx);
}
} else {
function_args[idx] = this.func_arg;
idx += 1;
}
}

if (should_add_callback_function) {
has_callback_function = true;
const callback_func = JSC.NewFunctionWithData(
vm.global,
ZigString.static("done"),
0,
TestScope.onDone,
false,
task,
);
task.done_callback_state = .pending;
function_args[idx] = callback_func;
}

initial_value = func.call(vm.global, function_args);

allocator.free(function_args);
} else {
initial_value = func.call(vm.global, &.{});
if (this.func_has_callback) {
const callback_func = JSC.NewFunctionWithData(
vm.global,
ZigString.static("done"),
0,
TestScope.onDone,
false,
task,
);
task.done_callback_state = .pending;
this.func_arg[this.func_arg_size - 1] = callback_func;
}

initial_value = this.func.call(vm.global, @as([]const JSC.JSValue, this.func_arg));

if (initial_value.isAnyError()) {
if (!Jest.runner.?.did_pending_test_fail) {
// test failed unless it's a todo
Expand Down Expand Up @@ -784,7 +748,7 @@ pub const TestScope = struct {
}
}

if (has_callback_function) {
if (this.func_has_callback) {
return .{ .pending = {} };
}

Expand Down Expand Up @@ -1088,6 +1052,10 @@ pub const DescribeScope = struct {
return createScope(globalThis, callframe, "describe.todo()", false, .todo);
}

pub fn each(globalThis: *JSGlobalObject, callframe: *CallFrame) callconv(.C) JSValue {
return createEach(globalThis, callframe, "describe.each()", "each", false);
}

pub fn callIf(globalThis: *JSGlobalObject, callframe: *CallFrame) callconv(.C) JSValue {
return createIfScope(globalThis, callframe, "describe.if()", "if", DescribeScope, false);
}
Expand All @@ -1096,7 +1064,7 @@ pub const DescribeScope = struct {
return createIfScope(globalThis, callframe, "describe.skipIf()", "skipIf", DescribeScope, true);
}

pub fn run(this: *DescribeScope, globalObject: *JSC.JSGlobalObject, callback: JSC.JSValue) JSC.JSValue {
pub fn run(this: *DescribeScope, globalObject: *JSC.JSGlobalObject, callback: JSC.JSValue, args: []const JSC.JSValue) JSC.JSValue {
if (comptime is_bindgen) return undefined;
callback.protect();
defer callback.unprotect();
Expand All @@ -1111,7 +1079,7 @@ pub const DescribeScope = struct {
{
JSC.markBinding(@src());
globalObject.clearTerminationException();
var result = callback.call(globalObject, &.{});
var result = callback.call(globalObject, args);

if (result.asAnyPromise()) |prom| {
globalObject.bunVM().waitForPromise(prom);
Expand Down Expand Up @@ -1588,13 +1556,23 @@ inline fn createScope(
function.protect();
}

const func_params_length = function.getLength(globalThis);
var arg_size: usize = 0;
var has_callback = false;
if (func_params_length > 0) {
has_callback = true;
arg_size = 1;
}
var function_args = allocator.alloc(JSC.JSValue, arg_size) catch unreachable;

parent.tests.append(allocator, TestScope{
.label = label,
.parent = parent,
.tag = tag,
.func = if (is_skip) .zero else function,
.func_arg = .zero,
.func_has_args = false,
.func_arg = function_args,
.func_arg_size = arg_size,
.func_has_callback = has_callback,
.timeout_millis = timeout_ms,
}) catch unreachable;

Expand All @@ -1613,7 +1591,10 @@ inline fn createScope(
.is_skip = is_skip or parent.is_skip,
};

return scope.run(globalThis, function);
const function_args = allocator.alloc(JSC.JSValue, 0) catch unreachable;
defer allocator.free(function_args);

return scope.run(globalThis, function, function_args);
}

return this;
Expand Down Expand Up @@ -1768,6 +1749,8 @@ pub fn printGithubAnnotation(exception: *JSC.ZigException) void {
Output.flush();
}

pub const EachData = struct { strong: JSC.Strong, is_test: bool };

fn eachBind(
globalThis: *JSGlobalObject,
callframe: *CallFrame,
Expand Down Expand Up @@ -1820,12 +1803,12 @@ fn eachBind(

if (JSC.getFunctionData(callee)) |data| {
const allocator = getAllocator(globalThis);
const strong_ptr = bun.cast(*JSC.Strong, data);
const each_data = bun.cast(*EachData, data);
JSC.setFunctionData(callee, null);
const array = strong_ptr.*.get() orelse return .zero;
const array = each_data.*.strong.get() orelse return .zero;
defer {
strong_ptr.*.deinit();
allocator.destroy(strong_ptr);
each_data.*.strong.deinit();
allocator.destroy(each_data);
}

if (array.isUndefinedOrNull() or !array.jsType().isArray()) {
Expand All @@ -1841,17 +1824,69 @@ fn eachBind(
else
(description.toSlice(globalThis, allocator).cloneIfNeeded(allocator) catch unreachable).slice();

function.protect();
item.protect();
parent.tests.append(allocator, TestScope{
.label = label,
.parent = parent,
.tag = parent.tag,
.func = function,
.func_arg = item,
.func_has_args = true,
.timeout_millis = timeout_ms,
}) catch unreachable;
const func_params_length = function.getLength(globalThis);
const item_is_array = !item.isEmptyOrUndefinedOrNull() and item.jsType().isArray();
var arg_size: usize = 1;

if (item_is_array) {
arg_size = item.getLength(globalThis);
}

// add room for callback function
var has_callback_function: bool = func_params_length > arg_size and each_data.is_test;
if (has_callback_function) {
arg_size += 1;
}

var function_args = allocator.alloc(JSC.JSValue, arg_size) catch @panic("can't create function_args");
var idx: u32 = 0;

if (item_is_array) {
// Spread array as args
const item_length = item.getLength(globalThis);
while (idx < item_length) : (idx += 1) {
var arg = item.getIndex(globalThis, idx);
arg.protect();
function_args[idx] = arg;
}
} else {
item.protect();
function_args[idx] = item;
idx += 1;
}

if (each_data.is_test) {
function.protect();
parent.tests.append(allocator, TestScope{
.label = label,
.parent = parent,
.tag = parent.tag,
.func = function,
.func_arg = function_args,
.func_arg_size = arg_size,
.func_has_callback = has_callback_function,
.timeout_millis = timeout_ms,
}) catch unreachable;

if (test_elapsed_timer == null) create_timer: {
var timer = allocator.create(std.time.Timer) catch unreachable;
timer.* = std.time.Timer.start() catch break :create_timer;
test_elapsed_timer = timer;
}
} else {
var scope = allocator.create(DescribeScope) catch unreachable;
scope.* = .{
.label = label,
.parent = parent,
.file_id = parent.file_id,
.tag = if (parent.is_skip) parent.tag else .pass,
.is_skip = parent.is_skip,
};

var ret = scope.run(globalThis, function, function_args);
_ = ret;
allocator.free(function_args);
}
}
}

Expand All @@ -1863,10 +1898,8 @@ inline fn createEach(
callframe: *CallFrame,
comptime property: string,
comptime signature: string,
comptime Scope: type,
comptime is_test: bool,
) JSValue {
_ = Scope;

const arguments = callframe.arguments(1);
const args = arguments.ptr[0..arguments.len];

Expand All @@ -1884,8 +1917,11 @@ inline fn createEach(
const allocator = getAllocator(globalThis);
const name = ZigString.static(property);
var strong = JSC.Strong.create(array, globalThis);
var strong_ptr = allocator.create(JSC.Strong) catch unreachable;
strong_ptr.* = strong;
var each_data = allocator.create(EachData) catch unreachable;
each_data.* = EachData{
.strong = strong,
.is_test = is_test,
};

return JSC.NewFunctionWithData(globalThis, name, 3, eachBind, true, strong_ptr);
return JSC.NewFunctionWithData(globalThis, name, 3, eachBind, true, each_data);
}
12 changes: 9 additions & 3 deletions test/js/bun/test/jest-each.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ describe("jest-each", () => {
it.each(NUMBERS)("add two numbers", (a, b, e) => {
expect(a + b).toBe(e);
});

it.each(NUMBERS)("add two numbers with callback", (a, b, e, done) => {
expect(a + b).toBe(e);
expect(done).toBeDefined();
Expand All @@ -36,10 +35,17 @@ describe("jest-each", () => {
{ a: 1, b: 2, e: 3 },
{ a: 2, b: 13, e: 15 },
{ a: 2, b: 13, e: 15 },
{ a: 2, b: 13, e: 15 },
{ a: 2, b: 13, e: 15 },
{ a: 2, b: 123, e: 125 },
{ a: 15, b: 13, e: 28 },
])("add two numbers with object", ({ a, b, e }, cb) => {
expect(a + b).toBe(e);
cb();
});
});

describe.each(["some", "cool", "strings"])("works with describe", s => {
it(`has access to params : ${s}`, done => {
expect(s).toBeTypeOf("string");
done();
});
});

0 comments on commit 5df318d

Please sign in to comment.