feat: allow "bun:test" utilities at runtime (#7486)

* feat: allow "bun:test" utilities at runtime

* fsdafdsafd

* yippee
This commit is contained in:
dave caruso
2023-12-06 22:09:43 -08:00
committed by GitHub
parent b1c8ae97ff
commit 1bf540efcf
11 changed files with 195 additions and 155 deletions

View File

@@ -2200,6 +2200,7 @@ pub const ModuleLoader = struct {
.@"node:util/types" => return jsSyntheticModule(.@"node:util/types", specifier),
.@"node:constants" => return jsSyntheticModule(.@"node:constants", specifier),
.@"bun:jsc" => return jsSyntheticModule(.@"bun:jsc", specifier),
.@"bun:test" => return jsSyntheticModule(.@"bun:test", specifier),
// These are defined in src/js/*
.@"bun:ffi" => return jsSyntheticModule(.@"bun:ffi", specifier),
@@ -2377,6 +2378,7 @@ pub const HardcodedModule = enum {
@"bun:ffi",
@"bun:jsc",
@"bun:main",
@"bun:test", // usually replaced by the transpiler but `await import("bun:" + "test")` has to work
@"bun:sqlite",
@"detect-libc",
@"node:assert",
@@ -2450,6 +2452,7 @@ pub const HardcodedModule = enum {
.{ "bun:ffi", HardcodedModule.@"bun:ffi" },
.{ "bun:jsc", HardcodedModule.@"bun:jsc" },
.{ "bun:main", HardcodedModule.@"bun:main" },
.{ "bun:test", HardcodedModule.@"bun:test" },
.{ "bun:sqlite", HardcodedModule.@"bun:sqlite" },
.{ "detect-libc", HardcodedModule.@"detect-libc" },
.{ "node-fetch", HardcodedModule.@"node-fetch" },
@@ -2658,6 +2661,7 @@ pub const HardcodedModule = enum {
const bun_extra_alias_kvs = .{
.{ "bun", .{ .path = "bun", .tag = .bun } },
.{ "bun:test", .{ .path = "bun:test", .tag = .bun_test } },
.{ "bun:ffi", .{ .path = "bun:ffi" } },
.{ "bun:jsc", .{ .path = "bun:jsc" } },
.{ "bun:sqlite", .{ .path = "bun:sqlite" } },

View File

@@ -9,13 +9,11 @@ void generateNativeModule_BunObject(JSC::JSGlobalObject *lexicalGlobalObject,
JSC::Identifier moduleKey,
Vector<JSC::Identifier, 4> &exportNames,
JSC::MarkedArgumentBuffer &exportValues) {
// FIXME: this does not add each property as a top level export
JSC::VM &vm = lexicalGlobalObject->vm();
Zig::GlobalObject *globalObject =
reinterpret_cast<Zig::GlobalObject *>(lexicalGlobalObject);
Zig::GlobalObject *globalObject = jsCast<Zig::GlobalObject *>(lexicalGlobalObject);
JSObject *object =
globalObject->get(globalObject, Identifier::fromString(vm, "Bun"_s))
.getObject();
JSObject *object = globalObject->bunObject();
exportNames.append(vm.propertyNames->defaultKeyword);
exportValues.append(object);

View File

@@ -0,0 +1,17 @@
namespace Zig {
void generateNativeModule_BunTest(
JSC::JSGlobalObject *lexicalGlobalObject,
JSC::Identifier moduleKey,
Vector<JSC::Identifier, 4> &exportNames,
JSC::MarkedArgumentBuffer &exportValues) {
JSC::VM &vm = lexicalGlobalObject->vm();
auto globalObject = jsCast<Zig::GlobalObject*>(lexicalGlobalObject);
JSObject *object = globalObject->lazyPreloadTestModuleObject();
exportNames.append(vm.propertyNames->defaultKeyword);
exportValues.append(object);
}
} // namespace Zig

View File

@@ -35,6 +35,7 @@ static constexpr ASCIILiteral builtinModuleNames[] = {
"bun:ffi"_s,
"bun:jsc"_s,
"bun:sqlite"_s,
"bun:test"_s,
"bun:wrap"_s,
"child_process"_s,
"cluster"_s,

View File

@@ -25,6 +25,7 @@
#define BUN_FOREACH_NATIVE_MODULE(macro) \
macro("bun"_s, BunObject) \
macro("bun:test"_s, BunTest) \
macro("bun:jsc"_s, BunJSC) \
macro("node:buffer"_s, NodeBuffer) \
macro("node:constants"_s, NodeConstants) \

View File

@@ -334,11 +334,14 @@ pub const Expect = struct {
};
expect.* = .{
.parent = if (Jest.runner.?.pending_test) |pending|
Expect.ParentScope{ .TestScope = Expect.TestScope{
.describe = pending.describe,
.test_id = pending.test_id,
} }
.parent = if (Jest.runner) |runner|
if (runner.pending_test) |pending|
Expect.ParentScope{ .TestScope = Expect.TestScope{
.describe = pending.describe,
.test_id = pending.test_id,
} }
else
Expect.ParentScope{ .global = {} }
else
Expect.ParentScope{ .global = {} },
};

View File

@@ -273,6 +273,11 @@ pub const Jest = struct {
globalThis: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) callconv(.C) JSValue {
const the_runner = runner orelse {
globalThis.throw("Cannot use " ++ name ++ "() outside of the test runner. Run \"bun test\" to run tests.", .{});
return .zero;
};
const arguments = callframe.arguments(2);
if (arguments.len < 1) {
globalThis.throwNotEnoughArguments("callback", 1, arguments.len);
@@ -291,7 +296,7 @@ pub const Jest = struct {
}
function.protect();
@field(Jest.runner.?.global_callbacks, name).append(
@field(the_runner.global_callbacks, name).append(
bun.default_allocator,
function,
) catch unreachable;
@@ -300,49 +305,23 @@ pub const Jest = struct {
}.appendGlobalFunctionCallback;
}
pub fn Bun__Jest__createTestPreloadObject(globalObject: *JSC.JSGlobalObject) callconv(.C) JSC.JSValue {
JSC.markBinding(@src());
var global_hooks_object = JSC.JSValue.createEmptyObject(globalObject, 8);
global_hooks_object.ensureStillAlive();
const notSupportedHereFn = struct {
pub fn notSupportedHere(
globalThis: *JSC.JSGlobalObject,
_: *JSC.CallFrame,
) callconv(.C) JSValue {
globalThis.throw("This function can only be used in a test.", .{});
return .zero;
}
}.notSupportedHere;
const notSupportedHere = JSC.NewFunction(globalObject, null, 0, notSupportedHereFn, false);
notSupportedHere.ensureStillAlive();
inline for (.{
"expect",
"describe",
"it",
"test",
}) |name| {
global_hooks_object.put(globalObject, ZigString.static(name), notSupportedHere);
}
inline for (.{ "beforeAll", "beforeEach", "afterAll", "afterEach" }) |name| {
const function = JSC.NewFunction(globalObject, null, 1, globalHook(name), false);
function.ensureStillAlive();
global_hooks_object.put(globalObject, ZigString.static(name), function);
}
createMockObjects(globalObject, global_hooks_object);
return global_hooks_object;
pub fn Bun__Jest__createTestModuleObject(globalObject: *JSC.JSGlobalObject) callconv(.C) JSC.JSValue {
return createTestModule(globalObject, false);
}
pub fn Bun__Jest__createTestModuleObject(globalObject: *JSC.JSGlobalObject) callconv(.C) JSC.JSValue {
JSC.markBinding(@src());
pub fn Bun__Jest__createTestPreloadObject(globalObject: *JSC.JSGlobalObject) callconv(.C) JSC.JSValue {
return createTestModule(globalObject, true);
}
pub fn createTestModule(globalObject: *JSC.JSGlobalObject, comptime outside_of_test: bool) JSC.JSValue {
const ThisTestScope, const ThisDescribeScope = if (outside_of_test)
.{ WrappedTestScope, WrappedDescribeScope }
else
.{ TestScope, DescribeScope };
const module = JSC.JSValue.createEmptyObject(globalObject, 13);
const test_fn = JSC.NewFunction(globalObject, ZigString.static("test"), 2, TestScope.call, false);
const test_fn = JSC.NewFunction(globalObject, ZigString.static("test"), 2, ThisTestScope.call, false);
module.put(
globalObject,
ZigString.static("test"),
@@ -351,32 +330,32 @@ pub const Jest = struct {
test_fn.put(
globalObject,
ZigString.static("only"),
JSC.NewFunction(globalObject, ZigString.static("only"), 2, TestScope.only, false),
JSC.NewFunction(globalObject, ZigString.static("only"), 2, ThisTestScope.only, false),
);
test_fn.put(
globalObject,
ZigString.static("skip"),
JSC.NewFunction(globalObject, ZigString.static("skip"), 2, TestScope.skip, false),
JSC.NewFunction(globalObject, ZigString.static("skip"), 2, ThisTestScope.skip, false),
);
test_fn.put(
globalObject,
ZigString.static("todo"),
JSC.NewFunction(globalObject, ZigString.static("todo"), 2, TestScope.todo, false),
JSC.NewFunction(globalObject, ZigString.static("todo"), 2, ThisTestScope.todo, false),
);
test_fn.put(
globalObject,
ZigString.static("if"),
JSC.NewFunction(globalObject, ZigString.static("if"), 2, TestScope.callIf, false),
JSC.NewFunction(globalObject, ZigString.static("if"), 2, ThisTestScope.callIf, false),
);
test_fn.put(
globalObject,
ZigString.static("skipIf"),
JSC.NewFunction(globalObject, ZigString.static("skipIf"), 2, TestScope.skipIf, false),
JSC.NewFunction(globalObject, ZigString.static("skipIf"), 2, ThisTestScope.skipIf, false),
);
test_fn.put(
globalObject,
ZigString.static("each"),
JSC.NewFunction(globalObject, ZigString.static("each"), 2, TestScope.each, false),
JSC.NewFunction(globalObject, ZigString.static("each"), 2, ThisTestScope.each, false),
);
module.put(
@@ -384,36 +363,36 @@ pub const Jest = struct {
ZigString.static("it"),
test_fn,
);
const describe = JSC.NewFunction(globalObject, ZigString.static("describe"), 2, DescribeScope.call, false);
const describe = JSC.NewFunction(globalObject, ZigString.static("describe"), 2, ThisDescribeScope.call, false);
describe.put(
globalObject,
ZigString.static("only"),
JSC.NewFunction(globalObject, ZigString.static("only"), 2, DescribeScope.only, false),
JSC.NewFunction(globalObject, ZigString.static("only"), 2, ThisDescribeScope.only, false),
);
describe.put(
globalObject,
ZigString.static("skip"),
JSC.NewFunction(globalObject, ZigString.static("skip"), 2, DescribeScope.skip, false),
JSC.NewFunction(globalObject, ZigString.static("skip"), 2, ThisDescribeScope.skip, false),
);
describe.put(
globalObject,
ZigString.static("todo"),
JSC.NewFunction(globalObject, ZigString.static("todo"), 2, DescribeScope.todo, false),
JSC.NewFunction(globalObject, ZigString.static("todo"), 2, ThisDescribeScope.todo, false),
);
describe.put(
globalObject,
ZigString.static("if"),
JSC.NewFunction(globalObject, ZigString.static("if"), 2, DescribeScope.callIf, false),
JSC.NewFunction(globalObject, ZigString.static("if"), 2, ThisDescribeScope.callIf, false),
);
describe.put(
globalObject,
ZigString.static("skipIf"),
JSC.NewFunction(globalObject, ZigString.static("skipIf"), 2, DescribeScope.skipIf, false),
JSC.NewFunction(globalObject, ZigString.static("skipIf"), 2, ThisDescribeScope.skipIf, false),
);
describe.put(
globalObject,
ZigString.static("each"),
JSC.NewFunction(globalObject, ZigString.static("each"), 2, DescribeScope.each, false),
JSC.NewFunction(globalObject, ZigString.static("each"), 2, ThisDescribeScope.each, false),
);
module.put(
@@ -422,26 +401,22 @@ pub const Jest = struct {
describe,
);
module.put(
globalObject,
ZigString.static("beforeAll"),
JSC.NewRuntimeFunction(globalObject, ZigString.static("beforeAll"), 1, DescribeScope.beforeAll, false, false),
);
module.put(
globalObject,
ZigString.static("beforeEach"),
JSC.NewRuntimeFunction(globalObject, ZigString.static("beforeEach"), 1, DescribeScope.beforeEach, false, false),
);
module.put(
globalObject,
ZigString.static("afterAll"),
JSC.NewRuntimeFunction(globalObject, ZigString.static("afterAll"), 1, DescribeScope.afterAll, false, false),
);
module.put(
globalObject,
ZigString.static("afterEach"),
JSC.NewRuntimeFunction(globalObject, ZigString.static("afterEach"), 1, DescribeScope.afterEach, false, false),
);
inline for (.{ "beforeAll", "beforeEach", "afterAll", "afterEach" }) |name| {
const function = if (outside_of_test)
JSC.NewFunction(globalObject, null, 1, globalHook(name), false)
else
JSC.NewRuntimeFunction(
globalObject,
ZigString.static(name),
1,
@field(DescribeScope, name),
false,
false,
);
module.put(globalObject, ZigString.static(name), function);
function.ensureStillAlive();
}
module.put(
globalObject,
ZigString.static("expect"),
@@ -523,12 +498,12 @@ pub const Jest = struct {
globalObject: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
JSC.markBinding(@src());
var vm = globalObject.bunVM();
if (vm.is_in_preload or runner == null) {
return Bun__Jest__testPreloadObject(globalObject);
}
const arguments = callframe.arguments(2).slice();
var runner_ = runner orelse {
globalObject.throw("Run \"bun test\" to run a test", .{});
return .undefined;
};
if (arguments.len < 1 or !arguments[0].isString()) {
globalObject.throw("Bun.jest() expects a string filename", .{});
@@ -542,14 +517,9 @@ pub const Jest = struct {
globalObject.throw("Bun.jest() expects an absolute file path", .{});
return .undefined;
}
var vm = globalObject.bunVM();
if (vm.is_in_preload) {
return Bun__Jest__testPreloadObject(globalObject);
}
var filepath = Fs.FileSystem.instance.filename_store.append([]const u8, slice) catch unreachable;
var scope = runner_.getOrPutFile(filepath);
var scope = runner.?.getOrPutFile(filepath);
scope.push();
return Bun__Jest__testModuleObject(globalObject);
@@ -874,7 +844,7 @@ pub const DescribeScope = struct {
pub threadlocal var active: ?*DescribeScope = null;
const CallbackFn = *const fn (
const CallbackFn = fn (
*JSC.JSGlobalObject,
*JSC.CallFrame,
) callconv(.C) JSC.JSValue;
@@ -1242,6 +1212,44 @@ pub const DescribeScope = struct {
};
pub fn wrapTestFunction(comptime name: []const u8, comptime func: DescribeScope.CallbackFn) DescribeScope.CallbackFn {
return struct {
pub fn wrapped(globalThis: *JSGlobalObject, callframe: *CallFrame) callconv(.C) JSValue {
if (Jest.runner == null) {
globalThis.throw("Cannot use " ++ name ++ "() outside of the test runner. Run \"bun test\" to run tests.", .{});
return .zero;
}
if (globalThis.bunVM().is_in_preload) {
globalThis.throw("Cannot use " ++ name ++ "() outside of a test file.", .{});
return .zero;
}
return @call(.always_inline, func, .{ globalThis, callframe });
}
}.wrapped;
}
/// This wrapped scope as well as the wrapped describe scope is used when you load `bun:test`
/// outside of
pub const WrappedTestScope = struct {
pub const call = wrapTestFunction("test", TestScope.call);
pub const only = wrapTestFunction("test", TestScope.only);
pub const skip = wrapTestFunction("test", TestScope.skip);
pub const todo = wrapTestFunction("test", TestScope.todo);
pub const callIf = wrapTestFunction("test", TestScope.callIf);
pub const skipIf = wrapTestFunction("test", TestScope.skipIf);
pub const each = wrapTestFunction("test", TestScope.each);
};
pub const WrappedDescribeScope = struct {
pub const call = wrapTestFunction("describe", DescribeScope.call);
pub const only = wrapTestFunction("describe", DescribeScope.only);
pub const skip = wrapTestFunction("describe", DescribeScope.skip);
pub const todo = wrapTestFunction("describe", DescribeScope.todo);
pub const callIf = wrapTestFunction("describe", DescribeScope.callIf);
pub const skipIf = wrapTestFunction("describe", DescribeScope.skipIf);
pub const each = wrapTestFunction("describe", DescribeScope.each);
};
pub const TestRunnerTask = struct {
test_id: TestRunner.Test.ID,
describe: *DescribeScope,

View File

@@ -18,6 +18,65 @@ function start() {
throwNotImplemented("node:repl");
}
const builtinModules = [
"bun",
"ffi",
"assert",
"assert/strict",
"async_hooks",
"buffer",
"child_process",
"cluster",
"console",
"constants",
"crypto",
"dgram",
"diagnostics_channel",
"dns",
"dns/promises",
"domain",
"events",
"fs",
"fs/promises",
"http",
"http2",
"https",
"inspector",
"inspector/promises",
"module",
"net",
"os",
"path",
"path/posix",
"path/win32",
"perf_hooks",
"process",
"punycode",
"querystring",
"readline",
"readline/promises",
"repl",
"stream",
"stream/consumers",
"stream/promises",
"stream/web",
"string_decoder",
"sys",
"timers",
"timers/promises",
"tls",
"trace_events",
"tty",
"url",
"util",
"util/types",
"v8",
"vm",
"wasi",
"worker_threads",
"zlib",
];
export default {
lines: [],
context: globalThis,
@@ -74,62 +133,6 @@ export default {
},
},
),
_builtinLibs: [
"bun",
"ffi",
"assert",
"assert/strict",
"async_hooks",
"buffer",
"child_process",
"cluster",
"console",
"constants",
"crypto",
"dgram",
"diagnostics_channel",
"dns",
"dns/promises",
"domain",
"events",
"fs",
"fs/promises",
"http",
"http2",
"https",
"inspector",
"inspector/promises",
"module",
"net",
"os",
"path",
"path/posix",
"path/win32",
"perf_hooks",
"process",
"punycode",
"querystring",
"readline",
"readline/promises",
"repl",
"stream",
"stream/consumers",
"stream/promises",
"stream/web",
"string_decoder",
"sys",
"timers",
"timers/promises",
"tls",
"trace_events",
"tty",
"url",
"util",
"util/types",
"v8",
"vm",
"wasi",
"worker_threads",
"zlib",
],
_builtinLibs: builtinModules,
builtinModules: builtinModules,
};

View File

@@ -1017,7 +1017,7 @@ fn NewPrinter(
}
fn printBunJestImportStatement(p: *Printer, import: S.Import) void {
if (comptime !is_bun_platform) unreachable;
comptime std.debug.assert(is_bun_platform);
switch (p.options.module_type) {
.cjs => {

View File

@@ -6,7 +6,7 @@ import path from "path";
test("builtinModules exists", () => {
expect(Array.isArray(builtinModules)).toBe(true);
expect(builtinModules).toHaveLength(76);
expect(builtinModules).toHaveLength(77);
});
test("isBuiltin() works", () => {

View File

@@ -1,4 +1,4 @@
import { describe, expect, test } from "bun:test";
import { expect, test } from "bun:test";
const weirdInternalSpecifiers = [
"_http_agent",
@@ -93,3 +93,8 @@ for (let specifier of specifiers) {
}
});
}
test("you can import bun:test", async () => {
const bunTest1 = await import("bun:test" + String(""));
const bunTest2 = require("bun:test" + String(""));
});