vitest
Version:
Next generation testing framework powered by Vite
1,317 lines • 53.5 kB
JavaScript
import { fileURLToPath, pathToFileURL } from 'node:url';
import v8 from 'node:v8';
import vm, { isContext, runInContext } from 'node:vm';
import { l as loadEnvironment, a as listenForErrors, e as emitModuleRunner } from './init.PvxYpuCm.js';
import { distDir } from '../path.js';
import { createCustomConsole } from './console.DtI4yxfN.js';
import fs__default from 'node:fs';
import { createRequire, Module, isBuiltin } from 'node:module';
import { d as dirname, f as extname, b as basename, C as CSS_LANGS_RE, h as KNOWN_ASSET_RE, t as toArray, k as splitFileAndPostfix, n as normalize, l as isBareImport, r as resolve } from './pathe.M-eThtNZ.Ba-7h1rE.js';
import { l as lookupPackageScopeType } from './resolver.CtCcxIy1.js';
import { initSync, parse } from 'es-module-lexer';
import { dirname as dirname$1 } from 'node:path';
import { V as VITEST_VM_CONTEXT_SYMBOL, s as startVitestModuleRunner, c as createNodeImportMeta } from './index.CK-t0WMg.js';
import { getDefaultRequestStubs } from '../module-evaluator.js';
import { b as setupEnv } from './setup-common.HGAqWOnX.js';
import { p as provideWorkerState } from './utils.DYj33du9.js';
/**
* Worker-wide cache of V8 code cache buffers for externalized modules.
*
* vm pools create a fresh executor per test file, so every externalized
* module is compiled and evaluated again in each fresh context. The compiled
* code has no per-context state — reusing its V8 code cache skips the
* re-parse/re-compile while the evaluation still happens per context.
*
* Entries are keyed by the module identifier and guarded by the exact source
* text, so an invalidated module that produces different code simply replaces
* its entry.
*/
class CodeCache {
entries = /* @__PURE__ */ new Map();
get(identifier, source) {
const entry = this.entries.get(identifier);
if (entry && entry.source === source) return entry.data;
}
/**
* Stores the code cache produced by `produce` unless an entry for the same
* source already exists. A `produce` failure is recorded as an empty entry,
* so it is not retried on every fresh context.
*/
store(identifier, source, produce) {
const entry = this.entries.get(identifier);
if (entry && entry.source === source) return;
let data;
try {
data = produce();
} catch {
data = void 0;
}
this.entries.set(identifier, {
source,
data
});
}
delete(identifier) {
this.entries.delete(identifier);
}
clear() {
this.entries.clear();
}
}
/**
* `node:v8` as seen inside the vm context: changing V8 flags invalidates every
* code cache produced so far, so `setFlagsFromString` also empties ours.
*/
function createV8ModuleWithCacheReset(v8, codeCache) {
const patched = Object.create(Object.getPrototypeOf(v8), Object.getOwnPropertyDescriptors(v8));
patched.setFlagsFromString = function setFlagsFromString(flags) {
v8.setFlagsFromString(flags);
codeCache.clear();
};
return patched;
}
function interopCommonJsModule(interopDefault, mod) {
if (isPrimitive(mod) || Array.isArray(mod) || mod instanceof Promise) return {
keys: [],
moduleExports: {},
defaultExport: mod
};
if (interopDefault !== false && "__esModule" in mod && !isPrimitive(mod.default)) {
const defaultKets = Object.keys(mod.default);
const moduleKeys = Object.keys(mod);
const allKeys = /* @__PURE__ */ new Set([...defaultKets, ...moduleKeys]);
allKeys.delete("default");
// the namespace always provides its own synthetic 'module.exports'
// export, shadowing a real property of that name (Node parity)
allKeys.delete("module.exports");
return {
keys: Array.from(allKeys),
moduleExports: new Proxy(mod, { get(mod, prop) {
return mod[prop] ?? mod.default?.[prop];
} }),
defaultExport: mod
};
}
return {
keys: Object.keys(mod).filter((key) => key !== "default" && key !== "module.exports"),
moduleExports: mod,
defaultExport: mod
};
}
function isPrimitive(obj) {
return !(obj != null && (typeof obj === "object" || typeof obj === "function"));
}
const SyntheticModule = vm.SyntheticModule;
const SourceTextModule = vm.SourceTextModule;
// `SourceTextModule#hasAsyncGraph` marks the Node 24.9+ vm APIs required to
// load an ES module graph synchronously (`moduleRequests`, `linkRequests`,
// `instantiate`, synchronously-completing `evaluate`) — the same APIs Node
// itself uses for require(esm)
const supportsSyncEsmEvaluate = typeof SourceTextModule?.prototype.hasAsyncGraph === "function";
let lexerInitialized = false;
// Returns true when `source` contains ESM syntax: static import/export
// statements or `import.meta` (dynamic import is allowed in CJS and does not
// count). Returns false when the lexer cannot parse the source at all —
// native ESM would fail on it as well, so the CJS error should surface.
function hasEsmSyntax(source) {
if (!lexerInitialized) {
initSync();
lexerInitialized = true;
}
try {
return parse(source)[3];
} catch {
return false;
}
}
// mirrors Node's require(esm) error codes so user-side catches work uniformly
function createRequireAsyncModuleError(identifier, detail) {
const error = /* @__PURE__ */ new Error(`require() cannot be used to load ES Module ${identifier}: ${detail}. Use import() instead.`);
error.code = "ERR_REQUIRE_ASYNC_MODULE";
return error;
}
function createConcurrentRequireError(identifier) {
const error = /* @__PURE__ */ new Error(`Cannot require() ES Module ${identifier} synchronously: it is currently being loaded by a concurrent import(). Await that import before calling require(), or import this module instead of requiring it.`);
error.code = "ERR_REQUIRE_ESM";
return error;
}
let activeVmExecutor;
function setActiveVmExecutor(executor) {
activeVmExecutor = executor;
}
async function activeImportModuleDynamically(specifier, referencer) {
if (!activeVmExecutor) throw new Error(`Cannot import "${specifier}": the test context was torn down.`);
return activeVmExecutor.importModuleDynamically(specifier, referencer);
}
// Node never collects a vm context in which multiple scripts installed
// closures, and `vm.SourceTextModule`s are pinned by the realm's base object
// list: the ContextifyContext/ModuleWrap wrappers keep the whole context
// reachable even through forced full GCs, so a long-lived vm worker
// accumulates every test file's world until it hits `vmMemoryLimit` and gets
// recycled, destroying the worker's compile caches with it. Clearing what the
// test file added to the global object (and the DOM) caps what a pinned
// context retains. Pristine globals are kept so that work queued before the
// teardown (jsdom events, worker-scoped fixture cleanups) can still run.
const captureKeysScript = new vm.Script(`Object.getOwnPropertyNames(globalThis).concat(Object.getOwnPropertySymbols(globalThis))`, { filename: "virtual:vitest-capture-context-keys.js" });
function captureContextKeys(context) {
try {
return new Set(captureKeysScript.runInContext(context));
} catch {
return /* @__PURE__ */ new Set();
}
}
const stripScript = new vm.Script(`(initialKeys) => {
const g = globalThis
try { g.document.body.textContent = '' } catch {}
try { g.document.head.textContent = '' } catch {}
let keys = []
try { keys = Object.getOwnPropertyNames(g).concat(Object.getOwnPropertySymbols(g)) } catch {}
for (const key of keys) {
if (initialKeys.has(key)) continue
try { delete g[key] } catch {}
}
}`, { filename: "virtual:vitest-strip-context.js" });
function stripDisposedContext(context, initialKeys) {
try {
stripScript.runInContext(context)(initialKeys);
} catch {}
}
const _require = createRequire(import.meta.url);
// Thrown when the CJS parser rejects a .js file that may contain ESM syntax.
// `loadCommonJSModule` catches it and retries the file as an ES module,
// mirroring Node's own require() ESM-syntax fallback for .js files without
// an ESM package scope. The original error is in `cause`.
class CjsParseError extends SyntaxError {
name = "CjsParseError";
constructor(cause) {
super(cause.message, { cause });
}
}
const requiresCache = /* @__PURE__ */ new WeakMap();
// Compiled scripts of commonjs modules, shared across vm contexts: only the
// evaluation has to happen per context. No invalidation is needed because
// watch mode reruns destroy the worker.
const cjsScriptCache = /* @__PURE__ */ new Map();
class CommonjsExecutor {
context;
requireCache = /* @__PURE__ */ new Map();
publicRequireCache = this.createProxyCache();
moduleCache = /* @__PURE__ */ new Map();
builtinCache = Object.create(null);
extensions = Object.create(null);
fs;
codeCache;
Module;
interopDefault;
shouldRequireAsEsm;
requireEsm;
// .js files that the ESM-syntax fallback already loaded as ES modules,
// so later require() calls skip the guaranteed-to-fail CJS parse
esmSyntaxFallbackFiles = /* @__PURE__ */ new Set();
constructor(options) {
this.context = options.context;
this.fs = options.fileMap;
this.codeCache = options.codeCache;
this.interopDefault = options.interopDefault;
this.shouldRequireAsEsm = options.shouldRequireAsEsm;
this.requireEsm = options.requireEsm;
const primitives = vm.runInContext("({ Object, Array, Error })", this.context);
// oxlint-disable-next-line typescript/no-this-alias
const executor = this;
this.Module = class Module$1 {
exports;
isPreloading = false;
id;
filename;
loaded;
parent;
children = [];
path;
paths = [];
constructor(id = "", parent) {
this.exports = primitives.Object.create(primitives.Object.prototype);
// in our case the path should always be resolved already
this.path = dirname(id);
this.id = id;
this.filename = id;
this.loaded = false;
this.parent = parent;
}
get require() {
const require = requiresCache.get(this);
if (require) return require;
const _require = Module$1.createRequire(this.id);
requiresCache.set(this, _require);
return _require;
}
static getSourceMapsSupport = () => ({
enabled: false,
nodeModules: false,
generatedCode: false
});
static setSourceMapsSupport = () => {
// noop
};
static register = () => {
throw new Error(`[vitest] "register" is not available when running in Vitest.`);
};
static registerHooks = () => {
throw new Error(`[vitest] "registerHooks" is not available when running in Vitest.`);
};
_compile(code, filename) {
const cjsModule = Module$1.wrap(code);
const codeCache = executor.codeCache;
let script = cjsScriptCache.get(filename);
if (!script) {
const cachedData = codeCache?.get(filename, cjsModule);
// the dynamic import callback is a static function (the executor is
// resolved when it is called), so the compiled script holds no
// per-context state and can be reused by every vm context
try {
script = new vm.Script(cjsModule, {
filename,
cachedData,
importModuleDynamically: activeImportModuleDynamically
});
} catch (error) {
if (error instanceof SyntaxError && executor.canFallbackToEsm(filename)) throw new CjsParseError(error);
throw error;
}
if (cachedData && script.cachedDataRejected) codeCache.delete(filename);
// @ts-expect-error mark script with current identifier
script.identifier = filename;
cjsScriptCache.set(filename, script);
}
const fn = script.runInContext(executor.context);
const __dirname = dirname(filename);
executor.requireCache.set(filename, this);
try {
fn.call(this.exports, this.exports, this.require, this, filename, __dirname);
return this.exports;
} finally {
this.loaded = true;
// store after execution so the code cache carries the compiled
// module body, not only the lazily-parsed wrapper
codeCache?.store(filename, cjsModule, () => script.createCachedData());
}
}
// exposed for external use, Node.js does the opposite
static _load = (request, parent, _isMain) => {
return Module$1.createRequire(parent?.filename ?? request)(request);
};
static wrap = (script) => {
return Module$1.wrapper[0] + script + Module$1.wrapper[1];
};
static wrapper = new primitives.Array("(function (exports, require, module, __filename, __dirname) { ", "\n});");
static builtinModules = Module.builtinModules;
static findSourceMap = Module.findSourceMap;
static SourceMap = Module.SourceMap;
static syncBuiltinESMExports = Module.syncBuiltinESMExports;
static _cache = executor.publicRequireCache;
static _extensions = executor.extensions;
static createRequire = (filename) => {
return executor.createRequire(filename);
};
static runMain = () => {
throw new primitives.Error("[vitest] \"runMain\" is not implemented.");
};
// @ts-expect-error not typed
static _resolveFilename = Module._resolveFilename;
// @ts-expect-error not typed
static _findPath = Module._findPath;
// @ts-expect-error not typed
static _initPaths = Module._initPaths;
// @ts-expect-error not typed
static _preloadModules = Module._preloadModules;
// @ts-expect-error not typed
static _resolveLookupPaths = Module._resolveLookupPaths;
// @ts-expect-error not typed
static globalPaths = Module.globalPaths;
static isBuiltin = Module.isBuiltin;
static constants = Module.constants;
static enableCompileCache = Module.enableCompileCache;
static getCompileCacheDir = Module.getCompileCacheDir;
static flushCompileCache = Module.flushCompileCache;
static stripTypeScriptTypes = Module.stripTypeScriptTypes;
static findPackageJSON = Module.findPackageJSON;
static Module = Module$1;
};
this.extensions[".js"] = this.requireJs;
this.extensions[".json"] = this.requireJson;
}
requireJs = (m, filename) => {
const content = this.fs.readFile(filename);
m._compile(content, filename);
};
requireJson = (m, filename) => {
const code = this.fs.readFile(filename);
m.exports = JSON.parse(code);
};
static cjsConditions;
static getCjsConditions() {
if (!CommonjsExecutor.cjsConditions) CommonjsExecutor.cjsConditions = parseCjsConditions(process.execArgv, process.env.NODE_OPTIONS, supportsSyncEsmEvaluate);
return CommonjsExecutor.cjsConditions;
}
createRequire = (filename) => {
const _require = createRequire(filename);
const resolve = (id, options) => {
return _require.resolve(id, {
...options,
// Works on Node 22.12+ where _resolveFilename supports conditions.
// Silently ignored on older Node versions.
conditions: CommonjsExecutor.getCjsConditions()
});
};
const require = ((id) => {
const resolved = resolve(id);
if (extname(resolved) === ".node" || isBuiltin(resolved)) return this.requireCoreModule(resolved);
if (this.shouldRequireAsEsm(resolved)) return this.requireEsm(resolved);
const module = new this.Module(resolved);
return this.loadCommonJSModule(module, resolved);
});
require.resolve = resolve;
require.resolve.paths = _require.resolve.paths;
Object.defineProperty(require, "extensions", {
get: () => this.extensions,
set: () => {},
configurable: true
});
require.main = void 0;
require.cache = this.publicRequireCache;
return require;
};
createProxyCache() {
return new Proxy(Object.create(null), {
defineProperty: () => true,
deleteProperty: () => true,
set: () => true,
get: (_, key) => this.requireCache.get(key),
has: (_, key) => this.requireCache.has(key),
ownKeys: () => Array.from(this.requireCache.keys()),
getOwnPropertyDescriptor() {
return {
configurable: true,
enumerable: true
};
}
});
}
// very naive implementation for Node.js require
loadCommonJSModule(module, filename) {
const cached = this.requireCache.get(filename);
if (cached) return cached.exports;
if (this.esmSyntaxFallbackFiles.has(filename)) return this.requireEsm(filename);
const extension = this.findLongestRegisteredExtension(filename);
const loader = this.extensions[extension] || this.extensions[".js"];
try {
loader(module, filename);
} catch (error) {
if (error instanceof CjsParseError) return this.fallbackRequireEsm(filename, error);
throw error;
}
return module.exports;
}
canFallbackToEsm(filename) {
return supportsSyncEsmEvaluate && extname(filename) === ".js";
}
fallbackRequireEsm(filename, parseError) {
let exports;
try {
exports = this.requireEsm(filename);
} catch (esmError) {
// both parsers rejected the file — surface the original CJS error
if (esmError instanceof SyntaxError) throw parseError.cause;
throw esmError;
}
this.esmSyntaxFallbackFiles.add(filename);
return exports;
}
findLongestRegisteredExtension(filename) {
const name = basename(filename);
let currentExtension;
let index;
let startIndex = 0;
// oxlint-disable-next-line no-cond-assign
while ((index = name.indexOf(".", startIndex)) !== -1) {
startIndex = index + 1;
if (index === 0) continue;
currentExtension = name.slice(index);
if (this.extensions[currentExtension]) return currentExtension;
}
return ".js";
}
getCoreSyntheticModule(identifier) {
if (this.moduleCache.has(identifier)) return this.moduleCache.get(identifier);
const exports = this.require(identifier);
const keys = Object.keys(exports);
const module = new SyntheticModule([...keys, "default"], () => {
for (const key of keys) module.setExport(key, exports[key]);
module.setExport("default", exports);
}, {
context: this.context,
identifier
});
this.moduleCache.set(identifier, module);
return module;
}
getCjsSyntheticModule(path, identifier) {
if (this.moduleCache.has(identifier)) return this.moduleCache.get(identifier);
const exports = this.require(path);
// TODO: technically module should be parsed to find static exports, implement for strict mode in #2854
const { keys, moduleExports, defaultExport } = interopCommonJsModule(this.interopDefault, exports);
const module = new SyntheticModule([
...keys,
"default",
"module.exports"
], function() {
for (const key of keys) this.setExport(key, moduleExports[key]);
this.setExport("default", defaultExport);
// the raw module.exports value, mirroring the handling of the
// 'module.exports' export name in require(esm) interop:
// https://nodejs.org/api/esm.html#commonjs-namespaces
this.setExport("module.exports", exports);
}, {
context: this.context,
identifier
});
this.moduleCache.set(identifier, module);
return module;
}
// TODO: use this in strict mode, when available in #2854
// private _getNamedCjsExports(path: string): Set<string> {
// const cachedNamedExports = this.cjsNamedExportsMap.get(path)
// if (cachedNamedExports) {
// return cachedNamedExports
// }
// if (extname(path) === '.node') {
// const moduleExports = this.require(path)
// const namedExports = new Set(Object.keys(moduleExports))
// this.cjsNamedExportsMap.set(path, namedExports)
// return namedExports
// }
// const code = this.fs.readFile(path)
// const { exports, reexports } = parseCjs(code, path)
// const namedExports = new Set(exports)
// this.cjsNamedExportsMap.set(path, namedExports)
// for (const reexport of reexports) {
// if (isNodeBuiltin(reexport)) {
// const exports = this.require(reexport)
// if (exports !== null && typeof exports === 'object') {
// for (const e of Object.keys(exports)) {
// namedExports.add(e)
// }
// }
// }
// else {
// const require = this.createRequire(path)
// const resolved = require.resolve(reexport)
// const exports = this._getNamedCjsExports(resolved)
// for (const e of exports) {
// namedExports.add(e)
// }
// }
// }
// return namedExports
// }
require(identifier) {
if (extname(identifier) === ".node" || isBuiltin(identifier)) return this.requireCoreModule(identifier);
if (this.shouldRequireAsEsm(identifier)) return this.requireEsm(identifier);
const module = new this.Module(identifier);
return this.loadCommonJSModule(module, identifier);
}
requireCoreModule(identifier) {
const normalized = identifier.replace(/^node:/, "");
if (this.builtinCache[normalized]) return this.builtinCache[normalized].exports;
const moduleExports = _require(identifier);
if (identifier === "node:module" || identifier === "module") {
const module = new this.Module("/module.js");
module.exports = this.Module;
this.builtinCache[normalized] = module;
return module.exports;
}
if (normalized === "v8" && this.codeCache) {
const module = new this.Module("/v8.js");
module.exports = createV8ModuleWithCacheReset(moduleExports, this.codeCache);
this.builtinCache[normalized] = module;
return module.exports;
}
this.builtinCache[normalized] = _require.cache[normalized];
// TODO: should we wrap module to rethrow context errors?
return moduleExports;
}
}
// The "module-sync" exports condition (added in Node 22.12/20.19 when
// require(esm) was unflagged) can resolve to ESM files. When require(esm)
// is supported (Node 24.9+), the condition is included, matching Node.
// Otherwise our CJS vm.Script executor cannot handle the resolved ESM
// files, so the condition is excluded by passing explicit CJS conditions
// to require.resolve (Node 22.12+).
// Must be a Set because Node's internal resolver calls conditions.has().
function parseCjsConditions(execArgv, nodeOptions, requireEsmSupported = false) {
const conditions = [
"node",
"require",
"node-addons"
];
if (requireEsmSupported) conditions.push("module-sync");
const args = [...execArgv, ...nodeOptions?.split(/\s+/) ?? []];
for (let i = 0; i < args.length; i++) {
const arg = args[i];
const eqMatch = arg.match(/^(?:--conditions|-C)=(.+)$/);
if (eqMatch) conditions.push(eqMatch[1]);
else if ((arg === "--conditions" || arg === "-C") && i + 1 < args.length) conditions.push(args[++i]);
}
if (!requireEsmSupported) return new Set(conditions.filter((c) => c !== "module-sync"));
return new Set(conditions);
}
// `hasAsyncGraph` only exists on SourceTextModule — a SyntheticModule is
// synchronous by definition (its evaluation callback is sync)
function moduleHasAsyncGraph(module) {
return module instanceof SourceTextModule && module.hasAsyncGraph();
}
const dataURIRegex = /^data:(?<mime>text\/javascript|application\/json|application\/wasm)(?:;(?<encoding>charset=utf-8|base64))?,(?<code>.*)$/;
function parseDataUri(identifier) {
const match = identifier.match(dataURIRegex);
if (!match || !match.groups) throw new Error("Invalid data URI");
const { mime, encoding } = match.groups;
let code = match.groups.code;
if (mime === "application/wasm") {
if (!encoding) throw new Error("Missing data URI encoding");
if (encoding !== "base64") throw new Error(`Invalid data URI encoding: ${encoding}`);
return {
mime,
code: Buffer.from(code, "base64")
};
}
if (!encoding || encoding === "charset=utf-8") code = decodeURIComponent(code);
else if (encoding === "base64") code = Buffer.from(code, "base64").toString();
else throw new Error(`Invalid data URI encoding: ${encoding}`);
return {
mime,
code
};
}
function getContextExecutor(mod) {
const vmContext = mod.context?.[VITEST_VM_CONTEXT_SYMBOL];
if (!vmContext) throw new Error(`Cannot import "${mod.identifier}": its vm context was torn down.`);
return vmContext.externalModulesExecutor;
}
async function staticImportModuleDynamically(specifier, referencer) {
return getContextExecutor(referencer).importModuleDynamically(specifier, referencer);
}
function staticInitializeImportMeta(meta, mod) {
meta.url = mod.identifier;
if (mod.identifier.startsWith("file:")) {
const filename = fileURLToPath(mod.identifier);
meta.filename = filename;
meta.dirname = dirname$1(filename);
}
meta.resolve = (specifier, importer) => {
return getContextExecutor(mod).resolve(specifier, importer != null ? importer.toString() : mod.identifier);
};
}
class EsmExecutor {
executor;
moduleCache = /* @__PURE__ */ new Map();
esmLinkMap = /* @__PURE__ */ new WeakMap();
linkQueue = Promise.resolve();
context;
#httpIp = IPnumber("127.0.0.0");
constructor(executor, options) {
this.executor = executor;
this.context = options.context;
}
async evaluateModule(m) {
// a module that failed to evaluate keeps living in the cache — rethrow
// its error instead of silently returning an unusable namespace
if (m.status === "errored") throw m.error;
await this.linkModule(m);
if (m.status === "linked") await m.evaluate();
return m;
}
// Roots are linked one at a time: Node's link() does not wait for a
// dependency that another root is still linking, and instantiate() then
// fails on it. Sharing the queue with all roots keeps cycle handling to
// Node's own single-root linker, which never has to wait.
linkModule(m) {
const pending = this.esmLinkMap.get(m);
if (pending) return pending;
if (m.status !== "unlinked" && m.status !== "linking") return Promise.resolve();
const linking = this.linkQueue.then(() => {
if (m.status === "unlinked") return m.link(this.linker);
});
this.esmLinkMap.set(m, linking);
this.linkQueue = linking.catch(() => {});
return linking;
}
linker = (identifier, referencer) => this.executor.resolveModule(identifier, referencer.identifier);
async createEsModule(fileURL, getCode) {
const cached = this.moduleCache.get(fileURL);
if (cached) return cached;
const promise = this.loadEsModule(fileURL, getCode);
this.moduleCache.set(fileURL, promise);
return promise;
}
loadEsModule(fileURL, getCode) {
const code = getCode();
if (code instanceof Promise) return code.then((content) => this.createModule(fileURL, content));
return this.createModule(fileURL, code);
}
createModule(fileURL, code) {
const module = fileURL.endsWith(".json") ? this.createJsonModule(fileURL, code) : this.createSourceTextModule(fileURL, code);
this.moduleCache.set(fileURL, module);
return module;
}
createSourceTextModule(fileURL, code) {
const codeCache = this.executor.codeCache;
let cachedData = codeCache?.get(fileURL, code);
const options = {
identifier: fileURL,
context: this.context,
// static callbacks: Node keeps them registered for as long as the
// module's host-defined-options symbol is alive, so a closure here would
// retain this executor (and the whole test file's world) beyond the
// file's lifetime. The executor is recovered from the module's context
// at call time instead.
importModuleDynamically: staticImportModuleDynamically,
initializeImportMeta: staticInitializeImportMeta
};
let m;
if (cachedData) try {
m = new SourceTextModule(code, {
...options,
cachedData
});
} catch (error) {
// unlike vm.Script, a module throws when V8 rejects the cache (e.g. the
// V8 flags changed at runtime): compile from source instead
if (error?.code !== "ERR_VM_MODULE_CACHED_DATA_REJECTED") throw error;
codeCache.delete(fileURL);
cachedData = void 0;
}
m ??= new SourceTextModule(code, options);
// the code cache of a SourceTextModule must be created before evaluation
if (!cachedData) {
const created = m;
codeCache?.store(fileURL, code, () => created.createCachedData());
}
return m;
}
// Loads an ES module graph synchronously for require(esm), mirroring Node's
// own behaviour on Node 24.9+. The graph is collected into a local scratch
// map first and committed to the module cache only after the whole graph is
// proven to be synchronously evaluable, so a failed require() does not
// poison the cache for a later import() of the same file.
requireEsModuleSync(rootIdentifier) {
const cachedRoot = this.moduleCache.get(rootIdentifier);
if (cachedRoot) return this.reuseSyncModule(rootIdentifier, cachedRoot);
const scratch = /* @__PURE__ */ new Map();
const worklist = [rootIdentifier];
while (worklist.length > 0) {
const identifier = worklist.pop();
if (scratch.has(identifier)) continue;
const cached = this.moduleCache.get(identifier);
if (cached) {
scratch.set(identifier, {
module: this.reuseSyncModule(identifier, cached),
commit: false
});
continue;
}
const disposition = identifier.startsWith("data:") ? this.materializeSyncDataModule(identifier) : this.executor.materializeSyncModule(identifier, identifier === rootIdentifier);
if (disposition.kind === "ready") {
scratch.set(identifier, {
module: disposition.module,
commit: false
});
continue;
}
if (disposition.kind === "json") {
scratch.set(identifier, {
module: this.createJsonModule(identifier, disposition.code),
commit: true
});
continue;
}
const module = this.createSourceTextModule(identifier, disposition.code);
if (module.hasTopLevelAwait()) throw createRequireAsyncModuleError(identifier, "the module uses top-level await");
const deps = [];
for (const request of module.moduleRequests) {
const depIdentifier = this.executor.resolveSyncSpecifier(request.specifier, identifier);
deps.push(depIdentifier);
if (!scratch.has(depIdentifier)) worklist.push(depIdentifier);
}
scratch.set(identifier, {
module,
deps,
commit: true
});
}
for (const entry of scratch.values()) if (entry.deps) entry.module.linkRequests(entry.deps.map((dep) => scratch.get(dep).module));
const root = scratch.get(rootIdentifier);
if (root.deps) root.module.instantiate();
if (moduleHasAsyncGraph(root.module)) {
// top-level await is rejected per module during the walk, so this is a
// defensive check that an async graph never reaches the sync evaluate
let culprit = rootIdentifier;
for (const [identifier, entry] of scratch) if (entry.deps && entry.module.hasTopLevelAwait()) {
culprit = identifier;
break;
}
throw createRequireAsyncModuleError(rootIdentifier, culprit === rootIdentifier ? "the module uses top-level await" : `its dependency uses top-level await (${culprit})`);
}
for (const [identifier, entry] of scratch) if (entry.commit && !this.moduleCache.has(identifier)) this.moduleCache.set(identifier, entry.module);
// with no top-level await in the graph, evaluate() fulfills synchronously
// and an evaluation error lands on `status`/`error`, not on the promise
root.module.evaluate().catch(() => {});
if (root.module.status === "errored") throw root.module.error;
if (root.module.status !== "evaluated") throw new Error(`[vitest] Expected synchronous evaluation to complete for ${rootIdentifier}, but module status is "${root.module.status}". This is a bug in Vitest.`);
return root.module;
}
// A cached module is reusable by the sync walker only when it is settled:
// anything else (a pending Promise or a module in 'unlinked' → 'evaluating')
// is a concurrent import() mid-flight that a synchronous require() can
// neither await nor safely link against.
reuseSyncModule(identifier, cached) {
if (cached instanceof Promise) throw createConcurrentRequireError(identifier);
if (cached.status === "errored") throw cached.error;
if (cached.status !== "evaluated") throw createConcurrentRequireError(identifier);
// a module with top-level await reports 'evaluated' as soon as evaluate()
// is called, while its async evaluation may still be pending — and even a
// settled async graph is never allowed in require() (Node parity)
if (moduleHasAsyncGraph(cached)) throw createRequireAsyncModuleError(identifier, "the module uses top-level await");
return cached;
}
materializeSyncDataModule(identifier) {
const { mime, code } = parseDataUri(identifier);
if (mime === "application/wasm") throw createRequireAsyncModuleError(identifier, "WebAssembly modules cannot be loaded synchronously");
if (mime === "application/json") return {
kind: "json",
code
};
return {
kind: "source",
code
};
}
createJsonModule(identifier, code) {
return new SyntheticModule(["default"], function() {
this.setExport("default", JSON.parse(code));
}, {
context: this.context,
identifier
});
}
async createWebAssemblyModule(fileUrl, getCode) {
const cached = this.moduleCache.get(fileUrl);
if (cached) return cached;
const m = this.loadWebAssemblyModule(getCode(), fileUrl);
this.moduleCache.set(fileUrl, m);
return m;
}
async createNetworkModule(fileUrl) {
// https://nodejs.org/api/esm.html#https-and-http-imports
if (fileUrl.startsWith("http:")) {
const url = new URL(fileUrl);
if (url.hostname !== "localhost" && url.hostname !== "::1" && (IPnumber(url.hostname) & IPmask(8)) !== this.#httpIp) throw new Error(
// we don't know the importer, so it's undefined (the same happens in --pool=threads)
`import of '${fileUrl}' by undefined is not supported: http can only be used to load local resources (use https instead).`
);
}
return this.createEsModule(fileUrl, () => fetch(fileUrl).then((r) => r.text()));
}
async loadWebAssemblyModule(source, identifier) {
const cached = this.moduleCache.get(identifier);
if (cached) return cached;
const wasmModule = await WebAssembly.compile(source);
const exports = WebAssembly.Module.exports(wasmModule);
const imports = WebAssembly.Module.imports(wasmModule);
const moduleLookup = {};
for (const { module } of imports) if (moduleLookup[module] === void 0) moduleLookup[module] = await this.executor.resolveModule(module, identifier);
const evaluateModule = (module) => this.evaluateModule(module);
return new SyntheticModule(exports.map(({ name }) => name), async function() {
const importsObject = {};
for (const { module, name } of imports) {
if (!importsObject[module]) importsObject[module] = {};
await evaluateModule(moduleLookup[module]);
importsObject[module][name] = moduleLookup[module].namespace[name];
}
const wasmInstance = new WebAssembly.Instance(wasmModule, importsObject);
for (const { name } of exports) this.setExport(name, wasmInstance.exports[name]);
}, {
context: this.context,
identifier
});
}
cacheModule(identifier, module) {
this.moduleCache.set(identifier, module);
}
resolveCachedModule(identifier) {
return this.moduleCache.get(identifier);
}
async createDataModule(identifier) {
const cached = this.moduleCache.get(identifier);
if (cached) return cached;
const { mime, code } = parseDataUri(identifier);
if (mime === "application/wasm") {
const module = this.loadWebAssemblyModule(code, identifier);
this.moduleCache.set(identifier, module);
return module;
}
if (mime === "application/json") {
const module = this.createJsonModule(identifier, code);
this.moduleCache.set(identifier, module);
return module;
}
return this.createEsModule(identifier, () => code);
}
}
function IPnumber(address) {
const ip = address.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/);
if (ip) return (+ip[1] << 24) + (+ip[2] << 16) + (+ip[3] << 8) + +ip[4];
throw new Error(`Expected IP address, received ${address}`);
}
function IPmask(maskSize) {
return -1 << 32 - maskSize;
}
const CLIENT_ID = "/@vite/client";
const CLIENT_FILE = pathToFileURL(CLIENT_ID).href;
class ViteExecutor {
options;
esm;
constructor(options) {
this.options = options;
this.esm = options.esmExecutor;
}
resolve = (identifier) => {
if (identifier === CLIENT_ID) return identifier;
};
get workerState() {
return this.options.context.__vitest_worker__;
}
async createViteModule(fileUrl) {
if (fileUrl === CLIENT_FILE || fileUrl === CLIENT_ID) return this.createViteClientModule();
const cached = this.esm.resolveCachedModule(fileUrl);
if (cached) return cached;
return this.esm.createEsModule(fileUrl, async () => {
try {
const result = await this.options.transform(fileUrl);
if (result.code) return result.code;
} catch (cause) {
// rethrow vite error if it cannot load the module because it's not resolved
if (typeof cause === "object" && cause.code === "ERR_LOAD_URL" || typeof cause?.message === "string" && cause.message.includes("Failed to load url")) {
const error = new Error(`Cannot find module '${fileUrl}'`, { cause });
error.code = "ERR_MODULE_NOT_FOUND";
throw error;
}
}
throw new Error(`[vitest] Failed to transform ${fileUrl}. Does the file exist?`);
});
}
createViteClientModule() {
const identifier = CLIENT_ID;
const cached = this.esm.resolveCachedModule(identifier);
if (cached) return cached;
const stub = this.options.viteClientModule;
const moduleKeys = Object.keys(stub);
const module = new SyntheticModule(moduleKeys, function() {
moduleKeys.forEach((key) => {
this.setExport(key, stub[key]);
});
}, {
context: this.options.context,
identifier
});
this.esm.cacheModule(identifier, module);
return module;
}
canResolve = (fileUrl) => {
if (fileUrl === CLIENT_FILE) return true;
const config = this.workerState.config.deps?.web || {};
const [modulePath] = fileUrl.split("?");
if (config.transformCss && CSS_LANGS_RE.test(modulePath)) return true;
if (config.transformAssets && KNOWN_ASSET_RE.test(modulePath)) return true;
if (toArray(config.transformGlobPattern).some((pattern) => pattern.test(modulePath))) return true;
return false;
};
}
const { existsSync } = fs__default;
// always defined when we use vm pool
const nativeResolve = import.meta.resolve;
// a relative ESM specifier resolves by plain URL join — Node's resolver adds
// no information for these (it does not check existence and relative
// specifiers never consult package.json), but it re-derives the package
// scope on every uncached call, re-parsing large `exports` maps. Restricted
// to a conservative charset so anything URL-special falls back to Node.
const SIMPLE_RELATIVE_SPECIFIER_RE = /^\.{1,2}\/[\w\-./]+$/;
// TODO: improve Node.js strict mode support in #2854
class ExternalModulesExecutor {
options;
cjs;
esm;
vite;
context;
fs;
codeCache;
resolvers = [];
#networkSupported = null;
constructor(options) {
this.options = options;
this.context = options.context;
this.fs = options.fileMap;
this.codeCache = options.codeCache;
this.esm = new EsmExecutor(this, { context: this.context });
setActiveVmExecutor(this);
this.cjs = new CommonjsExecutor({
context: this.context,
fileMap: options.fileMap,
codeCache: options.codeCache,
interopDefault: options.interopDefault,
shouldRequireAsEsm: this.shouldRequireAsEsm,
requireEsm: this.requireEsm
});
this.vite = new ViteExecutor({
esmExecutor: this.esm,
context: this.context,
transform: options.transform,
viteClientModule: options.viteClientModule
});
this.resolvers = [this.vite.resolve];
}
async import(identifier) {
const module = await this.createModule(identifier);
await this.esm.evaluateModule(module);
return module.namespace;
}
require(identifier) {
return this.cjs.require(identifier);
}
createRequire(identifier) {
return this.cjs.createRequire(identifier);
}
#esmSyntaxCache = /* @__PURE__ */ new Map();
// require() dispatches to the sync ESM loader only for files that are
// explicitly marked as ESM (.mjs or a "type": "module" package scope).
// JSON files keep the CJS json loader for Node require() parity — the
// extension wins over the package scope.
shouldRequireAsEsm = (resolvedPath) => {
if (!supportsSyncEsmEvaluate) return false;
const information = this.getModuleInformation(resolvedPath);
if (information.type !== "module" || information.path.endsWith(".json")) return false;
if (information.path.endsWith(".mjs")) return true;
// A .js file in an ESM package scope may still contain plain CJS code —
// Node evaluates it as ESM with injected CJS module variables (module,
// require, __filename), which a vm SourceTextModule cannot emulate.
// Files without ESM syntax keep loading through the CJS executor; a
// false negative here is corrected by its ESM-syntax fallback.
let syntax = this.#esmSyntaxCache.get(information.path);
if (syntax == null) {
syntax = hasEsmSyntax(this.fs.readFile(information.path));
this.#esmSyntaxCache.set(information.path, syntax);
}
return syntax;
};
requireEsm = (resolvedPath) => {
const { url } = this.getModuleInformation(resolvedPath);
const namespace = this.esm.requireEsModuleSync(url).namespace;
// Node parity: an ES module can define its own require() result with an
// export named "module.exports"
return "module.exports" in namespace ? namespace["module.exports"] : namespace;
};
resolveSyncSpecifier = (specifier, referencer) => {
const resolved = this.resolve(specifier, referencer);
if (resolved instanceof Promise) throw createRequireAsyncModuleError(referencer, `"${specifier}" cannot be resolved synchronously`);
return resolved;
};
// the sync counterpart of `createModule`, used by the require(esm) graph
// walker. `forceEsmSource` loads a 'commonjs'-typed file as ES module
// source — the CJS executor requests this after its parser rejected a .js
// file that contains ESM syntax.
materializeSyncModule = (identifier, forceEsmSource) => {
const information = this.getModuleInformation(identifier);
const { type, path } = information;
this.assertModuleExists(information);
switch (type) {
case "builtin": return {
kind: "ready",
module: this.cjs.getCoreSyntheticModule(identifier)
};
case "module":
case "commonjs":
if (type === "commonjs" && !forceEsmSource) return {
kind: "ready",
module: this.cjs.getCjsSyntheticModule(path, identifier)
};
if (path.endsWith(".json")) return {
kind: "json",
code: this.fs.readFile(path)
};
return {
kind: "source",
code: this.fs.readFile(path)
};
case "data":
// data: URIs are materialized by the ESM executor before it consults
// the external executor
throw new Error(`[vitest] Unexpected data: module ${identifier} in the sync module walker. This is a bug in Vitest.`);
case "vite": throw createRequireAsyncModuleError(identifier, "the module is transformed by Vite, which is asynchronous");
case "wasm": throw createRequireAsyncModuleError(identifier, "WebAssembly modules cannot be loaded synchronously");
case "network": throw createRequireAsyncModuleError(identifier, "network modules cannot be loaded synchronously");
default: return type;
}
};
// dynamic import can be used in both ESM and CJS, so we have it in the executor
importModuleDynamically = async (specifier, referencer) => {
const module = await this.resolveModule(specifier, referencer.identifier);
return await this.esm.evaluateModule(module);
};
resolveModule = async (specifier, referencer) => {
let identifier = this.resolve(specifier, referencer);
if (identifier instanceof Promise) identifier = await identifier;
return await this.createModule(identifier);
};
resolve(specifier, parent) {
for (const resolver of this.resolvers) {
const id = resolver(specifier, parent);
if (id) return id;
}
if (SIMPLE_RELATIVE_SPECIFIER_RE.test(specifier) && parent.startsWith("file://")) return new URL(specifier, parent).href;
// resolution of externalized modules is stable for the lifetime of the
// worker (like fileMap/packageCache), while fresh vm contexts re-resolve
// every import edge
const cache = this.options.resolveCache;
const key = cache ? `${parent}\n${specifier}` : void 0;
if (cache) {
const cached = cache.get(key);
if (cached !== void 0) return cached;
}
// import.meta.resolve can be asynchronous in older +18 Node versions
const resolved = nativeResolve(specifier, parent);
if (cache && typeof resolved === "string") cache.set(key, resolved);
return resolved;
}
getModuleInformation(identifier) {
const cached = this.options.moduleInfoCache?.get(identifier);
if (cached) return cached;
const info = this.resolveModuleInformation(identifier);
this.options.moduleInfoCache?.set(identifier, info);
return info;
}
resolveModuleInformation(identifier) {
if (identifier.startsWith("data:")) return {
type: "data",
url: identifier,
path: identifier
};
const { file, postfix } = splitFileAndPostfix(identifier);
const extension = extname(file);
if (extension === ".node" || isBuiltin(identifier)) return {
type: "builtin",
url: identifier,
path: identifier
};
if (this.isNetworkSupported && (identifier.startsWith("http:") || identifier.startsWith("https:"))) return {
type: "network",
url: identifier,
path: identifier
};
const isFileUrl = identifier.startsWith("file://");
const pathUrl = isFileUrl ? fileURLToPath(file) : file;
const fileUrl = isFileUrl ? identifier : `${pathToFileURL(file)}${postfix}`;
let type;
if (this.vite.canResolve(fileUrl)) type = "vite";
else if (extension === ".mjs") type = "module";
else if (extension === ".cjs") type = "commonjs";
else if (extension === ".wasm")
// still experimental on NodeJS --experimental-wasm-modules
// cf. ESM_FILE_FORMAT(url) in https://nodejs.org/docs/latest-v20.x/api/esm.html#resolution-algorithm
type = "wasm";
else type = lookupPackageScopeType(normalize(pathUrl)) === "esm" ? "module" : "commonjs";
return {
type,
path: pathUrl,
url: fileUrl
};
}
// create ERR_MODULE_NOT_FOUND on our own since latest NodeJS's import.meta.resolve doesn't throw on non-existing namespace or path
// https://github.com/nodejs/node/pull/49038
assertModuleExists(information) {
const { type, path } = information;
if (type === "module" || type === "commonjs" || type === "wasm") information.exists ??= existsSync(path);
if (information.exists === false) {
const error = /* @__PURE__ */ new Error(`Cannot find ${isBareImport(path) ? "package" : "module"} '${path}'`);
error.code = "ERR_MODULE_NOT_FOUND";
throw error;
}
}
createModule(identifier) {
const information = this.getModuleInformation(identifier);
const { type, url, path } = information;
this.assertModuleExists(information);
switch (type) {
case "data": return this.esm.createDataModule(identifier);
case "builtin": return this.cjs.getCoreSyntheticModule(identifier);
case "vite": return this.vite.createViteModule(url);
case "wasm": return this.esm.createWebAssemblyModule(url, () => this.fs.readBuffer(path));
case "module": return this.esm.createEsModule(url, () => this.fs.readFile(path));
case "commonjs": return this.cjs.getCjsSyntheticModule(path, identifier);
case "network": return this.esm.createNetworkModule(url);
default: return type;
}
}
get isNetworkSupported() {
if (this.#networkSupported == null) {
if (process.execArgv.includes("--experimental-network-imports")) this.#networkSupported = true;
else if (process.env.NODE_OPTIONS?.includes("--experimental-network-imports")) this.#networkSupported = true;
else this.#networkSupported = false;
}
return this.#networkSupported;
}
}
const { promises, readFileSync } = fs__default;
class FileMap {
fsCache = /* @__PURE__ */ new Map();
fsBufferCache = /* @__PURE__ */ new Map();
async readFileAsync(path) {
const cached = this.fsCache.get(path);
if (cached != null) return cached;
const source = await promises.readFile(path, "utf-8");
this.fsCache.set(path, source);
return source;
}
readFile(path) {
const cached = this.fsCache.get(path);
if (cached != null) return cached;
const source = readFileSync(path, "utf-8");
this.fsCache.set(path, source);
return source;
}
readBuffer(path) {
const cached = this.fsBufferCache.get(path);
if (cached != null) return cached;
const buffer = readFileSync(path);
this.fsBufferCache.set(path, buffer);
return buffer;
}
}
const entryFile = pathToFileURL(resolve(distDir, "workers/runVmTests.js")).href;
const fileMap = new FileMap();
const packageCache = /* @__PURE__ */ new Map();
const codeCache = new CodeCache();
const resolveCache = /* @__PURE__ */ new Map();
const moduleInfoCache = /* @__PURE__ */ new Map();
async function runVmTests(method, state, traces) {
const { ctx, rpc } = state;
const beforeEnvironmentTime = performance.now();
const { environment, loader } = await loadEnvironment(ctx.environment.name, ctx.config.root, rpc, traces, true);
state.environment = environment;
loader?.evaluatedModules.idToModuleMap.forEach((_, id) => state.dependencies?.add(id));
// let the server transform this file's import graph while this worker is
// busy setting up the environment (jsdom takes ~0.5s per worker) —
// the server is otherwise idle during that window on a cold start.
if (environment.prewarmModules !== false) rpc.prewarmModuleGraph(environment.viteEnvironment || environment.name, ctx.files.map((file) => file.filepath)).catch(() => {});
if (!environment.setupVM) {
const envName = ctx.environment.name;
const packageId = envName[0] === "." ? envName : `vitest-environment-${envName}`;
throw new TypeError(`Environment "${ctx.environment.name}" is not a valid environment. Path "${packageId}" doesn't support vm environment because it doesn't provide "setupVM" method.`);
}
const vm = await traces.$("vitest.runtime.environment.setup", { attributes: {
"vitest.environment": environment.name,
"vitest.environment.vite_environment": environment.viteEnvironment || environment.name
} }, () => environment.setupVM(ctx.environment.options || ctx.config.environmentOptions || {}));
state.durations.environment = performance.now() - beforeEnvironmentTime;
process.env.VITEST_VM_POOL = "1";
if (!vm.getVmContext) throw new TypeError(`Environment ${environment.name} doesn't provide "getVmContext" method. It should return a context created by "vm.createContext" method.`);
const context = vm.getVmContext();
if (!isContext(context)) throw new TypeError(`Environment ${environment.name} doesn't provide a valid context. It should be created by "vm.createContext" method.`);
// captured before vitest installs its own globals (worker state, console,
// mocker, executor symbol): they reference the test file's module graph, so
// the teardown strip must treat them as removable, not as pristine
const initialContextKeys = captureContextKeys(context);
provideWorkerState(context, state);
// this is unfortunately needed for our own dependencies
// we need to find a way to not rely on this by default
// because browser doesn't provide these globals
context.process = process;
context.global = context;
context.console = state.config.disableConsoleIntercept ? console : createCustomConsole(state);
// TODO: don't hardcode setImmediate in fake timers defaults
context.setImmediate = setImmediate;
context.clearImmediate = clearImmediate;
const stubs = getDefaultRequestStubs(context);
const externalModulesExecutor = new ExternalModulesExecutor({
context,
fileMap,
codeCache,
resolveCache,
moduleInfoCache,
packageCache,
transform: rpc.transform,
viteClientModule: stubs["/@vite/client"]
});
process.exit = (code = process.exitCode || 0) => {
const filepath = state.filepath;
throw new Error(`process.exit unexpectedly called with "${code}"${filepath ? ` (test file: ${filepath})` : ""}`);
};
listenForErrors(() => state);
const moduleRunner = startVitestModuleRunner({
context,
evaluatedModules: state.evaluatedModules,
state,
externalModulesExecutor,
createImportMeta: createNodeImportMeta,
traces
});
emitModuleRunner(moduleRunner);
Object.defineProperty(context, VITEST_VM_CONTEXT_SYMBOL, {
value: {
context,
externalModulesExecutor
},
configurable: true,
enumerable: false,
writable: false
});
context.__vitest_mocker__ = moduleRunner.mocker;
setupEnv(ctx.config.env, state.metaEnv);
if (ctx.config.serializedDefines) try {
runInContext(ctx.config.serializedDefines, context, { filename: "virtual:load-defines.js" });
} catch (error) {
throw new Error(`Failed to load custom "defines": ${error.message}`);
}
await moduleRunner.mocker.initializeSpyModule();
const { run } = await moduleRunner.import(entryFile);
try {
await run(method, ctx.files, ctx.config, moduleRunner, traces);
} finally {
await traces.$("vitest.runtime.environment.teardown", () => vm.teardown?.());
// unregisters the runner from Vite's `Error.prepareStackTrace` interceptor:
// its module-level cache holds `evaluatedModules` of every runner it has
// seen, which would otherwise keep each test file's entire module graph
// (and with it the vm context) alive for the lifetime of the worker
await moduleRunner.close();
stripDisposedContext(context, initialContextKeys);
setActiveVmExecutor(void 0);
}
}
function setupVmWorker(context) {
if (context.config.experimental.viteModuleRunner === false) throw new Error(`Pool "${context.pool}" cannot run with "experimental.viteModuleRunner: false". Please, use "threads" or "forks" instead.`);
// V8's isolate-level compilation cache keeps evaluated `vm.SourceTextModule`s
// (and everything their module state references) alive until a
// memory-pressure GC clears the cache, which in practice means every test
// file's world accumulates until the worker hits `vmMemoryLimit`. The
// compiled-code caching the flag disables is already covered by the
// worker's own script and code caches.
v8.setFlagsFromString("--no-compilation-cache");
}
export { runVmTests as r, setupVmWorker as s };