Something went wrong. Try again.
Monorepo for the nibling.run platform
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289// 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/harness.js: the lazy bootstrap (the root `Test` is created on// the first call to test()/describe()/before()/..., not at require time), process-exit// handling (a `beforeExit` listener runs the root test and waits for pending subtests,// as the kernel's own loop already does for ordinary 'beforeExit'/'exit' semantics —// see src/runtime/loop.ts and src/runtime/main.ts), and uncaughtException/// unhandledRejection routing to whichever test is currently running.//// Divergence (recorded in compat/divergences.json under "test"): Node attributes an// async error to its owning test via async_hooks.createHook, tagging every async// resource at creation with testResources.set(asyncId, currentTest). That hook type is// refused here (DESIGN.md's refusals: async_hooks.createHook needs engine support the// browser does not expose). The closest honest equivalent is test.ts's// `getCurrentlyRunningTest()`: "whichever test is currently running when the error// arrives" is a correct attribution for every ordinary case; it only differs from// Node's for an async resource that outlives the test that created it (a stray timer, an// unresolved promise) and fires *after* that test finished but before another started —// Node still attributes that to the dead test (and reports a "ran after the test ended"// diagnostic on the root instead of failing it); here it attributes to the root test// directly, which is also what Node does when a resource has no owner at all.import { testFailure } from "./test-failure";import { Test, Suite, getCurrentlyRunningTest, currentBuildingParent, type Harness, type TestFn, type TestOptions,} from "./test";import { commandLineDefaults, getCallerLocation, type GlobalTestOptions } from "./utils";import { SpecReporter } from "./reporter/spec";import { compose, type PipeDestination } from "../stream";import type { GuestProcess } from "../process";
export const kCancelledByParent = "cancelledByParent";
let globalRoot: Test | null = null;let installed = false;
/** One instance per worker (module state): the process this harness is wired to. Set by * the builtin factory (test.ts's top-level assembler) before any test()/describe() can * run, since every runtime worker that loads node:test needs its own process object * rather than a module-global `process`. */let boundProcess: GuestProcess | null = null;export function bindProcess(proc: GuestProcess): void { boundProcess = proc;}
/** Node's internal/test_runner/harness.js createTestTree: builds a fresh root Test * from rootOptions (concurrency/timeout/signal/entryFile/loc) and globalOptions (the * harness.config every descendant reads — see commandLineDefaults/GlobalTestOptions), * replacing the module-scoped `globalRoot` the way Node's own does (confirmed against * real Node 24.16.0: a run() call's root is what any top-level test()/describe() in a * file it loads attaches to next, because createTestTree always reassigns the same * module-level variable lazyBootstrapRoot() reads). * * `autoReport` is false for run()'s root only: direct execution * (`node file.test.js`) is Node's one unconfigured-reporter default — no CLI flag * parser exists here (DESIGN: refused), so it is always `compose(root.reporter, * SpecReporter).pipe(stdout)` plus a 'test:summary' listener that fails the process on * an unsuccessful run. run()'s API hands the caller back `root.reporter` undecorated * (confirmed against real Node 24.16.0: a failing run({isolation:'none'}) test leaves * process.exitCode untouched — the CLI, not the API, sets it when it consumes the * returned stream itself) — the caller composes/pipes/listens on it however it likes. */export function createTestTree(rootOptions: TestOptions, globalOptions: GlobalTestOptions, opts: { autoReport?: boolean } = {}): Test { const proc = boundProcess!; const isFilteringByName = globalOptions.testNamePatterns !== null || globalOptions.testSkipPatterns !== null; // Node's harness.js: `const buildPhaseDeferred = PromiseWithResolvers()`, stashed as // harness.buildPromise. See Harness#buildPromise in test.ts for what this gates. let resolveBuildPhase!: () => void; const buildPromise = new Promise<void>((res) => (resolveBuildPhase = res)); const harness: Harness = { config: { timeout: globalOptions.timeout, only: globalOptions.only, cwd: globalOptions.cwd, updateSnapshots: globalOptions.testUpdateSnapshots, testNamePatterns: globalOptions.testNamePatterns, testSkipPatterns: globalOptions.testSkipPatterns, }, counters: { tests: 0, failed: 0, passed: 0, cancelled: 0, skipped: 0, todo: 0, topLevel: 0, suites: 0 }, success: true, // Direct execution (`node file.test.js`, no --test flag) and run({isolation: // 'none'}) both always filter by `only`, confirmed against real Node 24.16.0: // harness.js's isFilteringByOnly is the CLI-dependent expression `(isolation === // 'process' || NODE_TEST_CONTEXT) ? only : true`, and neither condition is ever // true for either path here — isolation is never 'process' (that refuses) and // NODE_TEST_CONTEXT is never set (only an isolation:'process' child gets it). isFilteringByOnly: true, isFilteringByName, buildPromise, buildSuites: [], isWaitingForBuildPhase: false, // Node's harness.js waitForBuildPhase(): await every top-level Suite's buildSuite // collected so far, then resolve buildPromise so every startSubtestAfterBootstrap // call waiting on it (and so every top-level test) can finally start. Node's own // version awaits them together through `SafePromiseAllReturnVoid` — a primordial // that iterates by hand rather than the native Promise.all combinator — not // `Promise.all` itself; a build that throws keeps the same stack either way, but // V8 tags a rejection awaited *through the native combinator* with an extra // `at async Promise.all (index N)` frame that SafePromiseAllReturnVoid's manual // iteration never produces (confirmed against real Node 24.16.0's golden here — // see tests/fixtures/guests/builtin-test-async-describe.cjs's "throws while // building" — and against this runtime before this loop replaced a `Promise.all` // call). Looping and awaiting each in turn is otherwise equivalent: every entry is // already running independently, so this only changes the bookkeeping of waiting // for them, not when any of them actually settles. async waitForBuildPhase(): Promise<void> { for (const buildSuite of harness.buildSuites!) await buildSuite; resolveBuildPhase(); }, }; // Node's harness.bootstrapPromise = harness.runBootstrap(): runBootstrap only does // real async work for options.globalSetupPath (unwired here — run()'s own // globalSetupPath option validates but is never consumed, see runner.ts), so this // seeds the same already-resolved promise runBootstrap() would produce for every // path this kernel reaches. startSubtestAfterBootstrap (below) awaits it before // starting any top-level test; runner.ts's runIsolatedNone replaces it with one that // stays pending until every test file has loaded. harness.bootstrapPromise = Promise.resolve(); const root = new Test({ ...rootOptions, harness, name: "<root>" }); root.startTime = performance.now(); setupProcessState(root); if (opts.autoReport !== false) { // Node's parseCommandLine().setup(): with no --test-reporter, the one reporter is // "spec" (lib/internal/test_runner/utils.js's kDefaultReporter) piped to stdout // (kDefaultDestination) via setupTestReporters's `compose(rootReporter, // reporter).pipe(destination)` — not a plain `root.reporter.pipe(spec).pipe(stdout)`. // That matters: compose()'s pull-based wiring (see stream.ts's compose) defers each // formatted line by the same nextTick hop Node's does, letting the next test's own // synchronous console.log interleave first (DESIGN: confirmed against real Node // 24.16.0; see tests/fixtures/guests/builtin-test-order.cjs). compose(root.reporter, new SpecReporter() as unknown as PipeDestination) .pipe(proc.stdout as unknown as PipeDestination); root.reporter.on("test:summary", (data: unknown) => { if (!(data as { success?: boolean }).success) proc.exitCode = 1; }); } globalRoot = root; return root;}
function setupProcessState(root: Test): void { if (installed) return; installed = true; const proc = boundProcess!;
const handleAsyncError = (eventName: "uncaughtException" | "unhandledRejection") => (err: unknown): void => { const test = getCurrentlyRunningTest() ?? root; if (!test || test.finished) { root.diagnostic( `Error: A resource generated asynchronous activity after the test ended. ` + `This activity created the error "${String(err)}" which triggered an ${eventName} event, ` + "caught by the test runner.", ); root.harness!.success = false; proc.exitCode = 1; return; } test.fail(testFailure(err, eventName)); test.abortController.abort(); };
proc.on("uncaughtException", handleAsyncError("uncaughtException")); proc.on("unhandledRejection", handleAsyncError("unhandledRejection"));
// Node's exitHandler, invoked on 'beforeExit': if the root never got any top-level // tests but registered global before/after hooks (`before()`/`after()` called at // module scope, outside any test()), run it once so those hooks still execute. Then // wait for any still-pending subtests (there should be none by the time the kernel's // loop considers itself idle, since every test here holds the loop — see // src/runtime/loop.ts — but a ref'ed handle a test created deliberately can still be // keeping the process alive) before giving root.postRun() the final word. proc.on("beforeExit", () => { void (async () => { if (root.subtests.length === 0 && (root.hooks.before.length > 0 || root.hooks.after.length > 0)) { await root.run(); } if (root.subtestsPromise !== null) { // Node's harness.js keeps a ref'd setInterval alive for exactly this wait // (`const keepAlive = setInterval(() => {}, TIMEOUT_MAX)`): without it, an // unref'd per-test timeout timer (test.ts's stopTest) never gets a chance to // fire — nothing else here holds the kernel's loop open while a test's own // promise never settles (confirmed against real Node 24.16.0: deleting this // and keeping only the unref'd timeout timer makes a hung test's timeout // silently never fire and the process exit immediately instead). const keepAlive = setInterval(() => {}, 2_147_483_647); await root.subtestsPromise.promise; clearInterval(keepAlive); } root.postRun(testFailure( "Promise resolution is still pending but the event loop has already resolved", kCancelledByParent, )); })(); });}
function lazyBootstrapRoot(): Test { if (!globalRoot) { const proc = boundProcess!; const entryFile = proc.argv[1]; createTestTree({ entryFile, loc: entryFile ? [1, 1, entryFile] : undefined }, commandLineDefaults(proc.cwd())); } return globalRoot!;}
/** Where a bare top-level test()/describe()/before()/... call attaches: the Suite * currently (synchronously) building its body (test.ts's currentBuildingParent), or * the lazily-bootstrapped root. */function currentParent(): Test { return currentBuildingParent() ?? lazyBootstrapRoot();}
/** Node's harness.js startSubtestAfterBootstrap: where every top-level test()/ * describe() call actually starts a subtest, gated on two separate promises in * sequence. * * First `root.harness.bootstrapPromise` (nulled out once awaited, so only the first * wave of callers actually waits) — the entire race fix for run()'s isolation:'none' * (runner.ts's runIsolatedNone): no top-level test can start — and so none can finish * and trigger finalize()'s "every top-level test is done" check — before that promise * resolves, which it only does once every test file has loaded. * * Then `root.harness.buildPromise`: every top-level describe()'s own `buildSuite` * promise is pushed onto `harness.buildSuites` here, as each one registers; the first * registration in a synchronous turn also queues a microtask (`waitForBuildPhase`) * that awaits all of them together once that turn's synchronous code has finished * running (and so every top-level call made in it has had a chance to push its own * buildSuite first). Only once every one of those builds has settled does * `waitForBuildPhase` resolve `buildPromise`, letting every startSubtestAfterBootstrap * call that's parked here resume and finally call `subtest.start()` — in the order * they were registered. * * Without this second gate (ported here after 6d4b5df, which only had the first), a * top-level describe() whose builder callback awaits (an "async describe") lets * sibling top-level tests that build synchronously start and finish first — so the * spec reporter prints them, flat, before the async describe's own subtests were even * attached (confirmed against real Node 24.16.0: see * tests/fixtures/guests/builtin-test-async-describe.cjs). */export async function startSubtestAfterBootstrap(subtest: Test): Promise<void> { const harness = subtest.root.harness!; if (harness.buildPromise) { if (harness.bootstrapPromise) { await harness.bootstrapPromise; harness.bootstrapPromise = null; } if (subtest instanceof Suite) harness.buildSuites!.push(subtest.buildSuite); if (!harness.isWaitingForBuildPhase) { harness.isWaitingForBuildPhase = true; queueMicrotask(() => { void harness.waitForBuildPhase!(); }); } await harness.buildPromise; harness.buildPromise = null; } await subtest.start();}
function runInParentContext(Factory: typeof Test) { function run(name: unknown, options: unknown, fn: unknown, overrides: Partial<TestOptions>): Promise<void> { const parent = currentParent(); const subtest = parent.createSubtest(Factory, name, options, fn, overrides); if (parent instanceof Suite) return Promise.resolve(); return startSubtestAfterBootstrap(subtest); }
const test = (name?: unknown, options?: unknown, fn?: unknown): Promise<void> => run(name, options, fn, { loc: getCallerLocation() }); for (const keyword of ["expectFailure", "skip", "todo", "only"] as const) { (test as unknown as Record<string, unknown>)[keyword] = (name?: unknown, options?: unknown, fn?: unknown) => run(name, options, fn, { [keyword]: true, loc: getCallerLocation() }); } return test as typeof test & { expectFailure: typeof test; skip: typeof test; todo: typeof test; only: typeof test };}
function hook(hookName: "before" | "after" | "beforeEach" | "afterEach") { return (fn: TestFn, options?: unknown): void => { const parent = currentParent(); parent.createHook(hookName, fn, { ...(options as object), parent, hookType: hookName, loc: getCallerLocation() }); };}
export const test = runInParentContext(Test);export const suite = runInParentContext(Suite);export const before = hook("before");export const after = hook("after");export const beforeEach = hook("beforeEach");export const afterEach = hook("afterEach");export { lazyBootstrapRoot };