mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
Initial support for node:test (#18140)
This commit is contained in:
@@ -174,7 +174,7 @@ Some methods are not optimized yet.
|
||||
|
||||
### [`node:test`](https://nodejs.org/api/test.html)
|
||||
|
||||
🔴 Not implemented. Use [`bun:test`](https://bun.sh/docs/cli/test) instead.
|
||||
🟡 Partly implemented. Missing mocks, snapshots, timers. Use [`bun:test`](https://bun.sh/docs/cli/test) instead.
|
||||
|
||||
### [`node:trace_events`](https://nodejs.org/api/tracing.html)
|
||||
|
||||
|
||||
@@ -256,8 +256,10 @@ async function runTests() {
|
||||
for (const testPath of tests) {
|
||||
const absoluteTestPath = join(testsPath, testPath);
|
||||
const title = relative(cwd, absoluteTestPath).replaceAll(sep, "/");
|
||||
if (isNodeParallelTest(testPath)) {
|
||||
const runWithBunTest = title.includes("needs-test") || readFileSync(absoluteTestPath, "utf-8").includes('bun:test');
|
||||
if (isNodeTest(testPath)) {
|
||||
const testContent = readFileSync(absoluteTestPath, "utf-8");
|
||||
const runWithBunTest =
|
||||
title.includes("needs-test") || testContent.includes("bun:test") || testContent.includes("node:test");
|
||||
const subcommand = runWithBunTest ? "test" : "run";
|
||||
await runTest(title, async () => {
|
||||
const { ok, error, stdout } = await spawnBun(execPath, {
|
||||
@@ -870,19 +872,26 @@ function isJavaScriptTest(path) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} testPath
|
||||
* @param {string} path
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isNodeParallelTest(testPath) {
|
||||
return testPath.replaceAll(sep, "/").includes("js/node/test/parallel/");
|
||||
function isNodeTest(path) {
|
||||
// Do not run node tests on macOS x64 in CI
|
||||
// TODO: Unclear why we decided to do this?
|
||||
if (isCI && isMacOS && isX64) {
|
||||
return false;
|
||||
}
|
||||
const unixPath = path.replaceAll(sep, "/");
|
||||
return unixPath.includes("js/node/test/parallel/") || unixPath.includes("js/node/test/sequential/");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} testPath
|
||||
* @param {string} path
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isNodeSequentialTest(testPath) {
|
||||
return testPath.replaceAll(sep, "/").includes("js/node/test/sequential/");
|
||||
function isClusterTest(path) {
|
||||
const unixPath = path.replaceAll(sep, "/");
|
||||
return unixPath.includes("js/node/cluster/test-") && unixPath.endsWith(".ts");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -890,21 +899,17 @@ function isNodeSequentialTest(testPath) {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isTest(path) {
|
||||
if (isNodeParallelTest(path) && targetDoesRunNodeTests()) return true;
|
||||
if (isNodeSequentialTest(path) && targetDoesRunNodeTests()) return true;
|
||||
if (path.replaceAll(sep, "/").startsWith("js/node/cluster/test-") && path.endsWith(".ts")) return true;
|
||||
return isTestStrict(path);
|
||||
return isNodeTest(path) || isClusterTest(path) ? true : isTestStrict(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} path
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isTestStrict(path) {
|
||||
return isJavaScript(path) && /\.test|spec\./.test(basename(path));
|
||||
}
|
||||
|
||||
function targetDoesRunNodeTests() {
|
||||
if (isMacOS && isX64) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} path
|
||||
* @returns {boolean}
|
||||
|
||||
@@ -78,27 +78,31 @@ static constexpr ASCIILiteral builtinModuleNamesSortedLength[] = {
|
||||
"inspector/promises"_s,
|
||||
"_stream_passthrough"_s,
|
||||
"diagnostics_channel"_s,
|
||||
"node:test"_s,
|
||||
};
|
||||
|
||||
namespace Bun {
|
||||
|
||||
bool isBuiltinModule(const String& namePossiblyWithNodePrefix)
|
||||
{
|
||||
// First check the original name as-is
|
||||
for (auto& builtinModule : builtinModuleNamesSortedLength) {
|
||||
if (namePossiblyWithNodePrefix == builtinModule)
|
||||
return true;
|
||||
}
|
||||
|
||||
// If no match found and the name has a "node:" prefix, try without the prefix
|
||||
String name = namePossiblyWithNodePrefix;
|
||||
if (name.startsWith("node:"_s)) {
|
||||
name = name.substringSharingImpl(5);
|
||||
|
||||
// bun doesn't have `node:test` as of writing, but this makes sure that
|
||||
// `node:module` is compatible (`test/parallel/test-module-isBuiltin.js`)
|
||||
if (name == "test"_s) {
|
||||
return true;
|
||||
// Check again with the prefix removed
|
||||
for (auto& builtinModule : builtinModuleNamesSortedLength) {
|
||||
if (name == builtinModule)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& builtinModule : builtinModuleNamesSortedLength) {
|
||||
if (name == builtinModule)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1781,7 +1781,7 @@ pub const ModuleLoader = struct {
|
||||
.already_bundled = true,
|
||||
.hash = 0,
|
||||
.bytecode_cache = if (bytecode_slice.len > 0) bytecode_slice.ptr else null,
|
||||
.bytecode_cache_size = if (bytecode_slice.len > 0) bytecode_slice.len else 0,
|
||||
.bytecode_cache_size = bytecode_slice.len,
|
||||
.is_commonjs_module = parse_result.already_bundled.isCommonJS(),
|
||||
};
|
||||
}
|
||||
@@ -2902,7 +2902,8 @@ pub const HardcodedModule = enum {
|
||||
.{ "stream/promises", .{ .path = "stream/promises" } },
|
||||
.{ "stream/web", .{ .path = "stream/web" } },
|
||||
.{ "string_decoder", .{ .path = "string_decoder" } },
|
||||
// .{ "test", .{ .path = "test" } },
|
||||
// Node.js does not support "test", only "node:test"
|
||||
// .{ "test", .{ .path = "node:test" } },
|
||||
.{ "timers", .{ .path = "timers" } },
|
||||
.{ "timers/promises", .{ .path = "timers/promises" } },
|
||||
.{ "tls", .{ .path = "tls" } },
|
||||
|
||||
@@ -115,6 +115,7 @@ static constexpr ASCIILiteral builtinModuleNames[] = {
|
||||
"worker_threads"_s,
|
||||
"ws"_s,
|
||||
"zlib"_s,
|
||||
"node:test"_s,
|
||||
};
|
||||
|
||||
template<std::size_t N, class T> consteval std::size_t countof(T (&)[N])
|
||||
|
||||
@@ -75,6 +75,7 @@ const builtinModules = [
|
||||
"wasi",
|
||||
"worker_threads",
|
||||
"zlib",
|
||||
"node:test",
|
||||
];
|
||||
|
||||
export default {
|
||||
|
||||
@@ -1,38 +1,621 @@
|
||||
// Hardcoded module "node:test"
|
||||
// This follows the Node.js API as described in: https://nodejs.org/api/test.html
|
||||
|
||||
const { throwNotImplemented } = require("internal/shared");
|
||||
const { jest } = Bun;
|
||||
const { kEmptyObject, throwNotImplemented } = require("internal/shared");
|
||||
const Readable = require("internal/streams/readable");
|
||||
|
||||
function suite() {
|
||||
throwNotImplemented("node:test", 5090, "bun:test in available in the interim.");
|
||||
const kDefaultName = "<anonymous>";
|
||||
const kDefaultFunction = () => {};
|
||||
const kDefaultOptions = kEmptyObject;
|
||||
const kDefaultFilePath = callerSourceOrigin();
|
||||
|
||||
function run(...args: unknown[]) {
|
||||
throwNotImplemented("run()", 5090, "Use `bun:test` in the interim.");
|
||||
}
|
||||
|
||||
function test() {
|
||||
throwNotImplemented("node:test", 5090, "bun:test in available in the interim.");
|
||||
function mock(...args: unknown[]) {
|
||||
throwNotImplemented("mock()", 5090, "Use `bun:test` in the interim.");
|
||||
}
|
||||
|
||||
function before() {
|
||||
throwNotImplemented("node:test", 5090, "bun:test in available in the interim.");
|
||||
/**
|
||||
* @link https://nodejs.org/api/test.html#class-mockfunctioncontext
|
||||
*/
|
||||
class MockFunctionContext {
|
||||
constructor() {
|
||||
throwNotImplemented("new MockFunctionContext()", 5090, "Use `bun:test` in the interim.");
|
||||
}
|
||||
|
||||
get calls() {
|
||||
throwNotImplemented("calls()", 5090, "Use `bun:test` in the interim.");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
callCount() {
|
||||
throwNotImplemented("callCount()", 5090, "Use `bun:test` in the interim.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
mockImplementation(fn: Function) {
|
||||
throwNotImplemented("mockImplementation()", 5090, "Use `bun:test` in the interim.");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
mockImplementationOnce(fn: Function, onCall?: unknown) {
|
||||
throwNotImplemented("mockImplementationOnce()", 5090, "Use `bun:test` in the interim.");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
resetCalls() {
|
||||
throwNotImplemented("resetCalls()", 5090, "Use `bun:test` in the interim.");
|
||||
}
|
||||
|
||||
restore() {
|
||||
throwNotImplemented("restore()", 5090, "Use `bun:test` in the interim.");
|
||||
}
|
||||
}
|
||||
|
||||
function after() {
|
||||
throwNotImplemented("node:test", 5090, "bun:test in available in the interim.");
|
||||
/**
|
||||
* @link https://nodejs.org/api/test.html#class-mockmodulecontext
|
||||
*/
|
||||
class MockModuleContext {
|
||||
constructor() {
|
||||
throwNotImplemented("new MockModuleContext()", 5090, "Use `bun:test` in the interim.");
|
||||
}
|
||||
|
||||
restore() {
|
||||
throwNotImplemented("restore()", 5090, "Use `bun:test` in the interim.");
|
||||
}
|
||||
}
|
||||
|
||||
function beforeEach() {
|
||||
throwNotImplemented("node:test", 5090, "bun:test in available in the interim.");
|
||||
/**
|
||||
* @link https://nodejs.org/api/test.html#class-mocktracker
|
||||
*/
|
||||
class MockTracker {
|
||||
constructor() {
|
||||
throwNotImplemented("new MockTracker()", 5090, "Use `bun:test` in the interim.");
|
||||
}
|
||||
|
||||
fn(original: unknown, implementation: unknown, options: unknown) {
|
||||
throwNotImplemented("fn()", 5090, "Use `bun:test` in the interim.");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
method(object: unknown, methodName: unknown, implementation: unknown, options: unknown) {
|
||||
throwNotImplemented("method()", 5090, "Use `bun:test` in the interim.");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getter(original: unknown, implementation: unknown, options: unknown) {
|
||||
throwNotImplemented("getter()", 5090, "Use `bun:test` in the interim.");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
setter(original: unknown, implementation: unknown, options: unknown) {
|
||||
throwNotImplemented("setter()", 5090, "Use `bun:test` in the interim.");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
module(specifier: unknown, options: unknown) {
|
||||
throwNotImplemented("module()", 5090, "Use `bun:test` in the interim.");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
reset() {
|
||||
throwNotImplemented("reset()", 5090, "Use `bun:test` in the interim.");
|
||||
}
|
||||
|
||||
restoreAll() {
|
||||
throwNotImplemented("restoreAll()", 5090, "Use `bun:test` in the interim.");
|
||||
}
|
||||
}
|
||||
|
||||
function afterEach() {
|
||||
throwNotImplemented("node:test", 5090, "bun:test in available in the interim.");
|
||||
class MockTimers {
|
||||
constructor() {
|
||||
throwNotImplemented("new MockTimers()", 5090, "Use `bun:test` in the interim.");
|
||||
}
|
||||
|
||||
enable(options: unknown) {
|
||||
throwNotImplemented("enable()", 5090, "Use `bun:test` in the interim.");
|
||||
}
|
||||
|
||||
reset() {
|
||||
throwNotImplemented("reset()", 5090, "Use `bun:test` in the interim.");
|
||||
}
|
||||
|
||||
tick(milliseconds: unknown) {
|
||||
throwNotImplemented("tick()", 5090, "Use `bun:test` in the interim.");
|
||||
}
|
||||
|
||||
runAll() {
|
||||
throwNotImplemented("runAll()", 5090, "Use `bun:test` in the interim.");
|
||||
}
|
||||
|
||||
setTime(milliseconds: unknown) {
|
||||
throwNotImplemented("setTime()", 5090, "Use `bun:test` in the interim.");
|
||||
}
|
||||
|
||||
[Symbol.dispose]() {
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
suite,
|
||||
test,
|
||||
describe: suite,
|
||||
it: test,
|
||||
before,
|
||||
after,
|
||||
beforeEach,
|
||||
afterEach,
|
||||
/**
|
||||
* @link https://nodejs.org/api/test.html#class-testsstream
|
||||
*/
|
||||
class TestsStream extends Readable {
|
||||
constructor() {
|
||||
super();
|
||||
throwNotImplemented("new TestsStream()", 5090, "Use `bun:test` in the interim.");
|
||||
}
|
||||
}
|
||||
|
||||
function fileSnapshot(value: unknown, path: string, options: { serializers?: Function[] } = kEmptyObject) {
|
||||
throwNotImplemented("fileSnapshot()", 5090, "Use `bun:test` in the interim.");
|
||||
}
|
||||
|
||||
function snapshot(value: unknown, options: { serializers?: Function[] } = kEmptyObject) {
|
||||
throwNotImplemented("snapshot()", 5090, "Use `bun:test` in the interim.");
|
||||
}
|
||||
|
||||
function register(name: string, fn: Function) {
|
||||
throwNotImplemented("register()", 5090, "Use `bun:test` in the interim.");
|
||||
}
|
||||
|
||||
const assert = {
|
||||
...require("node:assert"),
|
||||
fileSnapshot,
|
||||
snapshot,
|
||||
// register,
|
||||
};
|
||||
|
||||
// Delete deprecated methods on assert (required to pass node's tests)
|
||||
delete assert.AssertionError;
|
||||
delete assert.CallTracker;
|
||||
delete assert.strict;
|
||||
|
||||
/**
|
||||
* @link https://nodejs.org/api/test.html#class-suitecontext
|
||||
*/
|
||||
class SuiteContext {
|
||||
#name: string | undefined;
|
||||
#filePath: string | undefined;
|
||||
#abortController?: AbortController;
|
||||
|
||||
constructor(name: string | undefined, filePath: string | undefined) {
|
||||
this.#name = name;
|
||||
this.#filePath = filePath || kDefaultFilePath;
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return this.#name!;
|
||||
}
|
||||
|
||||
get filePath(): string {
|
||||
return this.#filePath!;
|
||||
}
|
||||
|
||||
get signal(): AbortSignal {
|
||||
if (this.#abortController === undefined) {
|
||||
this.#abortController = new AbortController();
|
||||
}
|
||||
return this.#abortController.signal;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @link https://nodejs.org/api/test.html#class-testcontext
|
||||
*/
|
||||
class TestContext {
|
||||
#insideTest: boolean;
|
||||
#name: string | undefined;
|
||||
#filePath: string | undefined;
|
||||
#parent?: TestContext;
|
||||
#abortController?: AbortController;
|
||||
|
||||
constructor(
|
||||
insideTest: boolean,
|
||||
name: string | undefined,
|
||||
filePath: string | undefined,
|
||||
parent: TestContext | undefined,
|
||||
) {
|
||||
this.#insideTest = insideTest;
|
||||
this.#name = name;
|
||||
this.#filePath = filePath || parent?.filePath || kDefaultFilePath;
|
||||
this.#parent = parent;
|
||||
}
|
||||
|
||||
get signal(): AbortSignal {
|
||||
if (this.#abortController === undefined) {
|
||||
this.#abortController = new AbortController();
|
||||
}
|
||||
return this.#abortController.signal;
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return this.#name!;
|
||||
}
|
||||
|
||||
get fullName(): string {
|
||||
let fullName = this.#name;
|
||||
let parent = this.#parent;
|
||||
while (parent && parent.name) {
|
||||
fullName = `${parent.name} > ${fullName}`;
|
||||
parent = parent.#parent;
|
||||
}
|
||||
return fullName!;
|
||||
}
|
||||
|
||||
get filePath(): string {
|
||||
return this.#filePath!;
|
||||
}
|
||||
|
||||
diagnostic(message: string) {
|
||||
console.log(message);
|
||||
}
|
||||
|
||||
plan(count: number, options: { wait?: boolean } = kEmptyObject) {
|
||||
throwNotImplemented("plan()", 5090, "Use `bun:test` in the interim.");
|
||||
}
|
||||
|
||||
get assert() {
|
||||
return assert;
|
||||
}
|
||||
|
||||
get mock() {
|
||||
throwNotImplemented("mock", 5090, "Use `bun:test` in the interim.");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
runOnly(value?: boolean) {
|
||||
throwNotImplemented("runOnly()", 5090, "Use `bun:test` in the interim.");
|
||||
}
|
||||
|
||||
skip(message?: string) {
|
||||
throwNotImplemented("skip()", 5090, "Use `bun:test` in the interim.");
|
||||
}
|
||||
|
||||
todo(message?: string) {
|
||||
throwNotImplemented("todo()", 5090, "Use `bun:test` in the interim.");
|
||||
}
|
||||
|
||||
before(arg0: unknown, arg1: unknown) {
|
||||
const { fn, options } = createHook(arg0, arg1);
|
||||
const { beforeAll } = bunTest(this);
|
||||
beforeAll(fn);
|
||||
}
|
||||
|
||||
after(arg0: unknown, arg1: unknown) {
|
||||
const { fn, options } = createHook(arg0, arg1);
|
||||
const { afterAll } = bunTest(this);
|
||||
afterAll(fn);
|
||||
}
|
||||
|
||||
beforeEach(arg0: unknown, arg1: unknown) {
|
||||
const { fn, options } = createHook(arg0, arg1);
|
||||
const { beforeEach } = bunTest(this);
|
||||
beforeEach(fn);
|
||||
}
|
||||
|
||||
afterEach(arg0: unknown, arg1: unknown) {
|
||||
const { fn, options } = createHook(arg0, arg1);
|
||||
const { afterEach } = bunTest(this);
|
||||
afterEach(fn);
|
||||
}
|
||||
|
||||
waitFor(condition: unknown, options: { timeout?: number } = kEmptyObject) {
|
||||
throwNotImplemented("waitFor()", 5090, "Use `bun:test` in the interim.");
|
||||
}
|
||||
|
||||
test(arg0: unknown, arg1: unknown, arg2: unknown) {
|
||||
const { name, fn, options } = createTest(arg0, arg1, arg2);
|
||||
|
||||
if (this.#insideTest) {
|
||||
throwNotImplemented("test() inside another test()", 5090, "Use `bun:test` in the interim.");
|
||||
}
|
||||
|
||||
const { test } = bunTest(this);
|
||||
if (options.only) {
|
||||
test.only(name, fn);
|
||||
} else if (options.todo) {
|
||||
test.todo(name, fn);
|
||||
} else if (options.skip) {
|
||||
test.skip(name, fn);
|
||||
} else {
|
||||
test(name, fn);
|
||||
}
|
||||
}
|
||||
|
||||
describe(arg0: unknown, arg1: unknown, arg2: unknown) {
|
||||
const { name, fn, options } = createDescribe(arg0, arg1, arg2);
|
||||
|
||||
if (this.#insideTest) {
|
||||
throwNotImplemented("describe() inside another test()", 5090, "Use `bun:test` in the interim.");
|
||||
}
|
||||
|
||||
const { describe } = bunTest(this);
|
||||
describe(name, fn);
|
||||
}
|
||||
}
|
||||
|
||||
function bunTest(ctx: SuiteContext | TestContext) {
|
||||
return jest(ctx.filePath);
|
||||
}
|
||||
|
||||
let ctx = new TestContext(false, undefined, kDefaultFilePath, undefined);
|
||||
|
||||
function describe(arg0: unknown, arg1: unknown, arg2: unknown) {
|
||||
const { name, fn, options } = createDescribe(arg0, arg1, arg2);
|
||||
const { describe } = bunTest(ctx);
|
||||
describe(name, fn);
|
||||
}
|
||||
|
||||
describe.skip = function (arg0: unknown, arg1: unknown, arg2: unknown) {
|
||||
const { name, fn, options } = createDescribe(arg0, arg1, arg2);
|
||||
const { describe } = bunTest(ctx);
|
||||
describe.skip(name, fn);
|
||||
};
|
||||
|
||||
describe.todo = function (arg0: unknown, arg1: unknown, arg2: unknown) {
|
||||
const { name, fn, options } = createDescribe(arg0, arg1, arg2);
|
||||
const { describe } = bunTest(ctx);
|
||||
describe.todo(name, fn);
|
||||
};
|
||||
|
||||
describe.only = function (arg0: unknown, arg1: unknown, arg2: unknown) {
|
||||
const { name, fn, options } = createDescribe(arg0, arg1, arg2);
|
||||
const { describe } = bunTest(ctx);
|
||||
describe.only(name, fn);
|
||||
};
|
||||
|
||||
function test(arg0: unknown, arg1: unknown, arg2: unknown) {
|
||||
const { name, fn, options } = createTest(arg0, arg1, arg2);
|
||||
const { test } = bunTest(ctx);
|
||||
test(name, fn, options);
|
||||
}
|
||||
|
||||
test.skip = function (arg0: unknown, arg1: unknown, arg2: unknown) {
|
||||
const { name, fn, options } = createTest(arg0, arg1, arg2);
|
||||
const { test } = bunTest(ctx);
|
||||
test.skip(name, fn, options);
|
||||
};
|
||||
|
||||
test.todo = function (arg0: unknown, arg1: unknown, arg2: unknown) {
|
||||
const { name, fn, options } = createTest(arg0, arg1, arg2);
|
||||
const { test } = bunTest(ctx);
|
||||
test.todo(name, fn, options);
|
||||
};
|
||||
|
||||
test.only = function (arg0: unknown, arg1: unknown, arg2: unknown) {
|
||||
const { name, fn, options } = createTest(arg0, arg1, arg2);
|
||||
const { test } = bunTest(ctx);
|
||||
test.only(name, fn, options);
|
||||
};
|
||||
|
||||
function before(arg0: unknown, arg1: unknown) {
|
||||
const { fn, options } = createHook(arg0, arg1);
|
||||
const { beforeAll } = bunTest(ctx);
|
||||
beforeAll(fn);
|
||||
}
|
||||
|
||||
function after(arg0: unknown, arg1: unknown) {
|
||||
const { fn, options } = createHook(arg0, arg1);
|
||||
const { afterAll } = bunTest(ctx);
|
||||
afterAll(fn);
|
||||
}
|
||||
|
||||
function beforeEach(arg0: unknown, arg1: unknown) {
|
||||
const { fn, options } = createHook(arg0, arg1);
|
||||
const { beforeEach } = bunTest(ctx);
|
||||
beforeEach(fn);
|
||||
}
|
||||
|
||||
function afterEach(arg0: unknown, arg1: unknown) {
|
||||
const { fn, options } = createHook(arg0, arg1);
|
||||
const { afterEach } = bunTest(ctx);
|
||||
afterEach(fn);
|
||||
}
|
||||
|
||||
function isBuiltinModule(filePath: string) {
|
||||
return filePath.startsWith("node:") || filePath.startsWith("bun:") || filePath.startsWith("[native code]");
|
||||
}
|
||||
|
||||
function callerSourceOrigin(): string {
|
||||
const error = new Error();
|
||||
const originalPrepareStackTrace = Error.prepareStackTrace;
|
||||
let origin: string | undefined;
|
||||
Error.prepareStackTrace = (_, stack) => {
|
||||
origin = stack
|
||||
.find(s => {
|
||||
const filePath = s.getFileName();
|
||||
if (filePath && !isBuiltinModule(filePath)) {
|
||||
return filePath;
|
||||
}
|
||||
return undefined;
|
||||
})
|
||||
?.getFileName();
|
||||
};
|
||||
error.stack;
|
||||
Error.prepareStackTrace = originalPrepareStackTrace;
|
||||
if (!origin) {
|
||||
throw new Error("Failed to get caller source origin");
|
||||
}
|
||||
return origin;
|
||||
}
|
||||
|
||||
function parseTestOptions(arg0: unknown, arg1: unknown, arg2: unknown) {
|
||||
let name: string;
|
||||
let options: TestOptions;
|
||||
let fn: TestFn;
|
||||
|
||||
if (typeof arg0 === "function") {
|
||||
name = arg0.name || kDefaultName;
|
||||
fn = arg0 as TestFn;
|
||||
if (typeof arg1 === "object") {
|
||||
options = arg1 as TestOptions;
|
||||
} else {
|
||||
options = kDefaultOptions;
|
||||
}
|
||||
} else if (typeof arg0 === "string") {
|
||||
name = arg0;
|
||||
if (typeof arg1 === "object") {
|
||||
options = arg1 as TestOptions;
|
||||
if (typeof arg2 === "function") {
|
||||
fn = arg2 as TestFn;
|
||||
} else {
|
||||
fn = kDefaultFunction;
|
||||
}
|
||||
} else if (typeof arg1 === "function") {
|
||||
fn = arg1 as TestFn;
|
||||
options = kDefaultOptions;
|
||||
} else {
|
||||
fn = kDefaultFunction;
|
||||
options = kDefaultOptions;
|
||||
}
|
||||
} else {
|
||||
name = kDefaultName;
|
||||
fn = kDefaultFunction;
|
||||
options = kDefaultOptions;
|
||||
}
|
||||
|
||||
return { name, options, fn };
|
||||
}
|
||||
|
||||
function createTest(arg0: unknown, arg1: unknown, arg2: unknown) {
|
||||
const { name, options, fn } = parseTestOptions(arg0, arg1, arg2);
|
||||
|
||||
const originalContext = ctx;
|
||||
const context = new TestContext(true, name, ctx.filePath, originalContext);
|
||||
|
||||
const runTest = (done: (error?: unknown) => void) => {
|
||||
ctx = context;
|
||||
const endTest = (error?: unknown) => {
|
||||
try {
|
||||
done(error);
|
||||
} finally {
|
||||
ctx = originalContext;
|
||||
}
|
||||
};
|
||||
|
||||
let result: unknown;
|
||||
try {
|
||||
result = fn(context);
|
||||
} catch (error) {
|
||||
endTest(error);
|
||||
return;
|
||||
}
|
||||
if (result instanceof Promise) {
|
||||
(result as Promise<unknown>).then(() => endTest()).catch(error => endTest(error));
|
||||
} else {
|
||||
endTest();
|
||||
}
|
||||
};
|
||||
|
||||
return { name, options, fn: runTest };
|
||||
}
|
||||
|
||||
function createDescribe(arg0: unknown, arg1: unknown, arg2: unknown) {
|
||||
const { name, fn, options } = parseTestOptions(arg0, arg1, arg2);
|
||||
|
||||
const originalContext = ctx;
|
||||
const context = new TestContext(false, name, ctx.filePath, originalContext);
|
||||
|
||||
const runDescribe = () => {
|
||||
ctx = context;
|
||||
const endDescribe = () => {
|
||||
ctx = originalContext;
|
||||
};
|
||||
|
||||
try {
|
||||
return fn(context);
|
||||
} finally {
|
||||
endDescribe();
|
||||
}
|
||||
};
|
||||
|
||||
return { name, options, fn: runDescribe };
|
||||
}
|
||||
|
||||
function parseHookOptions(arg0: unknown, arg1: unknown) {
|
||||
let fn: HookFn | undefined;
|
||||
let options: HookOptions;
|
||||
|
||||
if (typeof arg0 === "function") {
|
||||
fn = arg0 as HookFn;
|
||||
} else {
|
||||
fn = kDefaultFunction;
|
||||
}
|
||||
|
||||
if (typeof arg1 === "object") {
|
||||
options = arg1 as HookOptions;
|
||||
} else {
|
||||
options = kDefaultOptions;
|
||||
}
|
||||
|
||||
return { fn, options };
|
||||
}
|
||||
|
||||
function createHook(arg0: unknown, arg1: unknown) {
|
||||
const { fn, options } = parseHookOptions(arg0, arg1);
|
||||
|
||||
const runHook = (done: (error?: unknown) => void) => {
|
||||
let result: unknown;
|
||||
try {
|
||||
result = fn();
|
||||
} catch (error) {
|
||||
done(error);
|
||||
return;
|
||||
}
|
||||
if (result instanceof Promise) {
|
||||
(result as Promise<unknown>).then(() => done()).catch(error => done(error));
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
};
|
||||
|
||||
return { options, fn: runHook };
|
||||
}
|
||||
|
||||
type TestFn = (ctx: TestContext) => unknown | Promise<unknown>;
|
||||
type HookFn = () => unknown | Promise<unknown>;
|
||||
|
||||
type TestOptions = {
|
||||
concurrency?: number | boolean | null;
|
||||
only?: boolean;
|
||||
signal?: AbortSignal;
|
||||
skip?: boolean | string;
|
||||
todo?: boolean | string;
|
||||
timeout?: number;
|
||||
plan?: number;
|
||||
};
|
||||
|
||||
type HookOptions = {
|
||||
signal?: AbortSignal;
|
||||
timeout?: number;
|
||||
};
|
||||
|
||||
function setDefaultSnapshotSerializer(serializers: unknown[]) {
|
||||
throwNotImplemented("setDefaultSnapshotSerializer()", 5090, "Use `bun:test` in the interim.");
|
||||
}
|
||||
|
||||
function setResolveSnapshotPath(fn: unknown) {
|
||||
throwNotImplemented("setResolveSnapshotPath()", 5090, "Use `bun:test` in the interim.");
|
||||
}
|
||||
|
||||
test.describe = describe;
|
||||
test.suite = describe;
|
||||
test.test = test;
|
||||
test.it = test;
|
||||
test.before = before;
|
||||
test.after = after;
|
||||
test.beforeEach = beforeEach;
|
||||
test.afterEach = afterEach;
|
||||
test.assert = assert;
|
||||
test.snapshot = {
|
||||
setDefaultSnapshotSerializer,
|
||||
setResolveSnapshotPath,
|
||||
};
|
||||
test.run = run;
|
||||
test.mock = mock;
|
||||
|
||||
export default test;
|
||||
|
||||
@@ -221,6 +221,7 @@ pub const ExternalModules = struct {
|
||||
"stream",
|
||||
"string_decoder",
|
||||
"sys",
|
||||
"test",
|
||||
"timers",
|
||||
"tls",
|
||||
"trace_events",
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"bun-plugin-yaml": "0.0.1",
|
||||
"comlink": "4.4.1",
|
||||
"commander": "12.1.0",
|
||||
"detect-libc": "^2.0.3",
|
||||
"detect-libc": "2.0.3",
|
||||
"devalue": "5.1.1",
|
||||
"duckdb": "1.1.3",
|
||||
"es-module-lexer": "1.3.0",
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/**
|
||||
* @note this file patches `node:test` via the require cache.
|
||||
*/
|
||||
import { AnyFunction } from "bun";
|
||||
import os from "node:os";
|
||||
import { hideFromStackTrace } from "harness";
|
||||
import assertNode from "node:assert";
|
||||
|
||||
@@ -130,11 +128,11 @@ export function createTest(path: string) {
|
||||
}, exact);
|
||||
}
|
||||
|
||||
function mustCallAtLeast(fn: AnyFunction, minimum: number) {
|
||||
function mustCallAtLeast(fn: unknown, minimum: number) {
|
||||
return _mustCallInner(fn, minimum, "minimum");
|
||||
}
|
||||
|
||||
function _mustCallInner(fn: AnyFunction, criteria = 1, field: string) {
|
||||
function _mustCallInner(fn: unknown, criteria = 1, field: string) {
|
||||
// @ts-ignore
|
||||
if (process._exiting) throw new Error("Cannot use common.mustCall*() in process exit handler");
|
||||
if (typeof fn === "number") {
|
||||
@@ -266,129 +264,3 @@ export function createTest(path: string) {
|
||||
declare namespace Bun {
|
||||
function jest(path: string): typeof import("bun:test");
|
||||
}
|
||||
|
||||
const normalized = os.platform() === "win32" ? Bun.main.replaceAll("\\", "/") : Bun.main;
|
||||
if (normalized.includes("node/test/parallel")) {
|
||||
function createMockNodeTestModule() {
|
||||
interface TestError extends Error {
|
||||
testStack: string[];
|
||||
}
|
||||
type Context = {
|
||||
filename: string;
|
||||
testStack: string[];
|
||||
failures: Error[];
|
||||
successes: number;
|
||||
addFailure(err: unknown): TestError;
|
||||
recordSuccess(): void;
|
||||
};
|
||||
const contexts: Record</* requiring file */ string, Context> = {};
|
||||
|
||||
// @ts-ignore
|
||||
let activeSuite: Context = undefined;
|
||||
|
||||
function createContext(key: string): Context {
|
||||
return {
|
||||
filename: key, // duplicate for ease-of-use
|
||||
// entered each time describe, it, etc is called
|
||||
testStack: [],
|
||||
failures: [],
|
||||
successes: 0,
|
||||
addFailure(err: unknown) {
|
||||
const error: TestError = (err instanceof Error ? err : new Error(err as any)) as any;
|
||||
error.testStack = this.testStack;
|
||||
const testMessage = `Test failed: ${this.testStack.join(" > ")}`;
|
||||
error.message = testMessage + "\n" + error.message;
|
||||
this.failures.push(error);
|
||||
console.error(error);
|
||||
return error;
|
||||
},
|
||||
recordSuccess() {
|
||||
const fullname = this.testStack.join(" > ");
|
||||
console.log("✅ Test passed:", fullname);
|
||||
this.successes++;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getContext() {
|
||||
const key: string = Bun.main; // module.parent?.filename ?? require.main?.filename ?? __filename;
|
||||
return (activeSuite = contexts[key] ??= createContext(key));
|
||||
}
|
||||
|
||||
async function test(
|
||||
label: string | Function,
|
||||
optionsOrFn: Record<string, any> | Function,
|
||||
fn?: Function | undefined,
|
||||
) {
|
||||
let options = optionsOrFn;
|
||||
if (arguments.length === 2) {
|
||||
assertNode.equal(typeof optionsOrFn, "function", "Second argument to test() must be a function.");
|
||||
fn = optionsOrFn as Function;
|
||||
options = {};
|
||||
}
|
||||
if (typeof fn !== "function" && typeof label === "function") {
|
||||
fn = label;
|
||||
label = fn.name;
|
||||
options = {};
|
||||
}
|
||||
|
||||
const ctx = getContext();
|
||||
const { skip } = options;
|
||||
|
||||
if (skip) return;
|
||||
try {
|
||||
ctx.testStack.push(label as string);
|
||||
await fn();
|
||||
ctx.recordSuccess();
|
||||
} catch (err) {
|
||||
const error = ctx.addFailure(err);
|
||||
throw error;
|
||||
} finally {
|
||||
ctx.testStack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
function describe(labelOrFn: string | Function, maybeFnOrOptions?: Function, maybeFn?: Function) {
|
||||
const [label, fn] =
|
||||
typeof labelOrFn == "function" ? [labelOrFn.name, labelOrFn] : [labelOrFn, maybeFn ?? maybeFnOrOptions];
|
||||
if (typeof fn !== "function") throw new TypeError("Second argument to describe() must be a function.");
|
||||
|
||||
getContext().testStack.push(label);
|
||||
try {
|
||||
fn();
|
||||
} catch (e) {
|
||||
getContext().addFailure(e);
|
||||
throw e;
|
||||
} finally {
|
||||
getContext().testStack.pop();
|
||||
}
|
||||
|
||||
const failures = getContext().failures.length;
|
||||
const successes = getContext().successes;
|
||||
console.error(`describe("${label}") finished with ${successes} passed and ${failures} failed tests.`);
|
||||
if (failures > 0) {
|
||||
throw new Error(`${failures} tests failed.`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
test,
|
||||
it: test,
|
||||
describe,
|
||||
suite: describe,
|
||||
};
|
||||
}
|
||||
|
||||
require.cache["node:test"] ??= {
|
||||
exports: createMockNodeTestModule(),
|
||||
loaded: true,
|
||||
isPreloading: false,
|
||||
id: "node:test",
|
||||
parent: require.main,
|
||||
filename: "node:test",
|
||||
children: [],
|
||||
path: "node:test",
|
||||
paths: [],
|
||||
require,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,7 +5,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", () => {
|
||||
@@ -17,6 +17,8 @@ test("isBuiltin() works", () => {
|
||||
expect(isBuiltin("events")).toBe(true);
|
||||
expect(isBuiltin("node:events")).toBe(true);
|
||||
expect(isBuiltin("node:bacon")).toBe(false);
|
||||
expect(isBuiltin("node:test")).toBe(true);
|
||||
expect(isBuiltin("test")).toBe(false); // "test" does not alias to "node:test"
|
||||
});
|
||||
|
||||
test("module.globalPaths exists", () => {
|
||||
|
||||
@@ -90,20 +90,28 @@ function parseTestFlags(filename = process.argv[1]) {
|
||||
fs.closeSync(fd);
|
||||
const source = buffer.toString('utf8', 0, bytesRead);
|
||||
|
||||
const flags = [];
|
||||
const flagStart = source.search(/\/\/ Flags:\s+--/) + 10;
|
||||
|
||||
const isNodeTest = source.includes('node:test');
|
||||
if (isNodeTest) {
|
||||
flags.push('test');
|
||||
}
|
||||
|
||||
if (flagStart === 9) {
|
||||
return [];
|
||||
return flags;
|
||||
}
|
||||
let flagEnd = source.indexOf('\n', flagStart);
|
||||
// Normalize different EOL.
|
||||
if (source[flagEnd - 1] === '\r') {
|
||||
flagEnd--;
|
||||
}
|
||||
|
||||
return source
|
||||
.substring(flagStart, flagEnd)
|
||||
.split(/\s+/)
|
||||
.filter(Boolean);
|
||||
.filter(Boolean)
|
||||
.concat(flags);
|
||||
}
|
||||
|
||||
// Check for flags. Skip this for workers (both, the `cluster` module and
|
||||
@@ -130,6 +138,10 @@ if (process.argv.length === 2 &&
|
||||
process.env.SKIP_FLAG_CHECK = "1";
|
||||
break;
|
||||
}
|
||||
if (flag === "test") {
|
||||
process.env.SKIP_FLAG_CHECK = "1";
|
||||
break;
|
||||
}
|
||||
console.log(
|
||||
'NOTE: The test started as a child_process using these flags:',
|
||||
inspect(flags),
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
'use strict';
|
||||
const { isWindows } = require('../../common');
|
||||
|
||||
const { test } = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const url = require('node:url');
|
||||
|
||||
test('invalid arguments', () => {
|
||||
for (const arg of [null, undefined, 1, {}, true]) {
|
||||
assert.throws(() => url.fileURLToPath(arg), {
|
||||
code: 'ERR_INVALID_ARG_TYPE'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test('input must be a file URL', () => {
|
||||
assert.throws(() => url.fileURLToPath('https://a/b/c'), {
|
||||
code: 'ERR_INVALID_URL_SCHEME'
|
||||
});
|
||||
});
|
||||
|
||||
test('fileURLToPath with host', () => {
|
||||
const withHost = new URL('file://host/a');
|
||||
|
||||
if (isWindows) {
|
||||
assert.strictEqual(url.fileURLToPath(withHost), '\\\\host\\a');
|
||||
} else {
|
||||
assert.throws(() => url.fileURLToPath(withHost), {
|
||||
code: 'ERR_INVALID_FILE_URL_HOST'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test('fileURLToPath with invalid path', () => {
|
||||
if (isWindows) {
|
||||
assert.throws(() => url.fileURLToPath('file:///C:/a%2F/'), {
|
||||
code: 'ERR_INVALID_FILE_URL_PATH'
|
||||
});
|
||||
assert.throws(() => url.fileURLToPath('file:///C:/a%5C/'), {
|
||||
code: 'ERR_INVALID_FILE_URL_PATH'
|
||||
});
|
||||
assert.throws(() => url.fileURLToPath('file:///?:/'), {
|
||||
code: 'ERR_INVALID_FILE_URL_PATH'
|
||||
});
|
||||
} else {
|
||||
assert.throws(() => url.fileURLToPath('file:///a%2F/'), {
|
||||
code: 'ERR_INVALID_FILE_URL_PATH'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const windowsTestCases = [
|
||||
// Lowercase ascii alpha
|
||||
{ path: 'C:\\foo', fileURL: 'file:///C:/foo' },
|
||||
// Uppercase ascii alpha
|
||||
{ path: 'C:\\FOO', fileURL: 'file:///C:/FOO' },
|
||||
// dir
|
||||
{ path: 'C:\\dir\\foo', fileURL: 'file:///C:/dir/foo' },
|
||||
// trailing separator
|
||||
{ path: 'C:\\dir\\', fileURL: 'file:///C:/dir/' },
|
||||
// dot
|
||||
{ path: 'C:\\foo.mjs', fileURL: 'file:///C:/foo.mjs' },
|
||||
// space
|
||||
{ path: 'C:\\foo bar', fileURL: 'file:///C:/foo%20bar' },
|
||||
// question mark
|
||||
{ path: 'C:\\foo?bar', fileURL: 'file:///C:/foo%3Fbar' },
|
||||
// number sign
|
||||
{ path: 'C:\\foo#bar', fileURL: 'file:///C:/foo%23bar' },
|
||||
// ampersand
|
||||
{ path: 'C:\\foo&bar', fileURL: 'file:///C:/foo&bar' },
|
||||
// equals
|
||||
{ path: 'C:\\foo=bar', fileURL: 'file:///C:/foo=bar' },
|
||||
// colon
|
||||
{ path: 'C:\\foo:bar', fileURL: 'file:///C:/foo:bar' },
|
||||
// semicolon
|
||||
{ path: 'C:\\foo;bar', fileURL: 'file:///C:/foo;bar' },
|
||||
// percent
|
||||
{ path: 'C:\\foo%bar', fileURL: 'file:///C:/foo%25bar' },
|
||||
// backslash
|
||||
{ path: 'C:\\foo\\bar', fileURL: 'file:///C:/foo/bar' },
|
||||
// backspace
|
||||
{ path: 'C:\\foo\bbar', fileURL: 'file:///C:/foo%08bar' },
|
||||
// tab
|
||||
{ path: 'C:\\foo\tbar', fileURL: 'file:///C:/foo%09bar' },
|
||||
// newline
|
||||
{ path: 'C:\\foo\nbar', fileURL: 'file:///C:/foo%0Abar' },
|
||||
// carriage return
|
||||
{ path: 'C:\\foo\rbar', fileURL: 'file:///C:/foo%0Dbar' },
|
||||
// latin1
|
||||
{ path: 'C:\\fóóbàr', fileURL: 'file:///C:/f%C3%B3%C3%B3b%C3%A0r' },
|
||||
// Euro sign (BMP code point)
|
||||
{ path: 'C:\\€', fileURL: 'file:///C:/%E2%82%AC' },
|
||||
// Rocket emoji (non-BMP code point)
|
||||
{ path: 'C:\\🚀', fileURL: 'file:///C:/%F0%9F%9A%80' },
|
||||
// UNC path (see https://docs.microsoft.com/en-us/archive/blogs/ie/file-uris-in-windows)
|
||||
{ path: '\\\\nas\\My Docs\\File.doc', fileURL: 'file://nas/My%20Docs/File.doc' },
|
||||
];
|
||||
|
||||
const posixTestCases = [
|
||||
// Lowercase ascii alpha
|
||||
{ path: '/foo', fileURL: 'file:///foo' },
|
||||
// Uppercase ascii alpha
|
||||
{ path: '/FOO', fileURL: 'file:///FOO' },
|
||||
// dir
|
||||
{ path: '/dir/foo', fileURL: 'file:///dir/foo' },
|
||||
// trailing separator
|
||||
{ path: '/dir/', fileURL: 'file:///dir/' },
|
||||
// dot
|
||||
{ path: '/foo.mjs', fileURL: 'file:///foo.mjs' },
|
||||
// space
|
||||
{ path: '/foo bar', fileURL: 'file:///foo%20bar' },
|
||||
// question mark
|
||||
{ path: '/foo?bar', fileURL: 'file:///foo%3Fbar' },
|
||||
// number sign
|
||||
{ path: '/foo#bar', fileURL: 'file:///foo%23bar' },
|
||||
// ampersand
|
||||
{ path: '/foo&bar', fileURL: 'file:///foo&bar' },
|
||||
// equals
|
||||
{ path: '/foo=bar', fileURL: 'file:///foo=bar' },
|
||||
// colon
|
||||
{ path: '/foo:bar', fileURL: 'file:///foo:bar' },
|
||||
// semicolon
|
||||
{ path: '/foo;bar', fileURL: 'file:///foo;bar' },
|
||||
// percent
|
||||
{ path: '/foo%bar', fileURL: 'file:///foo%25bar' },
|
||||
// backslash
|
||||
{ path: '/foo\\bar', fileURL: 'file:///foo%5Cbar' },
|
||||
// backspace
|
||||
{ path: '/foo\bbar', fileURL: 'file:///foo%08bar' },
|
||||
// tab
|
||||
{ path: '/foo\tbar', fileURL: 'file:///foo%09bar' },
|
||||
// newline
|
||||
{ path: '/foo\nbar', fileURL: 'file:///foo%0Abar' },
|
||||
// carriage return
|
||||
{ path: '/foo\rbar', fileURL: 'file:///foo%0Dbar' },
|
||||
// latin1
|
||||
{ path: '/fóóbàr', fileURL: 'file:///f%C3%B3%C3%B3b%C3%A0r' },
|
||||
// Euro sign (BMP code point)
|
||||
{ path: '/€', fileURL: 'file:///%E2%82%AC' },
|
||||
// Rocket emoji (non-BMP code point)
|
||||
{ path: '/🚀', fileURL: 'file:///%F0%9F%9A%80' },
|
||||
];
|
||||
|
||||
test('fileURLToPath with windows path', { skip: !isWindows }, () => {
|
||||
|
||||
for (const { path, fileURL } of windowsTestCases) {
|
||||
const fromString = url.fileURLToPath(fileURL, { windows: true });
|
||||
assert.strictEqual(fromString, path);
|
||||
const fromURL = url.fileURLToPath(new URL(fileURL), { windows: true });
|
||||
assert.strictEqual(fromURL, path);
|
||||
}
|
||||
});
|
||||
|
||||
test('fileURLToPath with posix path', { skip: isWindows }, () => {
|
||||
for (const { path, fileURL } of posixTestCases) {
|
||||
const fromString = url.fileURLToPath(fileURL, { windows: false });
|
||||
assert.strictEqual(fromString, path);
|
||||
const fromURL = url.fileURLToPath(new URL(fileURL), { windows: false });
|
||||
assert.strictEqual(fromURL, path);
|
||||
}
|
||||
});
|
||||
|
||||
const defaultTestCases = isWindows ? windowsTestCases : posixTestCases;
|
||||
|
||||
test('options is null', () => {
|
||||
const whenNullActual = url.fileURLToPath(new URL(defaultTestCases[0].fileURL), null);
|
||||
assert.strictEqual(whenNullActual, defaultTestCases[0].path);
|
||||
});
|
||||
|
||||
test('defaultTestCases', () => {
|
||||
for (const { path, fileURL } of defaultTestCases) {
|
||||
const fromString = url.fileURLToPath(fileURL);
|
||||
assert.strictEqual(fromString, path);
|
||||
const fromURL = url.fileURLToPath(new URL(fileURL));
|
||||
assert.strictEqual(fromURL, path);
|
||||
}
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
require('../common');
|
||||
const { test } = require('node:test');
|
||||
|
||||
// This test ensures Math functions don't fail with an "illegal instruction"
|
||||
// error on ARM devices (primarily on the Raspberry Pi 1)
|
||||
@@ -7,9 +8,11 @@ require('../common');
|
||||
// and https://code.google.com/p/v8/issues/detail?id=4019
|
||||
|
||||
// Iterate over all Math functions
|
||||
Object.getOwnPropertyNames(Math).forEach((functionName) => {
|
||||
if (!/[A-Z]/.test(functionName)) {
|
||||
// The function names don't have capital letters.
|
||||
Math[functionName](-0.5);
|
||||
}
|
||||
test('Iterate over all Math functions', () => {
|
||||
Object.getOwnPropertyNames(Math).forEach((functionName) => {
|
||||
if (!/[A-Z]/.test(functionName)) {
|
||||
// The function names don't have capital letters.
|
||||
Math[functionName](-0.5);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use strict';
|
||||
require('../../common');
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
const { describe, it } = require('node:test');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use strict';
|
||||
const { hasCrypto } = require('../../common');
|
||||
const { hasCrypto } = require('../common');
|
||||
const { test } = require('node:test');
|
||||
const assert = require('assert');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use strict';
|
||||
require('../../common');
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
const { test } = require('node:test');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const { spawnPromisified } = require('../../common');
|
||||
const { spawnPromisified } = require('../common');
|
||||
const assert = require('node:assert');
|
||||
const { describe, it } = require('node:test');
|
||||
|
||||
70
test/js/node/test/parallel/test-assert-fail-deprecation.js
Normal file
70
test/js/node/test/parallel/test-assert-fail-deprecation.js
Normal file
@@ -0,0 +1,70 @@
|
||||
// Flags: --no-warnings
|
||||
'use strict';
|
||||
|
||||
const { expectWarning } = require('../common');
|
||||
const assert = require('assert');
|
||||
const { test } = require('node:test');
|
||||
|
||||
expectWarning(
|
||||
'DeprecationWarning',
|
||||
'assert.fail() with more than one argument is deprecated. ' +
|
||||
'Please use assert.strictEqual() instead or only pass a message.',
|
||||
'DEP0094'
|
||||
);
|
||||
|
||||
test('Two args only, operator defaults to "!="', () => {
|
||||
assert.throws(() => {
|
||||
assert.fail('first', 'second');
|
||||
}, {
|
||||
code: 'ERR_ASSERTION',
|
||||
name: 'AssertionError',
|
||||
message: '\'first\' != \'second\'',
|
||||
operator: '!=',
|
||||
actual: 'first',
|
||||
expected: 'second',
|
||||
generatedMessage: true
|
||||
});
|
||||
});
|
||||
|
||||
test('Three args', () => {
|
||||
assert.throws(() => {
|
||||
assert.fail('ignored', 'ignored', 'another custom message');
|
||||
}, {
|
||||
code: 'ERR_ASSERTION',
|
||||
name: 'AssertionError',
|
||||
message: 'another custom message',
|
||||
operator: 'fail',
|
||||
actual: 'ignored',
|
||||
expected: 'ignored',
|
||||
generatedMessage: false
|
||||
});
|
||||
});
|
||||
|
||||
test('Three args with custom Error', () => {
|
||||
assert.throws(() => {
|
||||
assert.fail(typeof 1, 'object', new TypeError('another custom message'));
|
||||
}, {
|
||||
name: 'TypeError',
|
||||
message: 'another custom message'
|
||||
});
|
||||
});
|
||||
|
||||
test('No third arg (but a fourth arg)', () => {
|
||||
assert.throws(() => {
|
||||
assert.fail('first', 'second', undefined, 'operator');
|
||||
}, {
|
||||
code: 'ERR_ASSERTION',
|
||||
name: 'AssertionError',
|
||||
message: '\'first\' operator \'second\'',
|
||||
operator: 'operator',
|
||||
actual: 'first',
|
||||
expected: 'second'
|
||||
});
|
||||
});
|
||||
|
||||
test('The stackFrameFunction should exclude the foo frame', () => {
|
||||
assert.throws(
|
||||
function foo() { assert.fail('first', 'second', 'message', '!==', foo); },
|
||||
(err) => !/^\s*at\sfoo\b/m.test(err.stack)
|
||||
);
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
require('../../common');
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
const { test } = require('node:test');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
require('../../common');
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
const { test } = require('node:test');
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
|
||||
const assert = require('node:assert');
|
||||
|
||||
require('../../../harness');
|
||||
require('../../harness');
|
||||
|
||||
const {invalidArgTypeHelper} = require('../../common');
|
||||
const {invalidArgTypeHelper} = require('../common');
|
||||
const {inspect} = require('util');
|
||||
const {test} = require('node:test');
|
||||
const vm = require('vm');
|
||||
@@ -4,39 +4,26 @@
|
||||
require('../common');
|
||||
const { Buffer } = require('node:buffer');
|
||||
const { strictEqual } = require('node:assert');
|
||||
const { describe, it } = require('node:test');
|
||||
|
||||
{
|
||||
{
|
||||
describe('Using resizable ArrayBuffer with Buffer...', () => {
|
||||
it('works as expected', () => {
|
||||
const ab = new ArrayBuffer(10, { maxByteLength: 20 });
|
||||
const buffer = Buffer.from(ab, 1);
|
||||
strictEqual(ab.byteLength, 10);
|
||||
strictEqual(buffer.buffer.byteLength, 10);
|
||||
strictEqual(buffer.byteLength, 9);
|
||||
ab.resize(15);
|
||||
strictEqual(ab.byteLength, 15);
|
||||
strictEqual(buffer.buffer.byteLength, 15);
|
||||
strictEqual(buffer.byteLength, 14);
|
||||
ab.resize(5);
|
||||
strictEqual(ab.byteLength, 5);
|
||||
strictEqual(buffer.buffer.byteLength, 5);
|
||||
strictEqual(buffer.byteLength, 4);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
{
|
||||
{
|
||||
it('works with the deprecated constructor also', () => {
|
||||
const ab = new ArrayBuffer(10, { maxByteLength: 20 });
|
||||
const buffer = new Buffer(ab, 1);
|
||||
strictEqual(ab.byteLength, 10);
|
||||
strictEqual(buffer.buffer.byteLength, 10);
|
||||
strictEqual(buffer.byteLength, 9);
|
||||
ab.resize(15);
|
||||
strictEqual(ab.byteLength, 15);
|
||||
strictEqual(buffer.buffer.byteLength, 15);
|
||||
strictEqual(buffer.byteLength, 14);
|
||||
ab.resize(5);
|
||||
strictEqual(ab.byteLength, 5);
|
||||
strictEqual(buffer.buffer.byteLength, 5);
|
||||
strictEqual(buffer.byteLength, 4);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
28
test/js/node/test/parallel/test-file-write-stream5.js
Normal file
28
test/js/node/test/parallel/test-file-write-stream5.js
Normal file
@@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
// Test 'uncork' for WritableStream.
|
||||
// Refs: https://github.com/nodejs/node/issues/50979
|
||||
|
||||
const common = require('../common');
|
||||
const fs = require('fs');
|
||||
const assert = require('assert');
|
||||
const test = require('node:test');
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
|
||||
const filepath = tmpdir.resolve('write_stream.txt');
|
||||
tmpdir.refresh();
|
||||
|
||||
const data = 'data';
|
||||
|
||||
test('writable stream uncork', () => {
|
||||
const fileWriteStream = fs.createWriteStream(filepath);
|
||||
|
||||
fileWriteStream.on('finish', common.mustCall(() => {
|
||||
const writtenData = fs.readFileSync(filepath, 'utf8');
|
||||
assert.strictEqual(writtenData, data);
|
||||
}));
|
||||
fileWriteStream.cork();
|
||||
fileWriteStream.write(data, common.mustCall());
|
||||
fileWriteStream.uncork();
|
||||
fileWriteStream.end();
|
||||
});
|
||||
@@ -4,7 +4,7 @@ require('../common');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const assert = require('node:assert');
|
||||
const { describe, it } = require('bun:test');
|
||||
const { describe, it } = require('node:test');
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
|
||||
tmpdir.refresh();
|
||||
|
||||
8
test/js/node/test/parallel/test-runner-aliases.js
Normal file
8
test/js/node/test/parallel/test-runner-aliases.js
Normal file
@@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
require('../common');
|
||||
const { strictEqual } = require('node:assert');
|
||||
const test = require('node:test');
|
||||
|
||||
strictEqual(test.test, test);
|
||||
strictEqual(test.it, test);
|
||||
strictEqual(test.describe, test.suite);
|
||||
21
test/js/node/test/parallel/test-runner-assert.js
Normal file
21
test/js/node/test/parallel/test-runner-assert.js
Normal file
@@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
require('../common');
|
||||
const assert = require('node:assert');
|
||||
const test = require('node:test');
|
||||
|
||||
test('expected methods are on t.assert', (t) => {
|
||||
const uncopiedKeys = [
|
||||
'AssertionError',
|
||||
'CallTracker',
|
||||
'strict',
|
||||
];
|
||||
const assertKeys = Object.keys(assert).filter((key) => !uncopiedKeys.includes(key));
|
||||
const expectedKeys = ['snapshot', 'fileSnapshot'].concat(assertKeys).sort();
|
||||
assert.deepStrictEqual(Object.keys(t.assert).sort(), expectedKeys);
|
||||
});
|
||||
|
||||
test('t.assert.ok correctly parses the stacktrace', (t) => {
|
||||
// FIXME: AssertionError message is incorrect
|
||||
// t.assert.throws(() => t.assert.ok(1 === 2), /t\.assert\.ok\(1 === 2\)/);
|
||||
t.assert.throws(() => t.assert.ok(1 === 2));
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const { before, after, test } = require('node:test');
|
||||
const { createServer } = require('node:http');
|
||||
|
||||
let server;
|
||||
|
||||
before(common.mustCall(() => {
|
||||
server = createServer();
|
||||
|
||||
return new Promise(common.mustCall((resolve, reject) => {
|
||||
server.listen(0, common.mustCall((err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
}));
|
||||
}));
|
||||
}));
|
||||
|
||||
after(common.mustCall(() => {
|
||||
server.close(common.mustCall());
|
||||
}));
|
||||
|
||||
test();
|
||||
12
test/js/node/test/parallel/test-runner-subtest-after-hook.js
Normal file
12
test/js/node/test/parallel/test-runner-subtest-after-hook.js
Normal file
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const { test } = require('node:test');
|
||||
|
||||
// Regression test for https://github.com/nodejs/node/issues/51997
|
||||
test('after hook should be called with no subtests', (t) => {
|
||||
const timer = setTimeout(common.mustNotCall(), 2 ** 30);
|
||||
|
||||
t.after(common.mustCall(() => {
|
||||
clearTimeout(timer);
|
||||
}));
|
||||
});
|
||||
26
test/js/node/test/parallel/test-runner-typechecking.js
Normal file
26
test/js/node/test/parallel/test-runner-typechecking.js
Normal file
@@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
require('../common');
|
||||
|
||||
// Return type of shorthands should be consistent
|
||||
// with the return type of test
|
||||
|
||||
const assert = require('assert');
|
||||
const { test, describe, it } = require('node:test');
|
||||
|
||||
const testOnly = test('only test', { only: true });
|
||||
const testTodo = test('todo test', { todo: true });
|
||||
const testSkip = test('skip test', { skip: true });
|
||||
const testOnlyShorthand = test.only('only test shorthand');
|
||||
const testTodoShorthand = test.todo('todo test shorthand');
|
||||
const testSkipShorthand = test.skip('skip test shorthand');
|
||||
|
||||
describe('\'node:test\' and its shorthands should return the same', () => {
|
||||
it('should return undefined', () => {
|
||||
assert.strictEqual(testOnly, undefined);
|
||||
assert.strictEqual(testTodo, undefined);
|
||||
assert.strictEqual(testSkip, undefined);
|
||||
assert.strictEqual(testOnlyShorthand, undefined);
|
||||
assert.strictEqual(testTodoShorthand, undefined);
|
||||
assert.strictEqual(testSkipShorthand, undefined);
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
// Flags: --expose-internals
|
||||
'use strict';
|
||||
|
||||
const common = require('../../common');
|
||||
const common = require('../common');
|
||||
const {
|
||||
ok,
|
||||
strictEqual,
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
require('../../common');
|
||||
require('../common');
|
||||
|
||||
const assert = require('node:assert');
|
||||
const url = require('node:url');
|
||||
@@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
const { test } = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
|
||||
// https://github.com/nodejs/node/issues/57272
|
||||
|
||||
test('should throw error when writing after close', async (t) => {
|
||||
const writable = new WritableStream({
|
||||
write(chunk) {
|
||||
console.log(chunk);
|
||||
},
|
||||
});
|
||||
|
||||
const writer = writable.getWriter();
|
||||
|
||||
await writer.write('Hello');
|
||||
await writer.close();
|
||||
|
||||
await assert.rejects(
|
||||
async () => {
|
||||
await writer.write('World');
|
||||
},
|
||||
{
|
||||
name: 'TypeError',
|
||||
}
|
||||
);
|
||||
});
|
||||
146
test/js/node/test_runner/fixtures/01-harness.js
Normal file
146
test/js/node/test_runner/fixtures/01-harness.js
Normal file
@@ -0,0 +1,146 @@
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert");
|
||||
|
||||
test("test() is a function", () => {
|
||||
assert(typeof test === "function", "test() is a function");
|
||||
});
|
||||
|
||||
test("describe() is a function", () => {
|
||||
assert(typeof test.describe === "function", "describe() is a function");
|
||||
});
|
||||
|
||||
test.describe("TestContext", () => {
|
||||
test("<exists>", t => {
|
||||
t.assert.ok(typeof t === "object", "test() returns an object");
|
||||
});
|
||||
|
||||
test("name", t => {
|
||||
t.assert.equal(t.name, "name"); // matches the name of the test
|
||||
});
|
||||
|
||||
test("filePath", t => {
|
||||
t.assert.equal(t.filePath, __filename);
|
||||
});
|
||||
|
||||
test("signal", t => {
|
||||
t.assert.ok(t.signal instanceof AbortSignal);
|
||||
});
|
||||
|
||||
test("assert", t => {
|
||||
t.assert.ok(typeof t.assert === "object", "test() argument has an assert property");
|
||||
const actual = Object.keys(t.assert).sort();
|
||||
const expected = Object.keys({ ...assert })
|
||||
.filter(key => !["CallTracker", "AssertionError", "strict"].includes(key))
|
||||
.concat(["fileSnapshot", "snapshot"])
|
||||
.sort();
|
||||
t.assert.deepEqual(actual, expected, "test() argument is the same as the node:assert module");
|
||||
});
|
||||
|
||||
test("diagnostic()", t => {
|
||||
t.assert.ok(typeof t.diagnostic === "function", "diagnostic() is a function");
|
||||
});
|
||||
|
||||
test("before()", t => {
|
||||
t.assert.ok(typeof t.before === "function", "before() is a function");
|
||||
});
|
||||
|
||||
test("after()", t => {
|
||||
t.assert.ok(typeof t.after === "function", "after() is a function");
|
||||
});
|
||||
|
||||
test("beforeEach()", t => {
|
||||
t.assert.ok(typeof t.beforeEach === "function", "beforeEach() is a function");
|
||||
});
|
||||
|
||||
test("afterEach()", t => {
|
||||
t.assert.ok(typeof t.afterEach === "function", "afterEach() is a function");
|
||||
});
|
||||
|
||||
test("test()", t => {
|
||||
t.assert.ok(typeof t.test === "function", "test() method is a function");
|
||||
});
|
||||
});
|
||||
|
||||
test("before() is a function", t => {
|
||||
t.assert.ok(typeof test.before === "function", "before() is a function");
|
||||
});
|
||||
|
||||
test("after() is a function", t => {
|
||||
t.assert.ok(typeof test.after === "function", "after() is a function");
|
||||
});
|
||||
|
||||
test("beforeEach() is a function", t => {
|
||||
t.assert.ok(typeof test.beforeEach === "function", "beforeEach() is a function");
|
||||
});
|
||||
|
||||
test("afterEach() is a function", t => {
|
||||
t.assert.ok(typeof test.afterEach === "function", "afterEach() is a function");
|
||||
});
|
||||
|
||||
test.describe("test", () => {
|
||||
test("test()", t => {
|
||||
t.assert.ok(typeof test === "function", "test() is a function");
|
||||
});
|
||||
|
||||
test("it()", t => {
|
||||
t.assert.ok(typeof test.it === "function", "test.it() is a function");
|
||||
});
|
||||
|
||||
test("skip()", t => {
|
||||
t.assert.ok(typeof test.skip === "function", "test.skip() is a function");
|
||||
});
|
||||
|
||||
test("todo()", t => {
|
||||
t.assert.ok(typeof test.todo === "function", "test.todo() is a function");
|
||||
});
|
||||
|
||||
test("only()", t => {
|
||||
t.assert.ok(typeof test.only === "function", "test.only() is a function");
|
||||
});
|
||||
|
||||
test("describe()", t => {
|
||||
t.assert.ok(typeof test.describe === "function", "test.describe() is a function");
|
||||
});
|
||||
|
||||
test("suite()", t => {
|
||||
t.assert.ok(typeof test.suite === "function", "test.suite() is a function");
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("describe", () => {
|
||||
test("<exists>", t => {
|
||||
t.assert.ok(typeof test.describe === "function", "describe() is a function");
|
||||
});
|
||||
|
||||
test("skip()", t => {
|
||||
t.assert.ok(typeof test.describe.skip === "function", "describe.skip() is a function");
|
||||
});
|
||||
|
||||
test("todo()", t => {
|
||||
t.assert.ok(typeof test.describe.todo === "function", "describe.todo() is a function");
|
||||
});
|
||||
|
||||
test("only()", t => {
|
||||
t.assert.ok(typeof test.describe.only === "function", "describe.only() is a function");
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("describe 1", t => {
|
||||
test("name is correct", t => {
|
||||
t.assert.equal(t.name, "name is correct");
|
||||
});
|
||||
|
||||
test("fullName is correct", t => {
|
||||
t.assert.equal(t.fullName, "describe 1 > fullName is correct");
|
||||
});
|
||||
|
||||
test.describe("describe 2", () => {
|
||||
test("name is correct", t => {
|
||||
t.assert.equal(t.name, "name is correct");
|
||||
});
|
||||
|
||||
test("fullName is correct", t => {
|
||||
t.assert.equal(t.fullName, "describe 1 > describe 2 > fullName is correct");
|
||||
});
|
||||
});
|
||||
});
|
||||
136
test/js/node/test_runner/fixtures/02-hooks.js
Normal file
136
test/js/node/test_runner/fixtures/02-hooks.js
Normal file
@@ -0,0 +1,136 @@
|
||||
const { describe, test, before, after, beforeEach, afterEach } = require("node:test");
|
||||
const { readFileSync } = require("node:fs");
|
||||
const { join } = require("node:path");
|
||||
const assert = require("node:assert");
|
||||
|
||||
const expectedFile = readFileSync(join(__dirname, "02-hooks.json"), "utf-8");
|
||||
const { node, bun } = JSON.parse(expectedFile);
|
||||
const order = [];
|
||||
|
||||
before(() => {
|
||||
order.push("before global");
|
||||
});
|
||||
|
||||
before(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 1));
|
||||
order.push("before global async");
|
||||
});
|
||||
|
||||
after(() => {
|
||||
order.push("after global");
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 1));
|
||||
order.push("after global async");
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
order.push("beforeEach global");
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 1));
|
||||
order.push("beforeEach global async");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
order.push("afterEach global");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 1));
|
||||
order.push("afterEach global async");
|
||||
});
|
||||
|
||||
describe("execution order", () => {
|
||||
before(() => {
|
||||
order.push("before");
|
||||
});
|
||||
|
||||
before(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 1));
|
||||
order.push("before");
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
order.push("beforeEach");
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 1));
|
||||
order.push("beforeEach");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
order.push("afterEach");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 1));
|
||||
order.push("afterEach");
|
||||
});
|
||||
|
||||
after(() => {
|
||||
order.push("after");
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 1));
|
||||
order.push("after");
|
||||
});
|
||||
|
||||
test("test 1", ({ fullName }) => {
|
||||
order.push(`test: ${fullName}`);
|
||||
});
|
||||
|
||||
describe("describe 1", () => {
|
||||
before(() => {
|
||||
order.push("before > describe 1");
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
order.push("beforeEach > describe 1");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
order.push("afterEach > describe 1");
|
||||
});
|
||||
|
||||
after(() => {
|
||||
order.push("after > describe 1");
|
||||
});
|
||||
|
||||
test("test 2", ({ fullName }) => {
|
||||
order.push(`test: ${fullName}`);
|
||||
});
|
||||
|
||||
describe("describe 2", () => {
|
||||
before(() => {
|
||||
order.push("before > describe 2");
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
order.push("beforeEach > describe 2");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
order.push("afterEach > describe 2");
|
||||
});
|
||||
|
||||
after(() => {
|
||||
order.push("after > describe 2");
|
||||
});
|
||||
|
||||
test("test 3", ({ fullName }) => {
|
||||
order.push(`test: ${fullName}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
after(() => {
|
||||
// FIXME: Due to subtle differences between how Node.js and Bun (using `bun test`) run tests,
|
||||
// this is a snapshot test. You must look at the snapshot to verify the output makes sense.
|
||||
assert.deepEqual(order, "Bun" in globalThis ? bun : node);
|
||||
});
|
||||
96
test/js/node/test_runner/fixtures/02-hooks.json
Normal file
96
test/js/node/test_runner/fixtures/02-hooks.json
Normal file
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"node": [
|
||||
"before global",
|
||||
"before global async",
|
||||
"before",
|
||||
"before",
|
||||
"beforeEach global",
|
||||
"beforeEach global async",
|
||||
"beforeEach",
|
||||
"beforeEach",
|
||||
"test: execution order > test 1",
|
||||
"afterEach",
|
||||
"afterEach",
|
||||
"afterEach global",
|
||||
"afterEach global async",
|
||||
"before > describe 1",
|
||||
"beforeEach global",
|
||||
"beforeEach global async",
|
||||
"beforeEach",
|
||||
"beforeEach",
|
||||
"beforeEach > describe 1",
|
||||
"test: execution order > describe 1 > test 2",
|
||||
"afterEach > describe 1",
|
||||
"afterEach",
|
||||
"afterEach",
|
||||
"afterEach global",
|
||||
"afterEach global async",
|
||||
"before > describe 2",
|
||||
"beforeEach global",
|
||||
"beforeEach global async",
|
||||
"beforeEach",
|
||||
"beforeEach",
|
||||
"beforeEach > describe 1",
|
||||
"beforeEach > describe 2",
|
||||
"test: execution order > describe 1 > describe 2 > test 3",
|
||||
"afterEach > describe 2",
|
||||
"afterEach > describe 1",
|
||||
"afterEach",
|
||||
"afterEach",
|
||||
"afterEach global",
|
||||
"afterEach global async",
|
||||
"after > describe 2",
|
||||
"after > describe 1",
|
||||
"after",
|
||||
"after",
|
||||
"after global",
|
||||
"after global async"
|
||||
],
|
||||
"bun": [
|
||||
"before global",
|
||||
"before global async",
|
||||
"before",
|
||||
"before",
|
||||
"before > describe 1",
|
||||
"before > describe 2",
|
||||
"beforeEach global",
|
||||
"beforeEach global async",
|
||||
"beforeEach",
|
||||
"beforeEach",
|
||||
"beforeEach > describe 1",
|
||||
"beforeEach > describe 2",
|
||||
"test: execution order > describe 1 > describe 2 > test 3",
|
||||
"afterEach > describe 2",
|
||||
"afterEach > describe 1",
|
||||
"afterEach",
|
||||
"afterEach",
|
||||
"afterEach global",
|
||||
"afterEach global async",
|
||||
"after > describe 2",
|
||||
"beforeEach global",
|
||||
"beforeEach global async",
|
||||
"beforeEach",
|
||||
"beforeEach",
|
||||
"beforeEach > describe 1",
|
||||
"test: execution order > describe 1 > test 2",
|
||||
"afterEach > describe 1",
|
||||
"afterEach",
|
||||
"afterEach",
|
||||
"afterEach global",
|
||||
"afterEach global async",
|
||||
"after > describe 1",
|
||||
"beforeEach global",
|
||||
"beforeEach global async",
|
||||
"beforeEach",
|
||||
"beforeEach",
|
||||
"test: execution order > test 1",
|
||||
"afterEach",
|
||||
"afterEach",
|
||||
"afterEach global",
|
||||
"afterEach global async",
|
||||
"after",
|
||||
"after",
|
||||
"after global",
|
||||
"after global async"
|
||||
]
|
||||
}
|
||||
55
test/js/node/test_runner/fixtures/03-test-variations.js
Normal file
55
test/js/node/test_runner/fixtures/03-test-variations.js
Normal file
@@ -0,0 +1,55 @@
|
||||
const test = require("node:test");
|
||||
|
||||
test(); // test without name or callback
|
||||
|
||||
test("test with name and callback", t => {
|
||||
t.assert.ok(true);
|
||||
});
|
||||
|
||||
test("test with name, options, and callback", { timeout: 5000 }, t => {
|
||||
t.assert.ok(true);
|
||||
});
|
||||
|
||||
test(t => {
|
||||
t.assert.equal(t.name, "<anonymous>");
|
||||
});
|
||||
|
||||
test(function testWithFunctionName(t) {
|
||||
t.assert.equal(t.name, "testWithFunctionName");
|
||||
});
|
||||
|
||||
test({ timeout: 5000 }, t => {
|
||||
t.assert.equal(t.name, "<anonymous>");
|
||||
});
|
||||
|
||||
test.describe("describe with name and callback", () => {
|
||||
test("nested test", t => {
|
||||
t.assert.ok(true);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("describe with name, options, and callback", { timeout: 5000 }, () => {
|
||||
test("nested test", t => {
|
||||
t.assert.ok(true);
|
||||
});
|
||||
});
|
||||
|
||||
test.skip("skipped test", t => {
|
||||
t.assert.fail("This test should be skipped");
|
||||
});
|
||||
|
||||
test.skip("skipped test with options", { timeout: 5000 }, t => {
|
||||
t.assert.fail("This test should be skipped");
|
||||
});
|
||||
|
||||
test.todo("todo test");
|
||||
|
||||
test.todo("todo test with options", { timeout: 5000 });
|
||||
|
||||
test.describe.skip("skipped describe", () => {
|
||||
test("nested test", t => {
|
||||
t.assert.fail("This test should be skipped");
|
||||
});
|
||||
});
|
||||
|
||||
test.describe.todo("todo describe");
|
||||
54
test/js/node/test_runner/fixtures/04-async-tests.js
Normal file
54
test/js/node/test_runner/fixtures/04-async-tests.js
Normal file
@@ -0,0 +1,54 @@
|
||||
const { describe, test, after } = require("node:test");
|
||||
const assert = require("node:assert");
|
||||
|
||||
let callCount = 0;
|
||||
|
||||
function mustCall(fn) {
|
||||
try {
|
||||
fn();
|
||||
} finally {
|
||||
callCount++;
|
||||
}
|
||||
}
|
||||
|
||||
test(
|
||||
"test with an async function",
|
||||
mustCall(async () => {
|
||||
const result = await Promise.resolve(42);
|
||||
assert.equal(result, 42);
|
||||
}),
|
||||
);
|
||||
|
||||
test(
|
||||
"test with an async function that delays",
|
||||
mustCall(async () => {
|
||||
const start = Date.now();
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
const end = Date.now();
|
||||
assert.ok(end - start > 10, "should wait at least 10ms");
|
||||
}),
|
||||
);
|
||||
|
||||
describe("nested tests", () => {
|
||||
test(
|
||||
"nested test with an async function",
|
||||
mustCall(async () => {
|
||||
const result = await Promise.resolve(42);
|
||||
assert.equal(result, 42);
|
||||
}),
|
||||
);
|
||||
|
||||
test(
|
||||
"nested test with an async function that delays",
|
||||
mustCall(async () => {
|
||||
const start = Date.now();
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
const end = Date.now();
|
||||
assert.ok(end - start > 10, "should wait at least 10ms");
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
assert.equal(callCount, 4);
|
||||
});
|
||||
57
test/js/node/test_runner/node-test.test.ts
Normal file
57
test/js/node/test_runner/node-test.test.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { describe, test, expect } from "bun:test";
|
||||
import { spawn } from "bun";
|
||||
import { join } from "node:path";
|
||||
import { bunEnv, bunExe } from "harness";
|
||||
|
||||
describe("node:test", () => {
|
||||
test("should run basic tests", async () => {
|
||||
const { exitCode, stderr } = await runTest("01-harness.js");
|
||||
expect({ exitCode, stderr }).toMatchObject({
|
||||
exitCode: 0,
|
||||
stderr: expect.stringContaining("0 fail"),
|
||||
});
|
||||
});
|
||||
|
||||
test("should run hooks in the right order", async () => {
|
||||
const { exitCode, stderr } = await runTest("02-hooks.js");
|
||||
expect({ exitCode, stderr }).toMatchObject({
|
||||
exitCode: 0,
|
||||
stderr: expect.stringContaining("0 fail"),
|
||||
});
|
||||
});
|
||||
|
||||
test("should run tests with different variations", async () => {
|
||||
const { exitCode, stderr } = await runTest("03-test-variations.js");
|
||||
expect({ exitCode, stderr }).toMatchObject({
|
||||
exitCode: 0,
|
||||
stderr: expect.stringContaining("0 fail"),
|
||||
});
|
||||
});
|
||||
|
||||
test("should run async tests", async () => {
|
||||
const { exitCode, stderr } = await runTest("04-async-tests.js");
|
||||
expect({ exitCode, stderr }).toMatchObject({
|
||||
exitCode: 0,
|
||||
stderr: expect.stringContaining("0 fail"),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function runTest(filename: string) {
|
||||
const testPath = join(import.meta.dirname, "fixtures", filename);
|
||||
const {
|
||||
exited,
|
||||
stdout: stdoutStream,
|
||||
stderr: stderrStream,
|
||||
} = spawn({
|
||||
cmd: [bunExe(), "test", testPath],
|
||||
env: bunEnv,
|
||||
stderr: "pipe",
|
||||
});
|
||||
const [exitCode, stdout, stderr] = await Promise.all([
|
||||
exited,
|
||||
new Response(stdoutStream).text(),
|
||||
new Response(stderrStream).text(),
|
||||
]);
|
||||
return { exitCode, stdout, stderr };
|
||||
}
|
||||
Reference in New Issue
Block a user