Skip to content

Commit

Permalink
test: mock 'node:test' module in node test harness
Browse files Browse the repository at this point in the history
  • Loading branch information
Don Isaac committed Dec 11, 2024
1 parent 2d5ea49 commit 5ef7251
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 13 deletions.
1 change: 1 addition & 0 deletions test/js/node/bunfig.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
preload = ["./harness.ts"]
139 changes: 126 additions & 13 deletions test/js/node/harness.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { AnyFunction } from "bun";
import { hideFromStackTrace } from "harness";
/**
* @note this file patches `node:test` via the require cache.
*/
import {AnyFunction} from "bun";
import {hideFromStackTrace} from "harness";
import assertNode from "node:assert";

type DoneCb = (err?: Error) => any;
function noop() {}
export function createTest(path: string) {
const { expect, test, it, describe, beforeAll, afterAll, beforeEach, afterEach, mock } = Bun.jest(path);
const {expect, test, it, describe, beforeAll, afterAll, beforeEach, afterEach, mock} = Bun.jest(path);

hideFromStackTrace(expect);

Expand Down Expand Up @@ -201,23 +204,23 @@ export function createTest(path: string) {
let completed = 0;
const globalTimer = globalTimeout
? (timers.push(
setTimeout(() => {
console.log("Global Timeout");
done(new Error("Timed out!"));
}, globalTimeout),
),
setTimeout(() => {
console.log("Global Timeout");
done(new Error("Timed out!"));
}, globalTimeout),
),
timers[timers.length - 1])
: undefined;
function createDoneCb(timeout?: number) {
toComplete += 1;
const timer =
timeout !== undefined
? (timers.push(
setTimeout(() => {
console.log("Timeout");
done(new Error("Timed out!"));
}, timeout),
),
setTimeout(() => {
console.log("Timeout");
done(new Error("Timed out!"));
}, timeout),
),
timers[timers.length - 1])
: timeout;
return (result?: Error) => {
Expand Down Expand Up @@ -262,3 +265,113 @@ export function createTest(path: string) {
declare namespace Bun {
function jest(path: string): typeof import("bun:test");
}

if (Bun.main.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, fn?: Function | undefined) {
if (typeof fn !== "function" && typeof label === "function") {
fn = label;
label = fn.name;
}
const ctx = getContext();
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, maybeFn?: Function) {
const [label, fn] = (typeof labelOrFn == "function" ? [labelOrFn.name, labelOrFn] : [labelOrFn, maybeFn]);
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,
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,
};
}

0 comments on commit 5ef7251

Please sign in to comment.