Files
bun.sh/test/js/node/harness.ts
2025-03-19 11:49:00 -07:00

267 lines
6.9 KiB
TypeScript

/**
* @note this file patches `node:test` via the require cache.
*/
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);
hideFromStackTrace(expect);
// Assert
const strictEqual = (...args: Parameters<typeof assertNode.strictEqual>) => {
assertNode.strictEqual(...args);
expect(true).toBe(true);
};
const notStrictEqual = (...args: Parameters<typeof assertNode.notStrictEqual>) => {
assertNode.notStrictEqual(...args);
expect(true).toBe(true);
};
const deepStrictEqual = (...args: Parameters<typeof assertNode.deepStrictEqual>) => {
assertNode.deepStrictEqual(...args);
expect(true).toBe(true);
};
const throws = (...args: Parameters<typeof assertNode.throws>) => {
assertNode.throws(...args);
expect(true).toBe(true);
};
const ok = (...args: Parameters<typeof assertNode.ok>) => {
assertNode.ok(...args);
expect(true).toBe(true);
};
const ifError = (...args: Parameters<typeof assertNode.ifError>) => {
assertNode.ifError(...args);
expect(true).toBe(true);
};
const match = (...args: Parameters<typeof assertNode.match>) => {
assertNode.match(...args);
expect(true).toBe(true);
};
interface NodeAssert {
(args: any): void;
strictEqual: typeof strictEqual;
deepStrictEqual: typeof deepStrictEqual;
notStrictEqual: typeof notStrictEqual;
throws: typeof throws;
ok: typeof ok;
ifError: typeof ifError;
match: typeof match;
}
const assert = function (...args: any[]) {
// @ts-ignore
assertNode(...args);
} as NodeAssert;
hideFromStackTrace(strictEqual);
hideFromStackTrace(notStrictEqual);
hideFromStackTrace(deepStrictEqual);
hideFromStackTrace(throws);
hideFromStackTrace(ok);
hideFromStackTrace(ifError);
hideFromStackTrace(match);
hideFromStackTrace(assert);
Object.assign(assert, {
strictEqual,
deepStrictEqual,
notStrictEqual,
throws,
ok,
ifError,
match,
});
// End assert
const createCallCheckCtx = (done: DoneCb) => {
var timers: Timer[] = [];
const createDone = createDoneDotAll(done, undefined, timers);
// const mustCallChecks = [];
// failed.forEach(function (context) {
// console.log(
// "Mismatched %s function calls. Expected %s, actual %d.",
// context.name,
// context.messageSegment,
// context.actual
// );
// console.log(context.stack.split("\n").slice(2).join("\n"));
// });
// TODO: Implement this to be exact only
function mustCall(fn?: (...args: any[]) => any, exact?: number) {
return mustCallAtLeast(fn!, exact!);
}
function closeTimers() {
timers.forEach(t => clearTimeout(t));
}
function mustNotCall(reason: string = "function should not have been called", optionalCb?: (err?: any) => void) {
const localDone = createDone();
timers.push(setTimeout(() => localDone(), 200));
return () => {
closeTimers();
if (optionalCb) optionalCb.apply(undefined, reason ? [reason] : []);
done(new Error(reason));
};
}
function mustSucceed(fn: () => any, exact?: number) {
return mustCall(function (err, ...args) {
ifError(err);
// @ts-ignore
if (typeof fn === "function") return fn(...(args as []));
}, exact);
}
function mustCallAtLeast(fn: unknown, minimum: number) {
return _mustCallInner(fn, minimum, "minimum");
}
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") {
criteria = fn;
fn = noop;
} else if (fn === undefined) {
fn = noop;
}
if (typeof criteria !== "number") throw new TypeError(`Invalid ${field} value: ${criteria}`);
let actual = 0;
let expected = criteria;
// mustCallChecks.push(context);
const done = createDone();
const _return = (...args: any[]) => {
try {
// @ts-ignore
const result = fn(...args);
actual++;
if (actual >= expected) {
closeTimers();
done();
}
return result;
} catch (err) {
if (err instanceof Error) done(err);
else if (err?.toString) done(new Error(err?.toString()));
else {
console.error("Unknown error", err);
done(new Error("Unknown error"));
}
closeTimers();
}
};
// Function instances have own properties that may be relevant.
// Let's replicate those properties to the returned function.
// Refs: https://tc39.es/ecma262/#sec-function-instances
Object.defineProperties(_return, {
name: {
value: fn.name,
writable: false,
enumerable: false,
configurable: true,
},
length: {
value: fn.length,
writable: false,
enumerable: false,
configurable: true,
},
});
return _return;
}
return {
mustSucceed,
mustCall,
mustCallAtLeast,
mustNotCall,
closeTimers,
};
};
function createDoneDotAll(done: DoneCb, globalTimeout?: number, timers: Timer[] = []) {
let toComplete = 0;
let completed = 0;
const globalTimer = globalTimeout
? (timers.push(
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),
),
timers[timers.length - 1])
: timeout;
return (result?: Error) => {
if (timer) clearTimeout(timer);
if (globalTimer) clearTimeout(globalTimer);
if (result instanceof Error) {
done(result);
return;
}
completed += 1;
if (completed === toComplete) {
done();
}
};
}
return createDoneCb;
}
return {
expect,
test,
it,
describe,
beforeAll,
afterAll,
beforeEach,
afterEach,
createDoneDotAll,
strictEqual,
notStrictEqual,
deepStrictEqual,
throws,
ok,
ifError,
createCallCheckCtx,
match,
assert,
mock,
};
}
declare namespace Bun {
function jest(path: string): typeof import("bun:test");
}