// 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/utils.js, trimmed to what harness.ts/test.ts actually use: // the callback-invoked-twice guard, the ERR_TEST_FAILURE sniff, the counters bump and // the CLI-flag defaults. Coverage, randomize/seed, rerun-failures, --test-shard and // --test-name-pattern all read real CLI flags through getOptionValue(); none of that // parsing exists here (DESIGN: CLI flags are refused), so commandLineDefaults() is the // one place a later flag parser would feed real values in. import { testFailure, type TestFailureError } from "./test-failure"; import { invalidArgValue } from "../errors"; import type { Test } from "./test"; export const kMultipleCallbackInvocations = "multipleCallbackInvocations"; export interface DeferredCallback { promise: Promise; cb: (err?: unknown) => void; } /** Node's createDeferredCallback: resolves on the first call, rejects if it carried an * error, and fails the test (via the thrown ERR_TEST_FAILURE on the second call) if * the callback is invoked more than once. */ export function createDeferredCallback(): DeferredCallback { let calledCount = 0; let resolve!: () => void; let reject!: (err: unknown) => void; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); const cb = (err?: unknown): void => { calledCount++; if (calledCount > 1) { if (calledCount === 2) throw testFailure("callback invoked multiple times", kMultipleCallbackInvocations); return; } if (err) reject(err); else resolve(); }; return { promise, cb }; } export function isTestFailureError(err: unknown): err is TestFailureError { return Boolean(err) && typeof err === "object" && (err as { code?: string }).code === "ERR_TEST_FAILURE"; } /** Node's countCompletedTest: bumps the root's summary counters for one finished test * or suite. Skip/todo win over a failure; a suite only ever affects `suites`. */ export function countCompletedTest(test: Test): void { const harness = test.root.harness!; if (test.nesting === 0) harness.counters.topLevel++; if (test.reportedType === "suite") { harness.counters.suites++; if (!test.passed && !test.isTodo) harness.success = false; return; } if (test.skipped) harness.counters.skipped++; else if (test.isTodo) harness.counters.todo++; else if (test.cancelled) { harness.counters.cancelled++; harness.success = false; } else if (!test.passed) { harness.counters.failed++; harness.success = false; } else { harness.counters.passed++; } harness.counters.tests++; } export interface GlobalTestOptions { isTestRunner: boolean; concurrency: number | boolean; only: boolean; timeout: number; cwd: string; testUpdateSnapshots: boolean; /** Converted from run()'s testNamePatterns/testSkipPatterns (runner.ts), or null. A * later CLI parser (--test-name-pattern/--test-skip-pattern) would feed these the * same way: null here means "not filtering by name", read by test.ts's * willBeFilteredByName(). Not fed by run()'s own option today — see runner.ts's * header comment on the confirmed Node quirk this mirrors. */ testNamePatterns: RegExp[] | null; testSkipPatterns: RegExp[] | null; } /** Node's internalBinding('util').getCallerLocation(): where the function that calls * this one was itself called from. Parses the `.stack` string rather than using * Error.prepareStackTrace/CallSite#getFileName() (src/runtime/util.ts's * getCallSites() technique): CallSite#getFileName() comes back `undefined` for a * frame inside the guest's indirect-eval'd CJS wrapper in this engine (confirmed by * probing it directly), even though the wrapper's `//# sourceURL=` comment (see * compileCjs in modules.ts) does make the guest's real path show up correctly in the * plain `.stack` string's own frame lines, which is what's parsed here instead. * Every caller invokes this directly from its own body — test()/describe()/before()/ * ... in harness.ts, and TestContext#test/before/after/beforeEach/afterEach in * test.ts — so after the message line and this function's own frame, the next frame * (index 2, 0-based) is the caller's own wrapper body, and the one after that (index * 3) is the user's actual call site. */ export function getCallerLocation(): [number, number, string] | undefined { const lines = (new Error().stack ?? "").split("\n"); const line = lines[3]; if (!line) return undefined; const m = /\(([^()]+):(\d+):(\d+)\)\s*$/.exec(line) ?? /^\s*at\s+(\S+):(\d+):(\d+)\s*$/.exec(line); if (!m) return undefined; return [Number(m[2]), Number(m[3]), m[1]!]; } /** CLI flags Node reads through getOptionValue (DESIGN: no CLI flag parser here, so * every run behaves as `node file.test.js` with no --test* flags at all). A later * flag parser feeds real values in by replacing this one function. */ export function commandLineDefaults(cwd: string): GlobalTestOptions { return { isTestRunner: false, concurrency: 1, only: false, timeout: Infinity, cwd, testUpdateSnapshots: false, testNamePatterns: null, testSkipPatterns: null, }; } /** Node's internal/test_runner/utils.js kPatterns/kFileExtensions/kDefaultPattern: the * glob run() falls back to when `files` and `globPatterns` are both omitted. No * --strip-types flag exists here (DESIGN: no CLI flag parser), so .ts/.mts/.cts never * join the extension list the way Node's does under that flag. */ export const kDefaultPattern = "**/{test,test/**/*,test-*,*[._-]test}.{js,mjs,cjs}"; /** Node's internal/test_runner/utils.js convertStringToRegExp: a bare `/pat/flags` * string is read as a literal regex (its own flags, not case-insensitive by default); * anything else is the pattern verbatim with no flags. */ export function convertStringToRegExp(str: string, name: string): RegExp { const m = /^\/(.*)\/([a-z]*)$/.exec(str); const pattern = m?.[1] ?? str; const flags = m?.[2] || ""; try { return new RegExp(pattern, flags); } catch (err) { const msg = err instanceof Error ? err.message : undefined; throw invalidArgValue(name, str, `is an invalid regular expression.${msg ? ` ${msg}` : ""}`); } }