Something went wrong. Try again.
Monorepo for the nibling.run platform
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298// Derived from Node.js v24.16.0 (MIT; Copyright Node.js contributors, and Joyent, Inc. and// other Node contributors). See LICENSE-node.// node:internal/test_runner/test.js: Test, Suite, TestHook, TestContext, SuiteContext.// Trimmed from Node's file: no coverage, no randomize/seed, no rerun-failures replay,// no source-mapped locations, no diagnostics_channel publishing, no watch mode.// testNamePatterns/testSkipPatterns filtering (willBeFilteredByName,// getTestNameWithAncestors, testMatchesPattern) IS ported, reading harness.config —// but nothing feeds that config today: run()'s own testNamePatterns/testSkipPatterns// option never reaches it for isolation:'none' (confirmed against real Node 24.16.0 —// see runner.ts), and no CLI flag parser exists here (DESIGN: refused) to set it the// other way. It is wired so a later CLI parser only has to feed commandLineDefaults(). The// `only`-filtering behaviour direct execution actually exercises (confirmed against// real Node 24.16.0: `node file.js` always filters by `only`, unlike `--test`, because// harness.js's `isFilteringByOnly` is hardcoded true outside `--test`/run()) is kept.// Concurrency is accepted and validated like Node's, and subtests under `concurrency:// true` do interleave (their async bodies race on this single-threaded worker's// cooperative loop the same way Node's real parallel scheduling only ever interleaves// microtask-level work, not CPU-bound synchronous code) — confirmed against real Node// 24.16.0 with a `describe(..., { concurrency: true })` of two subtests that each log// before/after an awaited delay: both goldens print `a1,b1,b2,a2`.import { invalidArgType, invalidArgValue, outOfRange } from "../errors";import { assert as nodeAssert } from "../assert";import { testFailure, type TestFailureError } from "./test-failure";import { TestsStream, type Loc } from "./tests_stream";import { getAssertionMap } from "./assert";import { isTestFailureError, countCompletedTest, createDeferredCallback, getCallerLocation } from "./utils";import { createMockTracker, MockTracker } from "./mock/mock";import { SnapshotManager } from "./snapshot";
export const kCancelledByParent = "cancelledByParent";const kAborted = "testAborted";const kParentAlreadyFinished = "parentAlreadyFinished";export const kSubtestsFailed = "subtestsFailed";const kTestCodeFailure = "testCodeFailure";const kTestTimeoutFailure = "testTimeoutFailure";const kExpectedFailure = "expectedFailure";const kHookFailure = "hookFailed";const kCallbackAndPromisePresent = "callbackAndPromisePresent";const kDefaultTimeout: number | null = null;export const kUnwrapErrors = new Set([kTestCodeFailure, kHookFailure, "uncaughtException", "unhandledRejection"]);const kHookNames = ["before", "after", "beforeEach", "afterEach"] as const;type HookName = (typeof kHookNames)[number];const noop = (): void => {};// Node's lib/internal/timers.js TIMEOUT_MAX: the largest delay setTimeout/setInterval// accept (a signed 32-bit int), reused here as the upper bound for timeout/plan.wait/// waitFor's interval and timeout the way test.js's validateNumber calls do.const TIMEOUT_MAX = 2 ** 31 - 1;const MAX_UINT32 = 4_294_967_295;
// No shared validateUint32/validateNumber/validateAbortSignal exists in src/runtime (each// module that needs Node's internal/validators.js checks keeps its own small copy — see// e.g. histogram.ts's validateInteger, readline/promises.ts's validateInteger); these// mirror internal/validators.js's validateUint32, validateNumber and validateAbortSignal// for the handful of test_runner options that need them.function validateUint32(value: unknown, name: string, positive = false): void { if (typeof value !== "number") throw invalidArgType(name, "number", value); if (!Number.isInteger(value)) throw outOfRange(name, "an integer", value); const min = positive ? 1 : 0; if (value < min || value > MAX_UINT32) throw outOfRange(name, `>= ${min} && <= ${MAX_UINT32}`, value);}
function validateNumber(value: unknown, name: string, min?: number, max?: number): void { if (typeof value !== "number") throw invalidArgType(name, "number", value); if ((min != null && value < min) || (max != null && value > max) || ((min != null || max != null) && Number.isNaN(value))) { const range = `${min != null ? `>= ${min}` : ""}${min != null && max != null ? " && " : ""}${max != null ? `<= ${max}` : ""}`; throw outOfRange(name, range, value); }}
function validateAbortSignal(signal: unknown, name: string): void { if (signal !== undefined && (signal === null || typeof signal !== "object" || !("aborted" in signal))) { throw invalidArgType(name, "an instance of AbortSignal", signal); }}
/** Node's test.js testMatchesPattern: a pattern matches by the test's own name, by any * ancestor's name (a matched suite drags its subtests along), or by the full * "grandparent parent test" ancestor chain (so a non-unique leaf name can still be * pinned down). `.exec()`, not `.test()`, to mirror Node's RegExpPrototypeExec exactly * — including its `lastIndex` statefulness for a pattern carrying the `g`/`y` flag. */function testMatchesPattern(test: Test, patterns: RegExp[]): boolean { const matchesByNameOrParent = patterns.some((re) => re.exec(test.name) !== null) || (test.parent !== null && testMatchesPattern(test.parent, patterns)); if (matchesByNameOrParent) return true; const testNameWithAncestors = test.getTestNameWithAncestors().trim(); return patterns.some((re) => re.exec(testNameWithAncestors) !== null);}
/** The test whose body (fn, or before/beforeEach/afterEach/after hook) is currently * executing. The harness's uncaughtException/unhandledRejection handlers attribute an * async error to this test (or the root test when nothing is running), the closest * honest equivalent of Node's async_hooks-based resource-to-test attribution * (async_hooks.createHook is refused here — see divergences.json "test.<async attribution>" * and DESIGN.md's refusals section). */let currentlyRunning: Test | null = null;export function getCurrentlyRunningTest(): Test | null { return currentlyRunning;}
/** The nearest Suite whose builder callback is on the stack right now, pushed and * popped around the synchronous part of a Suite's body (Suite#createBuild). * harness.ts's top-level test()/describe()/hooks read it, falling back to the root. * Node answers this through async_hooks (testResources keyed by asyncId), which * follows awaits. The kernel has no async context, so a test()/it() registered after * an await inside an async describe() builder lands under the root here, not under * the suite. */const buildingStack: Test[] = [];export function pushBuildingParent(parent: Test): void { buildingStack.push(parent);}export function popBuildingParent(): void { buildingStack.pop();}
export function currentBuildingParent(): Test | null { return buildingStack[buildingStack.length - 1] ?? null;}
export interface Harness { config: { timeout: number; only: boolean; cwd: string; updateSnapshots: boolean; testNamePatterns: RegExp[] | null; testSkipPatterns: RegExp[] | null; }; counters: { tests: number; failed: number; passed: number; cancelled: number; skipped: number; todo: number; topLevel: number; suites: number; }; success: boolean; isFilteringByOnly: boolean; /** True when either pattern list is non-null — runner.ts's commandLineDefaults() * path (a later CLI parser would set this the same way). Read once per subtest, in * the constructor, to decide whether to call willBeFilteredByName() at all. */ isFilteringByName: boolean; reporter?: TestsStream; reported?: boolean; snapshotManager?: SnapshotManager; /** Node's harness.bootstrapPromise: null once resolved, otherwise every top-level * test()/describe() call (harness.ts's startSubtestAfterBootstrap) awaits it before * actually starting. createTestTree() seeds it with an already-resolved promise * (Node's runBootstrap(), which only does real async work for options.globalSetupPath * — unwired here, see runner.ts's header); run()'s isolation:'none' path (runner.ts's * runIsolatedNone) replaces it with one that stays pending until every test file has * loaded, which is the entire race fix: no top-level test can start — let alone * finish and trigger finalize()'s "every top-level test is done" check below — before * every file has had a chance to register its own. */ bootstrapPromise?: Promise<void> | null; /** Node's harness.buildPromise: resolves once every *top-level* Suite's buildSuite * (and, transitively, everything synchronously reachable from it) has settled — * harness.ts's startSubtestAfterBootstrap awaits it, nulling it once, before any * top-level test()/describe() actually starts(); Test#start() (below) re-checks it * so a nested subtest started from inside a Suite.run() that happens to race ahead * of the gate also defers. Seeded with a pending promise in createTestTree(); * resolved by waitForBuildPhase() once harness.buildSuites settles. This is the * fix for exactly the bug this file's history records: without it, a describe() * whose builder callback awaits (an async describe) lets sibling top-level tests * that build synchronously start and finish first, so the spec reporter sees them * — and reports them — before the async describe's own subtests ever got attached. */ buildPromise?: Promise<void> | null; /** Node's harness.buildSuites: every top-level Suite's `buildSuite` promise, * collected by startSubtestAfterBootstrap as each top-level describe() registers, * then awaited together (Promise.all) by waitForBuildPhase() before buildPromise * resolves. */ buildSuites?: Promise<void>[]; /** Node's harness.isWaitingForBuildPhase: set once a microtask has been queued to * call waitForBuildPhase(), so the second (and later) top-level registration in the * same synchronous turn doesn't queue a redundant one. */ isWaitingForBuildPhase?: boolean; /** Node's harness.waitForBuildPhase(): awaits every buildSuites entry collected so * far, then resolves buildPromise. Queued as a microtask by the first top-level * registration, so by the time it runs, every top-level test()/describe() call made * synchronously in this turn has already pushed its own buildSuite into the array. */ waitForBuildPhase?: () => Promise<void>;}
export interface TestOptions { name?: string; fn?: TestFn; parent?: Test | null; concurrency?: number | boolean; only?: boolean; timeout?: number; todo?: boolean | string; skip?: boolean | string; signal?: AbortSignal; plan?: number; expectFailure?: ExpectFailure; loc?: [number, number, string]; harness?: Harness; entryFile?: string; hookType?: HookName;}
export type ExpectFailure = boolean | string | RegExp | ((err: unknown) => boolean) | { label?: string; match?: RegExp | ((err: unknown) => boolean) };type ParsedExpectFailure = false | { label: string | undefined; match: RegExp | ((err: unknown) => boolean) | undefined };
function parseExpectFailure(expectFailure: ExpectFailure | undefined): ParsedExpectFailure { if (expectFailure === undefined || expectFailure === false) return false; if (typeof expectFailure === "string") return { label: expectFailure, match: undefined }; if (typeof expectFailure === "function" || expectFailure instanceof RegExp) return { label: undefined, match: expectFailure }; if (typeof expectFailure !== "object") return { label: undefined, match: undefined }; const keys = Object.keys(expectFailure); if (keys.length === 0) throw invalidArgValue("options.expectFailure", expectFailure, "must not be an empty object"); if (keys.every((k) => k === "match" || k === "label")) return { label: expectFailure.label, match: expectFailure.match }; return { label: undefined, match: undefined };}
export type TestFn = (ctx: TestContext | SuiteContext, cb?: (err?: unknown) => void) => unknown;
export interface RunArgs { ctx: TestContext | SuiteContext; args: [TestContext] | [SuiteContext]; }
export class TestPlan { expected: number; actual = 0; wait: boolean | number | undefined; #waitIndefinitely = false; #planPromise: { promise: Promise<void>; resolve: () => void; reject: (err: unknown) => void } | null = null; #timeoutId: ReturnType<typeof setTimeout> | null = null;
constructor(count: number, options: { wait?: boolean | number } = {}) { validateUint32(count, "count"); if (options === null || typeof options !== "object") throw invalidArgType("options", "Object", options); this.expected = count; const { wait } = options; if (typeof wait === "boolean") { this.wait = wait; this.#waitIndefinitely = wait; } else if (typeof wait === "number") { validateNumber(wait, "options.wait", 0, TIMEOUT_MAX); this.wait = wait; } else if (wait !== undefined) { throw invalidArgType("options.wait", "one of type boolean or number", wait); } }
#planMet(): boolean { return this.actual === this.expected; }
#shouldWait(): boolean { return this.wait !== undefined && this.wait !== false; }
check(): Promise<void> | undefined { if (this.#planMet()) { if (this.#timeoutId !== null) { clearTimeout(this.#timeoutId); this.#timeoutId = null; } if (this.#planPromise) { this.#planPromise.resolve(); this.#planPromise = null; } return; } if (!this.#shouldWait()) { throw testFailure(`plan expected ${this.expected} assertions but received ${this.actual}`, kTestCodeFailure); } if (!this.#planPromise) { let resolve!: () => void; let reject!: (err: unknown) => void; const promise = new Promise<void>((res, rej) => { resolve = res; reject = rej; }); this.#planPromise = { promise, resolve, reject }; if (!this.#waitIndefinitely) { this.#timeoutId = setTimeout(() => { reject(testFailure( `plan timed out after ${this.wait}ms with ${this.actual} assertions when expecting ${this.expected}`, kTestTimeoutFailure, )); }, this.wait as number); } } return this.#planPromise.promise; }
count(): void { this.actual++; if (this.#planPromise) this.check(); }}
function getFullName(test: Test): string { if (test === test.root) return test.name; let fullName = test.name; for (let t = test.parent!; t !== t.root; t = t.parent!) fullName = `${t.name} > ${fullName}`; return fullName;}
function stopTest(timeout: number | null, signal: AbortSignal): { promise: Promise<void>; dispose(): void } { let resolve!: () => void; const base = new Promise<void>((res) => (resolve = res)); const onAbort = (): void => resolve(); signal.addEventListener("abort", onAbort); let timer: ReturnType<typeof setTimeout> | undefined; let promise = base; let dispose: () => void; if (timeout === kDefaultTimeout) { dispose = () => signal.removeEventListener("abort", onAbort); } else { timer = setTimeout(resolve, timeout!); (timer as unknown as { unref?: () => void }).unref?.(); promise = base.then(() => { throw testFailure(`test timed out after ${timeout}ms`, kTestTimeoutFailure); }); dispose = () => { signal.removeEventListener("abort", onAbort); clearTimeout(timer); }; } return { promise, dispose };}
export class TestContext { #assert: Record<string, (...args: unknown[]) => unknown> | undefined; #test: Test;
constructor(test: Test) { this.#test = test; }
get signal(): AbortSignal { return this.#test.signal; }
get name(): string { return this.#test.name; }
get filePath(): string | undefined { return this.#test.entryFile; }
get fullName(): string { return getFullName(this.#test); }
get error(): unknown { return this.#test.error; }
get passed(): boolean { return this.#test.passed; }
get attempt(): number { return 0; }
get workerId(): number | undefined { return undefined; }
diagnostic(message: string): void { this.#test.diagnostic(message); }
plan(count: number, options: { wait?: boolean | number } = {}): void { if (this.#test.plan !== null) throw testFailure("cannot set plan more than once", kTestCodeFailure); this.#test.plan = new TestPlan(count, options); }
get assert(): Record<string, (...args: unknown[]) => unknown> { if (this.#assert === undefined) { const test = this.#test; const map = getAssertionMap(); // Node's lazyAssertObject(harness): the shared assertion map gains 'snapshot'/ // 'fileSnapshot' once, ever, for the process — the SnapshotManager instance is // created from whichever harness happens to reach here first (there is only // ever one harness per worker here, so this always matches Node). if (!map.has("snapshot")) { // A subtest's own `.harness` field is null (only the root Test carries it — // see Test's constructor/`harness` field above); every test reaches the same // harness through `.root.harness`, matching Node's `this.#test.root.harness`. const harness = test.root.harness!; const manager = new SnapshotManager(harness.config.updateSnapshots); harness.snapshotManager = manager; map.set("snapshot", manager.createAssert() as (...args: unknown[]) => unknown); map.set("fileSnapshot", manager.createFileAssert() as (...args: unknown[]) => unknown); } const assertObj: Record<string, (...args: unknown[]) => unknown> = {}; this.#assert = assertObj; for (const [name, method] of map) { assertObj[name] = (...args: unknown[]) => { if (test.plan !== null) test.plan.count(); return method.apply(this, args); }; } if (!map.has("ok")) { assertObj.ok = (...args: unknown[]) => { if (test.plan !== null) test.plan.count(); nodeAssert.ok(...args); }; } } return this.#assert; }
get mock(): unknown { return this.#test.mock(); }
runOnly(value: unknown): void { this.#test.runOnlySubtests = Boolean(value); }
skip(message?: string): void { this.#test.skip(message); }
todo(message?: string): void { this.#test.todo(message); }
test(name: unknown, options?: unknown, fn?: unknown): Promise<void> { if (this.#test.plan !== null) this.#test.plan.count(); const subtest = this.#test.createSubtest(Test, name, options, fn, { loc: getCallerLocation() }); return subtest.start(); }
before(fn: unknown, options?: unknown): void { this.#test.createHook("before", fn, { ...(options as object), parent: this.#test, hookType: "before", loc: getCallerLocation() }); }
after(fn: unknown, options?: unknown): void { this.#test.createHook("after", fn, { ...(options as object), parent: this.#test, hookType: "after", loc: getCallerLocation() }); }
beforeEach(fn: unknown, options?: unknown): void { this.#test.createHook("beforeEach", fn, { ...(options as object), parent: this.#test, hookType: "beforeEach", loc: getCallerLocation() }); }
afterEach(fn: unknown, options?: unknown): void { this.#test.createHook("afterEach", fn, { ...(options as object), parent: this.#test, hookType: "afterEach", loc: getCallerLocation() }); }
/** Node's TestContext#waitFor: poll `condition` every `interval`ms (default 50) until * it returns/resolves truthy-or-whatever, or `timeout`ms (default 1000) elapses. */ waitFor(condition: unknown, options: { interval?: number; timeout?: number } = {}): Promise<unknown> { if (typeof condition !== "function") throw invalidArgType("condition", "of type function", condition); if (options === null || typeof options !== "object") throw invalidArgType("options", "Object", options); const { interval = 50, timeout = 1000 } = options; validateNumber(interval, "options.interval", 0, TIMEOUT_MAX); validateNumber(timeout, "options.timeout", 0, TIMEOUT_MAX); let resolve!: (v: unknown) => void; let reject!: (e: unknown) => void; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); let cause: unknown; const noError = Symbol(); let causeSet = false; let pollerId: ReturnType<typeof setTimeout> | undefined; const timeoutId = setTimeout(() => { clearTimeout(pollerId); const err = new Error("waitFor() timed out"); if (causeSet) (err as Error & { cause?: unknown }).cause = cause; reject(err); }, timeout); const poller = async (): Promise<void> => { try { const result = await (condition as () => unknown)(); clearTimeout(timeoutId); resolve(result); } catch (err) { cause = err; causeSet = true; pollerId = setTimeout(poller, interval); } }; void poller(); void noError; return promise; }}
export class SuiteContext { #suite: Suite;
constructor(suite: Suite) { this.#suite = suite; }
get signal(): AbortSignal { return this.#suite.signal; }
get name(): string { return this.#suite.name; }
get filePath(): string | undefined { return this.#suite.entryFile; }
get fullName(): string { return getFullName(this.#suite); }
get passed(): boolean { return this.#suite.passed; }
get attempt(): number { return 0; }
diagnostic(message: string): void { this.#suite.diagnostic(message); }}
interface PendingSubtest { test: Test; resolve: () => void;}
export class Test { reportedType: "test" | "suite" = "test"; abortController = new AbortController(); outerSignal: AbortSignal | undefined; signal: AbortSignal; name: string; parent: Test | null; root: Test; harness: Harness | null = null; config: Harness["config"]; concurrency = 1; nesting: number; only: boolean | undefined; reporter: TestsStream; runOnlySubtests: boolean; childNumber: number; timeout: number | null; entryFile: string | undefined; testId: number; loc: Loc | undefined; fn: TestFn; mockTracker: MockTracker | null = null; plan: TestPlan | null = null; expectedAssertions: number | undefined; cancelled = false; expectFailure: ParsedExpectFailure; skipped: boolean; isTodo: boolean; startTime: number | null = null; endTime: number | null = null; passed = false; error: unknown = null; message: string | null; activeSubtests = 0; pendingSubtests: PendingSubtest[] = []; pendingSubtestsScheduled = false; readySubtests = new Map<number, Test>(); unfinishedSubtests = new Set<Test>(); subtestsPromise: { promise: Promise<void>; resolve: () => void } | null = null; subtests: Test[] = []; nextReportOrder = 1; reportOrder = 0; waitingOn = 0; finished = false; filtered = false; filteredByName = false; hasOnlyTests = false; testNumber = 0; outputSubtestCount = 0; diagnostics: string[] = []; nextTestId = 1; hookType: HookName | undefined; reported = false; #reportedSubtest = false; hooks: { before: TestHook[]; after: TestHook[]; beforeEach: TestHook[]; afterEach: TestHook[]; ownAfterEachCount: number } = { before: [], after: [], beforeEach: [], afterEach: [], ownAfterEachCount: 0, }; /** Always false on a plain Test; Suite's own field (below) flips to true once its * createBuild() settles. Declared here (not just on Suite) so createSubtest's guard * can read it on any parent without an instanceof check, matching Node's base * Test#buildPhaseFinished, which is likewise undefined (falsy) off a Suite. */ buildPhaseFinished = false;
constructor(options: TestOptions) { let { fn, name, parent = null } = options; const { concurrency, entryFile, expectFailure, loc, only, timeout, todo, skip, signal, plan } = options; if (typeof fn !== "function") fn = noop; if (typeof name !== "string" || name === "") name = fn.name || "<anonymous>"; if (!(parent instanceof Test)) parent = null;
this.name = name; this.parent = parent;
if (parent === null) { this.root = this; this.harness = options.harness!; this.config = this.harness.config; this.concurrency = 1; this.nesting = 0; this.only = this.config.only; this.reporter = new TestsStream(); this.runOnlySubtests = this.only; this.childNumber = 0; this.timeout = kDefaultTimeout; this.entryFile = entryFile; this.testId = 0; } else { const nesting = parent.parent === null ? parent.nesting : parent.nesting + 1; const { config, isFilteringByOnly, isFilteringByName } = parent.root.harness!; this.root = parent.root; this.config = config; this.concurrency = parent.concurrency; this.nesting = nesting; this.only = only; this.reporter = parent.reporter; this.runOnlySubtests = false; this.childNumber = parent.subtests.length + 1; this.timeout = parent.timeout; this.entryFile = parent.entryFile; this.testId = this.root.nextTestId++;
if (isFilteringByName) { this.filteredByName = this.willBeFilteredByName(); if (!this.filteredByName) { for (let t: Test | null = this.parent; t !== null && t.filteredByName; t = t.parent) t.filteredByName = false; } }
if (isFilteringByOnly) { if (this.only) { this.parent!.runOnlySubtests = true; if (this.parent === this.root || this.parent!.startTime === null) { for (let t: Test | null = this.parent; t !== null && !t.hasOnlyTests; t = t.parent) t.hasOnlyTests = true; } } else if (this.only === false) { fn = noop; } } }
switch (typeof concurrency) { case "number": validateUint32(concurrency, "options.concurrency", true); this.concurrency = concurrency; break; case "boolean": this.concurrency = concurrency ? (parent === null ? 1 : Infinity) : 1; break; default: if (concurrency != null) throw invalidArgType("options.concurrency", "one of type boolean or number", concurrency); }
if (timeout != null && timeout !== Infinity) { validateNumber(timeout, "options.timeout", 0, TIMEOUT_MAX); this.timeout = timeout; } else if (timeout == null) { const cliTimeout = this.config.timeout; if (cliTimeout != null && cliTimeout !== Infinity) { validateNumber(cliTimeout, "this.config.timeout", 0, TIMEOUT_MAX); this.timeout = cliTimeout; } }
if (skip) fn = noop;
this.outerSignal = signal; this.signal = this.abortController.signal; validateAbortSignal(signal, "options.signal"); this.outerSignal?.addEventListener("abort", this.#abortHandler);
this.fn = fn; this.expectedAssertions = plan; this.expectFailure = parseExpectFailure(expectFailure) || (this.parent?.expectFailure ?? false); this.skipped = skip !== undefined && skip !== false; this.isTodo = (todo !== undefined && todo !== false) || Boolean(this.parent?.isTodo); this.message = typeof skip === "string" ? skip : typeof todo === "string" ? todo : null; this.loc = loc === undefined ? undefined : { line: loc[0], column: loc[1], file: loc[2] }; }
applyFilters(): void { if (this.error) return; if (this.filteredByName) { this.filtered = true; return; } if (this.root.harness!.isFilteringByOnly && !this.only && !this.hasOnlyTests) { if (this.parent!.runOnlySubtests || this.parent!.hasOnlyTests || this.only === false) this.filtered = true; } }
/** Node's test.js willBeFilteredByName(): read once, from the constructor, against * harness.config.testNamePatterns/testSkipPatterns — null for every path here today * (direct execution, and run(), whose own testNamePatterns/testSkipPatterns option * Node itself never wires into this config for isolation:'none' — see runner.ts's * header comment), so this is dead code until a later CLI flag parser sets either * field through commandLineDefaults(). Kept so that wiring has somewhere to land. */ willBeFilteredByName(): boolean { const { testNamePatterns, testSkipPatterns } = this.config; if (testNamePatterns && !testMatchesPattern(this, testNamePatterns)) return true; if (testSkipPatterns && testMatchesPattern(this, testSkipPatterns)) return true; return false; }
/** Node's test.js getTestNameWithAncestors(): "grandparent parent test", used by * testMatchesPattern so a pattern can match a test by its full nested name. */ getTestNameWithAncestors(): string { if (!this.parent) return ""; return `${this.parent.getTestNameWithAncestors()} ${this.name}`; }
hasConcurrency(): boolean { return this.concurrency > this.activeSubtests; }
addPendingSubtest(p: PendingSubtest): void { this.pendingSubtests.push(p); }
schedulePendingSubtests(): void { if (this.pendingSubtestsScheduled) return; this.pendingSubtestsScheduled = true; queueMicrotask(() => { this.pendingSubtestsScheduled = false; void this.processPendingSubtests(); }); }
assignReportOrder(subtest: Test): void { if (subtest.reportOrder === 0) subtest.reportOrder = this.nextReportOrder++; }
async processPendingSubtests(): Promise<void> { while (this.pendingSubtests.length > 0 && this.hasConcurrency()) { const deferred = this.pendingSubtests.shift()!; const test = deferred.test; this.assignReportOrder(test); test.reporter.dequeue(test.nesting, test.loc, test.name, this.reportedType, test.testId); await test.run(); deferred.resolve(); } }
addReadySubtest(subtest: Test): void { this.assignReportOrder(subtest); this.readySubtests.set(subtest.reportOrder, subtest); if (this.unfinishedSubtests.delete(subtest) && this.unfinishedSubtests.size === 0) this.subtestsPromise!.resolve(); }
isClearToSend(): boolean { return this.parent === null || (this.parent.waitingOn === this.reportOrder && this.parent.isClearToSend()); }
processReadySubtestRange(canSend: boolean): void { const start = this.waitingOn; const end = start + this.readySubtests.size; for (let i = start; i < end; i++) { const subtest = this.readySubtests.get(i); if (subtest === undefined) return; canSend ||= this.isClearToSend(); if (!canSend) return; subtest.finalize(); this.readySubtests.delete(i); } }
createSubtest(Factory: typeof Test, nameArg: unknown, optionsArg: unknown, fnArg: unknown, overrides: Partial<TestOptions>): Test { let name = nameArg; let options = optionsArg; let fn = fnArg; if (typeof name === "function") { fn = name; name = undefined; } else if (name !== null && typeof name === "object") { fn = options; options = name; name = undefined; } else if (typeof options === "function") { fn = options; options = undefined; } if (options === null || typeof options !== "object") options = {};
let parent: Test = this; // Node's test.js: `this.finished || this.buildPhaseFinished` — a describe() whose // own builder callback already settled (e.g. it threw, or just returned) can still // have a stray test()/describe() call land on it afterward (a callback that fires // late); attach that one to the root instead, same as a call on an already-finished // test. const preventAddingSubtests = this.finished || this.buildPhaseFinished; if (preventAddingSubtests) { while (parent.parent !== null) parent = parent.parent; }
const test = new Factory({ fn: fn as TestFn, name: name as string | undefined, parent, ...(options as object), ...overrides });
if (parent.waitingOn === 0) { parent.waitingOn = test.childNumber; let resolve!: () => void; const promise = new Promise<void>((res) => (resolve = res)); parent.subtestsPromise = { promise, resolve }; }
if (preventAddingSubtests) { test.fail(testFailure("test could not be started because its parent finished", kParentAlreadyFinished)); }
parent.subtests.push(test); return test; }
#abortHandler = (): void => { const error = Object.assign(new Error("The test was aborted"), { name: "AbortError", failureType: kAborted }); this.#cancel(error); };
#cancel(error?: unknown): void { if (this.endTime !== null || this.error !== null) return; this.fail(error ?? testFailure("test did not finish before its parent and was cancelled", kCancelledByParent)); this.cancelled = true; this.abortController.abort(); }
computeInheritedHooks(): void { if (this.parent!.hooks.beforeEach.length > 0) this.hooks.beforeEach.unshift(...this.parent!.hooks.beforeEach); if (this.parent!.hooks.afterEach.length > 0) this.hooks.afterEach.push(...this.parent!.hooks.afterEach); }
createHook(name: HookName, fn: unknown, options: { loc?: [number, number, string] } & Record<string, unknown>): void { if (!kHookNames.includes(name)) throw invalidArgValue("hook name", name, `must be one of: ${kHookNames.map((n) => `'${n}'`).join(", ")}`); const hook = new TestHook(fn as TestFn, options as unknown as TestOptions & { parent: Test; hookType: HookName }); if (name === "before" || name === "after") { const run = hook.run.bind(hook); let ran = false; let result: Promise<void> | undefined; hook.run = (args?: RunArgs) => { if (!ran) { ran = true; result = run(args); } return result!; }; } if (name === "before" && this.startTime !== null) { void hook.run(this.getRunArgs()).then(() => { if (hook.error != null) this.fail(hook.error); }); } if (name === "afterEach") { this.hooks.afterEach.splice(this.hooks.ownAfterEachCount, 0, hook); this.hooks.ownAfterEachCount++; } else { this.hooks[name].push(hook); } }
fail(err: unknown): void { if (this.error !== null) return; if (this.expectFailure) { if (this.expectFailure.match !== undefined) { const match = this.expectFailure.match; const e = err as { code?: string; failureType?: string; cause?: unknown }; const errorToCheck = e?.code === "ERR_TEST_FAILURE" && e?.failureType === kTestCodeFailure && e.cause !== undefined ? e.cause : err; const ok = match instanceof RegExp ? match.test(String((errorToCheck as Error)?.message ?? errorToCheck)) : typeof match === "function" ? Boolean((match as (e: unknown) => boolean)(errorToCheck)) : false; if (!ok) { this.passed = false; this.error = testFailure("The test failed, but the error did not match the expected validation", kTestCodeFailure); (this.error as { cause?: unknown }).cause = errorToCheck; return; } } this.passed = true; } else { this.passed = false; } this.error = err; }
pass(): void { if (this.error == null && this.expectFailure && !this.skipped) { this.passed = false; this.error = testFailure("test was expected to fail but passed", kExpectedFailure); return; } if (this.error !== null) return; if (this.skipped || this.isTodo) { this.passed = true; return; } if (this.expectFailure) { this.passed = false; this.error = testFailure("Test passed but was expected to fail", kTestCodeFailure); return; } this.passed = true; }
skip(message?: string): void { this.skipped = true; this.message = message ?? null; }
todo(message?: string): void { this.isTodo = true; this.message = message ?? null; }
diagnostic(message: string): void { this.diagnostics.push(message); }
mock(): unknown { this.mockTracker ??= createMockTracker(); return this.mockTracker; }
start(): Promise<void> { this.applyFilters(); if (!this.filtered) this.testNumber = ++this.parent!.outputSubtestCount;
this.parent!.unfinishedSubtests.add(this); this.reporter.enqueue(this.nesting, this.loc, this.name, this.reportedType, this.testId); // Node's test.js start(): `this.root.harness.buildPromise || !this.parent.hasConcurrency()`. // harness.ts's startSubtestAfterBootstrap already awaits (and nulls) buildPromise // before calling a top-level subtest's start(), so this branch of the check is // normally moot for a top-level call; it matters for a subtest started directly by // a Suite's own run() (its `subtests.map(s => s.start())`), which re-checks the same // shared root harness rather than assuming the gate already cleared. if (this.root.harness!.buildPromise || !this.parent!.hasConcurrency()) { let resolve!: () => void; const promise = new Promise<void>((res) => (resolve = res)); this.parent!.addPendingSubtest({ test: this, resolve }); return promise; }
this.parent!.assignReportOrder(this); this.reporter.dequeue(this.nesting, this.loc, this.name, this.reportedType, this.testId); return this.run(); }
shouldAbort(): boolean { if (this.signal.aborted || this.outerSignal?.aborted) { this.#abortHandler(); return true; } return false; }
getRunArgs(): RunArgs { const ctx = new TestContext(this); return { ctx, args: [ctx] }; }
async runHook(hookName: HookName, args: RunArgs): Promise<void> { try { const hooks = this.hooks[hookName]; for (const hook of hooks) { await hook.run(args); if (hook.error) throw hook.error; } } catch (err) { const error = testFailure(`failed running ${hookName} hook`, kHookFailure); error.cause = isTestFailureError(err) ? err.cause : err; throw error; } }
async run(): Promise<void> { if (this.parent !== null) { this.parent.activeSubtests++; this.computeInheritedHooks(); } this.startTime ??= performance.now();
if (this.shouldAbort()) { this.postRun(); return; }
const hookArgs = this.getRunArgs(); const { args } = hookArgs; const ctx = hookArgs.ctx as TestContext; if (this.plan === null && this.expectedAssertions) ctx.plan(this.expectedAssertions);
const wasSkippedBeforeRun = this.skipped; const after = async (): Promise<void> => { if (this.hooks.after.length > 0) await this.runHook("after", hookArgs); }; let afterEachRan = false; let afterEachPromise: Promise<void> | undefined; const afterEach = (): Promise<void> => { if (!afterEachRan) { afterEachRan = true; afterEachPromise = (async () => { if ((this.parent?.hooks.afterEach.length ?? 0) > 0 && !wasSkippedBeforeRun) { await this.parent!.runHook("afterEach", hookArgs); } })(); } return afterEachPromise!; };
let stop: { promise: Promise<void>; dispose(): void } | undefined; const previouslyRunning = currentlyRunning; currentlyRunning = this; try { if ((this.parent?.hooks.before.length ?? 0) > 0) await this.parent!.runHook("before", this.parent!.getRunArgs()); if ((this.parent?.hooks.beforeEach.length ?? 0) > 0 && !this.skipped) await this.parent!.runHook("beforeEach", hookArgs);
stop = stopTest(this.timeout, this.signal); const runArgs: unknown[] = [...args];
const promises: Promise<unknown>[] = []; if (this.fn.length === runArgs.length + 1) { // Legacy Node-style error-first callback. const { promise, cb } = createDeferredCallback(); const ret = this.fn(ctx, cb); if (ret && typeof (ret as Promise<unknown>).then === "function") { this.fail(testFailure("passed a callback but also returned a Promise", kCallbackAndPromisePresent)); promises.push(Promise.resolve(ret)); } else { promises.push(promise); } } else { promises.push(Promise.resolve(this.fn(ctx))); } promises.push(stop.promise); await Promise.race(promises);
this.shouldAbort();
if (this.subtestsPromise !== null) await Promise.race([this.subtestsPromise.promise, stop.promise]);
if (this.plan !== null) { const planPromise = this.plan.check(); if (planPromise) await Promise.race([planPromise, stop.promise]); }
this.pass(); await afterEach(); await after(); } catch (err) { if (isTestFailureError(err)) { if (err.failureType === kTestTimeoutFailure) this.#cancel(err); else this.fail(err); } else { this.fail(testFailure(err, kTestCodeFailure)); } try { await afterEach(); } catch { /* test is already failing */ } try { await after(); } catch { /* ignore */ } } finally { stop?.dispose(); currentlyRunning = previouslyRunning; if (this.parent !== null) this.abortController.abort(); }
this.postRun(); }
postRun(pendingSubtestsError?: unknown): void { this.endTime ??= performance.now(); this.startTime ??= this.endTime;
this.pendingSubtests = []; let failed = 0; for (const subtest of this.subtests) { if (!subtest.finished) { subtest.#cancel(pendingSubtestsError); subtest.postRun(pendingSubtestsError); } if (!subtest.passed && !subtest.isTodo) failed++; }
if ((this.passed || this.parent === null) && failed > 0) { const subtestString = `subtest${failed > 1 ? "s" : ""}`; this.fail(testFailure(`${failed} ${subtestString} failed`, kSubtestsFailed)); }
this.outerSignal?.removeEventListener("abort", this.#abortHandler); // Node's postRun(): `this.mock?.reset()`, restoring every mock the test made and // disabling its mock timers, whether the test passed, failed or never mocked // anything (reset() is a no-op in that last case). this.mockTracker?.reset();
if (this.parent !== null) { if (!this.filtered) { const report = this.getReportDetails(); (report.details as { passed?: boolean }).passed = this.passed; this.testNumber ||= ++this.parent.outputSubtestCount; this.reporter.complete(this.nesting, this.loc, this.testNumber, this.name, report.details, report.directive, this.testId); this.parent.activeSubtests--; } this.parent.addReadySubtest(this); this.parent.processReadySubtestRange(false); void this.parent.processPendingSubtests(); } else if (!this.reported) { const { diagnostics, harness, loc, nesting, reporter } = this; this.reported = true; reporter.plan(nesting, loc, harness!.counters.topLevel); // Node's postRun(): write recorded snapshots before diagnostics, since a failed // write is itself reported as a diagnostic. harness!.snapshotManager?.writeSnapshotFiles(); for (const d of diagnostics) reporter.diagnostic(nesting, loc, d); const duration = this.duration(); reporter.diagnostic(nesting, loc, `tests ${harness!.counters.tests}`); reporter.diagnostic(nesting, loc, `suites ${harness!.counters.suites}`); reporter.diagnostic(nesting, loc, `pass ${harness!.counters.passed}`); reporter.diagnostic(nesting, loc, `fail ${harness!.counters.failed}`); reporter.diagnostic(nesting, loc, `cancelled ${harness!.counters.cancelled}`); reporter.diagnostic(nesting, loc, `skipped ${harness!.counters.skipped}`); reporter.diagnostic(nesting, loc, `todo ${harness!.counters.todo}`); reporter.diagnostic(nesting, loc, `duration_ms ${duration}`); reporter.summary(nesting, loc?.file, harness!.success, harness!.counters, duration); reporter.end(); } }
finalize(): void { this.processReadySubtestRange(true); this.report(); this.parent!.waitingOn++; this.finished = true;
// `waitingOn > subtests.length` means every top-level test has finished — safe to // rely on because no top-level test can even start until harness.ts's // startSubtestAfterBootstrap has resolved its `bootstrapPromise` wait, and (for // run()'s isolation:'none' path) that promise only resolves once every test file // has loaded and registered its own top-level tests. Give the root test's own // run() a chance to execute (it reports the final summary once it returns), same // as Node's harness invoking this from the last subtest's finalize() rather than // waiting for a separate 'beforeExit' turn. if (this.parent === this.root && this.root.waitingOn > this.root.subtests.length) { void this.root.run(); } }
duration(): number { return (this.endTime! - this.startTime!); }
getReportDetails(): { details: Record<string, unknown>; directive: Record<string, unknown> | undefined } { let directive: Record<string, unknown> | undefined; const details: Record<string, unknown> = { duration_ms: this.duration() }; if (this.skipped) directive = this.reporter.getSkip(this.message ?? undefined); else if (this.isTodo) directive = this.reporter.getTodo(this.message ?? undefined); else if (this.expectFailure) directive = this.reporter.getXFail(this.expectFailure.label);
if (this.reportedType) details.type = this.reportedType; if (!this.passed) details.error = this.error; return { details, directive }; }
report(): void { countCompletedTest(this); if (this.outputSubtestCount > 0) this.reporter.plan(this.subtests[0]!.nesting, this.loc, this.outputSubtestCount); else this.reportStarted(); const report = this.getReportDetails(); if (this.passed) this.reporter.ok(this.nesting, this.loc, this.testNumber, this.name, report.details, report.directive, this.testId); else this.reporter.fail(this.nesting, this.loc, this.testNumber, this.name, report.details, report.directive, this.testId); for (const d of this.diagnostics) this.reporter.diagnostic(this.nesting, this.loc, d); }
reportStarted(): void { if (this.#reportedSubtest || this.parent === null) return; this.#reportedSubtest = true; this.parent.reportStarted(); this.reporter.start(this.nesting, this.loc, this.name, this.testId); }}
export class TestHook extends Test { override reportedType: "test" | "suite" = "test"; parentTest: Test; #args: RunArgs | undefined;
constructor(fn: TestFn, options: TestOptions & { parent: Test; hookType: HookName }) { const { hookType, loc, parent, timeout, signal } = options; super({ fn, loc, timeout, signal, harness: parent.root.harness! }); this.parentTest = parent; this.hookType = hookType; }
override run(args?: RunArgs): Promise<void> { if (args) this.#args = args; if (this.error && !this.outerSignal?.aborted) { this.passed = false; this.error = null; this.abortController.abort(); this.abortController = new AbortController(); this.signal = this.abortController.signal; } return super.run(); }
override getRunArgs(): RunArgs { return this.#args!; }
override postRun(): void { const { error, loc, parentTest: parent } = this; if (error && parent === parent.root && this.hookType === "after") { if (isTestFailureError(error)) error.failureType = kHookFailure; this.endTime ??= performance.now(); parent.reporter.fail(0, loc, parent.subtests.length + 1, loc!.file, { duration_ms: this.duration(), error }, undefined, 0); } }}
export class Suite extends Test { override reportedType: "test" | "suite" = "suite"; buildSuite: Promise<void>;
constructor(options: TestOptions) { super(options); if (options.timeout == null) this.timeout = null; // Node's test.js also re-enables a `skip`ped suite's own fn here when BOTH // testNamePatterns and testSkipPatterns are non-null, so a name-filtered run can // still see inside it. Omitted: both fields are always null on every path this // kernel reaches today (see the file header), so the condition can never be true. this.buildSuite = this.createBuild(options.fn ?? noop); this.fn = noop; }
async createBuild(fn: TestFn): Promise<void> { try { const { ctx } = this.getRunArgs(); let result: unknown; pushBuildingParent(this); try { result = fn(ctx as never); } finally { popBuildingParent(); } await result; } catch (err) { this.fail(testFailure(err, kTestCodeFailure)); } finally { this.buildPhaseFinished = true; } }
override getRunArgs(): RunArgs { const ctx = new SuiteContext(this); return { ctx, args: [ctx] }; }
override async run(): Promise<void> { this.computeInheritedHooks(); const hookArgs = this.getRunArgs();
let stop: { promise: Promise<void>; dispose(): void } | undefined; let afterRan = false; const after = async (): Promise<void> => { if (!afterRan) { afterRan = true; await this.runHook("after", hookArgs); } }; try { this.parent!.activeSubtests++; await this.buildSuite; this.startTime = performance.now();
if (this.shouldAbort()) { this.subtests = []; this.postRun(); return; }
if (this.parent!.hooks.before.length > 0) await this.parent!.runHook("before", this.parent!.getRunArgs()); await this.runHook("before", hookArgs);
stop = stopTest(this.timeout, this.signal); const subtests = this.skipped || this.error ? [] : this.subtests; const promise = Promise.all(subtests.map((s) => s.start())); await Promise.race([promise, stop.promise]); await after(); this.pass(); } catch (err) { try { await after(); } catch { /* suite is already failing */ } if (isTestFailureError(err)) this.fail(err); else this.fail(testFailure(err, kTestCodeFailure)); } finally { stop?.dispose(); } this.postRun(); }}
export { getFullName };