vitest 4.0.0-beta.9 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +86 -102
- package/browser/context.d.ts +7 -0
- package/browser/context.js +20 -0
- package/dist/browser.d.ts +24 -7
- package/dist/browser.js +15 -5
- package/dist/chunks/{base.CA5N8Af0.js → base.CtHM3ryk.js} +36 -36
- package/dist/chunks/{benchmark.CJUa-Hsa.js → benchmark.DHKMYAts.js} +2 -2
- package/dist/chunks/{browser.d.DtfyY9yS.d.ts → browser.d.B9iJzZyn.d.ts} +3 -3
- package/dist/chunks/{cac.Dt7e1TIu.js → cac.B99MQg-w.js} +47 -73
- package/dist/chunks/{cli-api.eAzsLIxz.js → cli-api.PwHwIMss.js} +1519 -294
- package/dist/chunks/{config.d.DacWrqWe.d.ts → config.d.u2CUDWwS.d.ts} +5 -19
- package/dist/chunks/{console.7h5kHUIf.js → console.CTJL2nuH.js} +4 -6
- package/dist/chunks/{coverage.CDRAMTt7.js → coverage.FU3w4IrQ.js} +125 -1108
- package/dist/chunks/{creator.KEg6n5IC.js → creator.DucAaYBz.js} +10 -37
- package/dist/chunks/{defaults.CXFFjsi8.js → defaults.BOqNVLsY.js} +0 -1
- package/dist/chunks/environment.d.CrsxCzP1.d.ts +29 -0
- package/dist/chunks/evaluatedModules.Dg1zASAC.js +17 -0
- package/dist/chunks/{global.d.K6uBQHzY.d.ts → global.d.BgJSTpgQ.d.ts} +2 -17
- package/dist/chunks/{globals.CJrTTbxC.js → globals.BGT_RUsD.js} +11 -7
- package/dist/chunks/{index.BjKEiSn0.js → index.BdSLhLDZ.js} +3 -3
- package/dist/chunks/{index.DfviD7lX.js → index.CbWINfS7.js} +49 -21
- package/dist/chunks/{index.BIP7prJq.js → index.CcRZ6fUh.js} +1493 -114
- package/dist/chunks/{index.X0nbfr6-.js → index.Dc3xnDvT.js} +48 -289
- package/dist/chunks/{index.C832ioot.js → index.RwjEGCQ0.js} +4 -4
- package/dist/chunks/init-forks.DSafeltJ.js +54 -0
- package/dist/chunks/init-threads.SUtZ-067.js +17 -0
- package/dist/chunks/init.B2EESLQM.js +213 -0
- package/dist/chunks/{inspector.CvQD-Nie.js → inspector.DLZxSeU3.js} +2 -6
- package/dist/chunks/{moduleRunner.d.DxTLreRD.d.ts → moduleRunner.d.YtNsMIoJ.d.ts} +9 -14
- package/dist/chunks/{node.CyipiPvJ.js → node.BwAWWjHZ.js} +3 -4
- package/dist/chunks/{plugin.d.CIk0YiKb.d.ts → plugin.d.DQU1R5px.d.ts} +1 -1
- package/dist/chunks/{reporters.d.DmP-iHLr.d.ts → reporters.d.BMKt7f6I.d.ts} +1064 -1021
- package/dist/chunks/{resolveSnapshotEnvironment.Bvv2zr69.js → resolveSnapshotEnvironment.DJJKMKxb.js} +7 -8
- package/dist/chunks/{rpc.BKr6mtxz.js → rpc.cD77ENhU.js} +13 -14
- package/dist/chunks/{setup-common.B7I37Tji.js → setup-common.DR1sucx6.js} +6 -6
- package/dist/chunks/{startModuleRunner.BDRvKSdz.js → startModuleRunner.C2tTvmF9.js} +126 -110
- package/dist/chunks/{test.BAlBebnP.js → test.C3RPt8JR.js} +7 -7
- package/dist/chunks/{utils.D2R2NiOH.js → utils.CG9h5ccR.js} +2 -5
- package/dist/chunks/{vi.BB37KeLx.js → vi.BZvkKVkM.js} +61 -164
- package/dist/chunks/{vm.CjLTDaST.js → vm.DBeOXrP9.js} +20 -29
- package/dist/chunks/{worker.d.B2r4Ln6p.d.ts → worker.d.BFk-vvBU.d.ts} +42 -6
- package/dist/cli.js +12 -11
- package/dist/config.cjs +0 -1
- package/dist/config.d.ts +11 -13
- package/dist/config.js +1 -1
- package/dist/coverage.d.ts +7 -6
- package/dist/coverage.js +3 -14
- package/dist/environments.d.ts +3 -6
- package/dist/environments.js +1 -1
- package/dist/index.d.ts +20 -25
- package/dist/index.js +11 -7
- package/dist/module-evaluator.d.ts +5 -4
- package/dist/module-evaluator.js +11 -13
- package/dist/module-runner.js +5 -5
- package/dist/node.d.ts +82 -25
- package/dist/node.js +23 -20
- package/dist/reporters.d.ts +10 -9
- package/dist/reporters.js +12 -11
- package/dist/runners.d.ts +1 -1
- package/dist/runners.js +9 -7
- package/dist/snapshot.js +3 -3
- package/dist/suite.js +4 -3
- package/dist/worker.d.ts +26 -0
- package/dist/worker.js +45 -165
- package/dist/workers/forks.js +26 -43
- package/dist/workers/runVmTests.js +16 -12
- package/dist/workers/threads.js +26 -31
- package/dist/workers/vmForks.js +26 -39
- package/dist/workers/vmThreads.js +26 -29
- package/package.json +48 -32
- package/worker.d.ts +1 -0
- package/browser.d.ts +0 -1
- package/dist/chunks/environment.d.2fYMoz3o.d.ts +0 -66
- package/dist/chunks/moduleTransport.I-bgQy0S.js +0 -19
- package/dist/chunks/resolver.Bx6lE0iq.js +0 -119
- package/dist/chunks/typechecker.DB-fIMaH.js +0 -805
- package/dist/chunks/utils.C2YI6McM.js +0 -52
- package/dist/chunks/worker.d.DJ6qxO2w.d.ts +0 -8
- package/dist/workers.d.ts +0 -38
- package/dist/workers.js +0 -49
- package/execute.d.ts +0 -1
- package/utils.d.ts +0 -1
- package/workers.d.ts +0 -1
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
import fs, { promises, existsSync, readFileSync,
|
|
2
|
-
import { relative, resolve, dirname, extname, normalize,
|
|
1
|
+
import fs, { promises as promises$1, existsSync, mkdirSync, readFileSync, statSync, readdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { relative, resolve, dirname, join, extname, normalize, basename, isAbsolute } from 'pathe';
|
|
3
3
|
import { C as CoverageProviderMap } from './coverage.D_JHT54q.js';
|
|
4
4
|
import path, { resolve as resolve$1 } from 'node:path';
|
|
5
|
-
import { noop, createDefer, slash,
|
|
6
|
-
import {
|
|
5
|
+
import { noop, createDefer, slash, isExternalUrl, unwrapId, nanoid, withTrailingSlash, cleanUrl, wrapId, toArray, deepMerge, deepClone, isPrimitive, notNullish } from '@vitest/utils/helpers';
|
|
6
|
+
import { a as any, p as prompt } from './index.Dc3xnDvT.js';
|
|
7
7
|
import * as vite from 'vite';
|
|
8
|
-
import { parseAst,
|
|
8
|
+
import { parseAst, fetchModule, version, searchForWorkspaceRoot, mergeConfig, createServer } from 'vite';
|
|
9
9
|
import { A as API_PATH, c as configFiles, d as defaultBrowserPort, a as defaultPort } from './constants.D_Q9UYh-.js';
|
|
10
|
+
import * as nodeos from 'node:os';
|
|
10
11
|
import nodeos__default, { tmpdir } from 'node:os';
|
|
11
|
-
import { generateHash as generateHash$1, calculateSuiteHash, someTasksAreOnly, interpretTaskModes, generateFileHash, limitConcurrency, createFileTask as createFileTask$1,
|
|
12
|
+
import { generateHash as generateHash$1, calculateSuiteHash, someTasksAreOnly, interpretTaskModes, hasFailed, generateFileHash, limitConcurrency, createFileTask as createFileTask$1, getTasks, isTestCase } from '@vitest/runner/utils';
|
|
12
13
|
import { SnapshotManager } from '@vitest/snapshot/manager';
|
|
13
|
-
import { v as version$1 } from './cac.
|
|
14
|
+
import { v as version$1 } from './cac.B99MQg-w.js';
|
|
14
15
|
import { c as createBirpc } from './index.Bgo3tNWt.js';
|
|
15
|
-
import { p as parse,
|
|
16
|
+
import { p as parse, d as stringify, e as TraceMap, o as originalPositionFor, h as ancestor, i as printError, f as formatProjectName, w as withLabel, j as errorBanner, k as divider, l as Typechecker, m as generateCodeFrame, n as createDefinesScript, R as ReportersMap, B as BlobReporter, r as readBlobs, q as convertTasksToEvents, H as HangingProcessReporter } from './index.CcRZ6fUh.js';
|
|
16
17
|
import require$$0$3 from 'events';
|
|
17
18
|
import require$$1$1 from 'https';
|
|
18
19
|
import require$$2 from 'http';
|
|
@@ -25,29 +26,35 @@ import require$$0 from 'zlib';
|
|
|
25
26
|
import require$$0$1 from 'buffer';
|
|
26
27
|
import { g as getDefaultExportFromCjs } from './_commonjsHelpers.BFTU3MAI.js';
|
|
27
28
|
import crypto, { createHash } from 'node:crypto';
|
|
28
|
-
import {
|
|
29
|
-
import { h as hash, d as createFetchModuleFunction, n as normalizeResolvedIdToUrl, R as RandomSequencer, i as isPackageExists, g as getFilePoolName, e as isBrowserEnabled, r as resolveConfig, f as groupBy, j as getCoverageProvider, k as createPool, w as wildcardPatternToRegExp, a as resolveApiServerConfig, s as stdout } from './coverage.CDRAMTt7.js';
|
|
30
|
-
import { b as ancestor, c as createDefinesScript, d as convertTasksToEvents } from './typechecker.DB-fIMaH.js';
|
|
31
|
-
import { TraceMap, originalPositionFor, parseErrorStacktrace } from '@vitest/utils/source-map';
|
|
29
|
+
import { rootDir, distDir } from '../path.js';
|
|
32
30
|
import createDebug from 'debug';
|
|
31
|
+
import { h as hash, R as RandomSequencer, i as isPackageExists, c as isBrowserEnabled, r as resolveConfig, g as getCoverageProvider, a as resolveApiServerConfig } from './coverage.FU3w4IrQ.js';
|
|
32
|
+
import { readFile, writeFile, rename, stat, unlink, rm, mkdir, copyFile } from 'node:fs/promises';
|
|
33
33
|
import { VitestModuleEvaluator } from '#module-evaluator';
|
|
34
34
|
import { ModuleRunner } from 'vite/module-runner';
|
|
35
35
|
import { Console } from 'node:console';
|
|
36
36
|
import c from 'tinyrainbow';
|
|
37
|
+
import { highlight } from '@vitest/utils/highlight';
|
|
37
38
|
import { createRequire, builtinModules, isBuiltin } from 'node:module';
|
|
38
|
-
import url, { pathToFileURL } from 'node:url';
|
|
39
|
+
import url, { fileURLToPath, pathToFileURL } from 'node:url';
|
|
39
40
|
import { i as isTTY, a as isWindows } from './env.D4Lgay0q.js';
|
|
40
|
-
import {
|
|
41
|
+
import { isatty } from 'node:tty';
|
|
42
|
+
import EventEmitter$1, { EventEmitter } from 'node:events';
|
|
43
|
+
import { fork } from 'node:child_process';
|
|
44
|
+
import v8 from 'node:v8';
|
|
45
|
+
import { Worker } from 'node:worker_threads';
|
|
41
46
|
import pm from 'picomatch';
|
|
42
47
|
import { glob, isDynamicPattern } from 'tinyglobby';
|
|
43
48
|
import MagicString from 'magic-string';
|
|
44
49
|
import { hoistMocksPlugin, automockPlugin } from '@vitest/mocker/node';
|
|
45
|
-
import { c as configDefaults } from './defaults.
|
|
46
|
-
import {
|
|
50
|
+
import { c as configDefaults } from './defaults.BOqNVLsY.js';
|
|
51
|
+
import { KNOWN_ASSET_RE } from '@vitest/utils/constants';
|
|
52
|
+
import { findNearestPackageData } from '@vitest/utils/resolver';
|
|
47
53
|
import * as esModuleLexer from 'es-module-lexer';
|
|
48
|
-
import { a as BenchmarkReportsMap } from './index.
|
|
54
|
+
import { a as BenchmarkReportsMap } from './index.BdSLhLDZ.js';
|
|
49
55
|
import assert$1 from 'node:assert';
|
|
50
|
-
import {
|
|
56
|
+
import { serializeValue } from '@vitest/utils/serialize';
|
|
57
|
+
import { parseErrorStacktrace } from '@vitest/utils/source-map';
|
|
51
58
|
import readline from 'node:readline';
|
|
52
59
|
import { stripVTControlCharacters } from 'node:util';
|
|
53
60
|
|
|
@@ -5020,7 +5027,7 @@ var WebSocketServer = /*@__PURE__*/getDefaultExportFromCjs(websocketServerExport
|
|
|
5020
5027
|
async function getModuleGraph(ctx, projectName, id, browser = false) {
|
|
5021
5028
|
const graph = {}, externalized = /* @__PURE__ */ new Set(), inlined = /* @__PURE__ */ new Set(), project = ctx.getProjectByName(projectName);
|
|
5022
5029
|
async function get(mod, seen = /* @__PURE__ */ new Map()) {
|
|
5023
|
-
if (!mod || !mod.id || mod.id === "\
|
|
5030
|
+
if (!mod || !mod.id || mod.id === "\0vitest/browser") return;
|
|
5024
5031
|
if (seen.has(mod)) return seen.get(mod);
|
|
5025
5032
|
let id = clearId(mod.id);
|
|
5026
5033
|
seen.set(mod, id);
|
|
@@ -5081,8 +5088,8 @@ catch {}
|
|
|
5081
5088
|
}
|
|
5082
5089
|
|
|
5083
5090
|
function setup(ctx, _server) {
|
|
5084
|
-
const wss = new WebSocketServer({ noServer: true }), clients = /* @__PURE__ */ new Map()
|
|
5085
|
-
|
|
5091
|
+
const wss = new WebSocketServer({ noServer: true }), clients = /* @__PURE__ */ new Map();
|
|
5092
|
+
(_server || ctx.vite).httpServer?.on("upgrade", (request, socket, head) => {
|
|
5086
5093
|
if (!request.url) return;
|
|
5087
5094
|
const { pathname } = new URL(request.url, "http://localhost");
|
|
5088
5095
|
if (pathname === API_PATH) {
|
|
@@ -5107,11 +5114,11 @@ function setup(ctx, _server) {
|
|
|
5107
5114
|
return ctx.state.getPaths();
|
|
5108
5115
|
},
|
|
5109
5116
|
async readTestFile(id) {
|
|
5110
|
-
return !ctx.state.filesMap.has(id) || !existsSync(id) ? null : promises.readFile(id, "utf-8");
|
|
5117
|
+
return !ctx.state.filesMap.has(id) || !existsSync(id) ? null : promises$1.readFile(id, "utf-8");
|
|
5111
5118
|
},
|
|
5112
5119
|
async saveTestFile(id, content) {
|
|
5113
5120
|
if (!ctx.state.filesMap.has(id) || !existsSync(id)) throw new Error(`Test file "${id}" was not registered, so it cannot be updated using the API.`);
|
|
5114
|
-
return promises.writeFile(id, content, "utf-8");
|
|
5121
|
+
return promises$1.writeFile(id, content, "utf-8");
|
|
5115
5122
|
},
|
|
5116
5123
|
async rerun(files, resetTestNamePattern) {
|
|
5117
5124
|
await ctx.rerunFiles(files, void 0, true, resetTestNamePattern);
|
|
@@ -5132,7 +5139,7 @@ function setup(ctx, _server) {
|
|
|
5132
5139
|
const project = ctx.getProjectByName(projectName), result = browser ? await project.browser.vite.transformRequest(id) : await project.vite.transformRequest(id);
|
|
5133
5140
|
if (result) {
|
|
5134
5141
|
try {
|
|
5135
|
-
result.source = result.source || await promises.readFile(id, "utf-8");
|
|
5142
|
+
result.source = result.source || await promises$1.readFile(id, "utf-8");
|
|
5136
5143
|
} catch {}
|
|
5137
5144
|
return result;
|
|
5138
5145
|
}
|
|
@@ -5148,8 +5155,7 @@ function setup(ctx, _server) {
|
|
|
5148
5155
|
return ctx.state.getUnhandledErrors();
|
|
5149
5156
|
},
|
|
5150
5157
|
async getTestFiles() {
|
|
5151
|
-
|
|
5152
|
-
return spec.map((spec) => [
|
|
5158
|
+
return (await ctx.globTestSpecifications()).map((spec) => [
|
|
5153
5159
|
{
|
|
5154
5160
|
name: spec.project.config.name,
|
|
5155
5161
|
root: spec.project.config.root
|
|
@@ -5229,16 +5235,39 @@ var setup$1 = /*#__PURE__*/Object.freeze({
|
|
|
5229
5235
|
setup: setup
|
|
5230
5236
|
});
|
|
5231
5237
|
|
|
5238
|
+
function groupBy(collection, iteratee) {
|
|
5239
|
+
return collection.reduce((acc, item) => {
|
|
5240
|
+
const key = iteratee(item);
|
|
5241
|
+
return acc[key] ||= [], acc[key].push(item), acc;
|
|
5242
|
+
}, {});
|
|
5243
|
+
}
|
|
5244
|
+
function stdout() {
|
|
5245
|
+
// @ts-expect-error Node.js maps process.stdout to console._stdout
|
|
5246
|
+
// eslint-disable-next-line no-console
|
|
5247
|
+
return console._stdout || process.stdout;
|
|
5248
|
+
}
|
|
5249
|
+
function escapeRegExp(s) {
|
|
5250
|
+
// From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
|
|
5251
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5252
|
+
}
|
|
5253
|
+
function wildcardPatternToRegExp(pattern) {
|
|
5254
|
+
const negated = pattern[0] === "!";
|
|
5255
|
+
if (negated) pattern = pattern.slice(1);
|
|
5256
|
+
let regexp = `${pattern.split("*").map(escapeRegExp).join(".*")}$`;
|
|
5257
|
+
if (negated) regexp = `(?!${regexp})`;
|
|
5258
|
+
return new RegExp(`^${regexp}`, "i");
|
|
5259
|
+
}
|
|
5260
|
+
|
|
5232
5261
|
function createDebugger(namespace) {
|
|
5233
5262
|
const debug = createDebug(namespace);
|
|
5234
5263
|
if (debug.enabled) return debug;
|
|
5235
5264
|
}
|
|
5236
5265
|
|
|
5237
|
-
const debug = createDebugger("vitest:ast-collect-info"), verbose = createDebugger("vitest:ast-collect-verbose");
|
|
5266
|
+
const debug$1 = createDebugger("vitest:ast-collect-info"), verbose = createDebugger("vitest:ast-collect-verbose");
|
|
5238
5267
|
function astParseFile(filepath, code) {
|
|
5239
5268
|
const ast = parseAst(code);
|
|
5240
5269
|
if (verbose) verbose("Collecting", filepath, code);
|
|
5241
|
-
else debug?.("Collecting", filepath);
|
|
5270
|
+
else debug$1?.("Collecting", filepath);
|
|
5242
5271
|
const definitions = [], getName = (callee) => {
|
|
5243
5272
|
if (!callee) return null;
|
|
5244
5273
|
if (callee.type === "Identifier") return callee.name;
|
|
@@ -5302,7 +5331,7 @@ function astParseFile(filepath, code) {
|
|
|
5302
5331
|
const property = callee.tag?.property?.name;
|
|
5303
5332
|
isDynamicEach = property === "each" || property === "for";
|
|
5304
5333
|
}
|
|
5305
|
-
debug?.("Found", name, message, `(${mode})`), definitions.push({
|
|
5334
|
+
debug$1?.("Found", name, message, `(${mode})`), definitions.push({
|
|
5306
5335
|
start,
|
|
5307
5336
|
end,
|
|
5308
5337
|
name: message,
|
|
@@ -5338,17 +5367,13 @@ function createFailedFileTask(project, filepath, error) {
|
|
|
5338
5367
|
return file.file = file, file;
|
|
5339
5368
|
}
|
|
5340
5369
|
function serializeError(ctx, error) {
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
});
|
|
5349
|
-
return errors;
|
|
5350
|
-
}
|
|
5351
|
-
return [{
|
|
5370
|
+
return "errors" in error && "pluginCode" in error ? error.errors.map((e) => {
|
|
5371
|
+
return {
|
|
5372
|
+
name: error.name,
|
|
5373
|
+
message: e.text,
|
|
5374
|
+
stack: e.location ? `${error.name}: ${e.text}\n at ${relative(ctx.config.root, e.location.file)}:${e.location.line}:${e.location.column}` : ""
|
|
5375
|
+
};
|
|
5376
|
+
}) : [{
|
|
5352
5377
|
name: error.name,
|
|
5353
5378
|
stack: error.stack,
|
|
5354
5379
|
message: error.message
|
|
@@ -5390,8 +5415,8 @@ function createFileTask(testFilepath, code, requestMap, options) {
|
|
|
5390
5415
|
column: processedLocation.column
|
|
5391
5416
|
});
|
|
5392
5417
|
if (originalLocation.column != null) verbose?.(`Found location for`, definition.type, definition.name, `${processedLocation.line}:${processedLocation.column}`, "->", `${originalLocation.line}:${originalLocation.column}`), location = originalLocation;
|
|
5393
|
-
else debug?.("Cannot find original location for", definition.type, definition.name, `${processedLocation.column}:${processedLocation.line}`);
|
|
5394
|
-
} else debug?.("Cannot find original location for", definition.type, definition.name, `${definition.start}`);
|
|
5418
|
+
else debug$1?.("Cannot find original location for", definition.type, definition.name, `${processedLocation.column}:${processedLocation.line}`);
|
|
5419
|
+
} else debug$1?.("Cannot find original location for", definition.type, definition.name, `${definition.start}`);
|
|
5395
5420
|
if (definition.type === "suite") {
|
|
5396
5421
|
const task = {
|
|
5397
5422
|
type: definition.type,
|
|
@@ -5446,7 +5471,7 @@ async function astCollectTests(project, filepath) {
|
|
|
5446
5471
|
allowOnly: project.config.allowOnly,
|
|
5447
5472
|
testNamePattern: project.config.testNamePattern,
|
|
5448
5473
|
pool: project.browser ? "browser" : project.config.pool
|
|
5449
|
-
}) : (debug?.("Cannot parse", testFilepath, "(vite didn't return anything)"), createFailedFileTask(project, filepath, /* @__PURE__ */ new Error(`Failed to parse ${testFilepath}. Vite didn't return anything.`)));
|
|
5474
|
+
}) : (debug$1?.("Cannot parse", testFilepath, "(vite didn't return anything)"), createFailedFileTask(project, filepath, /* @__PURE__ */ new Error(`Failed to parse ${testFilepath}. Vite didn't return anything.`)));
|
|
5450
5475
|
}
|
|
5451
5476
|
async function transformSSR(project, filepath) {
|
|
5452
5477
|
const request = await project.vite.transformRequest(filepath, { ssr: false });
|
|
@@ -5611,18 +5636,197 @@ class VitestCache {
|
|
|
5611
5636
|
}
|
|
5612
5637
|
}
|
|
5613
5638
|
|
|
5639
|
+
const created = /* @__PURE__ */ new Set(), promises = /* @__PURE__ */ new Map();
|
|
5640
|
+
function createFetchModuleFunction(resolver, tmpDir = join(tmpdir(), nanoid()), dump) {
|
|
5641
|
+
return async (url, importer, environment, cacheFs, options) => {
|
|
5642
|
+
// We are copy pasting Vite's externalization logic from `fetchModule` because
|
|
5643
|
+
// we instead rely on our own `shouldExternalize` method because Vite
|
|
5644
|
+
// doesn't support `resolve.external` in non SSR environments (jsdom/happy-dom)
|
|
5645
|
+
if (url.startsWith("data:")) return {
|
|
5646
|
+
externalize: url,
|
|
5647
|
+
type: "builtin"
|
|
5648
|
+
};
|
|
5649
|
+
if (url === "/@vite/client" || url === "@vite/client")
|
|
5650
|
+
// this will be stubbed
|
|
5651
|
+
return {
|
|
5652
|
+
externalize: "/@vite/client",
|
|
5653
|
+
type: "module"
|
|
5654
|
+
};
|
|
5655
|
+
const isFileUrl = url.startsWith("file://");
|
|
5656
|
+
if (isExternalUrl(url) && !isFileUrl) return {
|
|
5657
|
+
externalize: url,
|
|
5658
|
+
type: "network"
|
|
5659
|
+
};
|
|
5660
|
+
// Vite does the same in `fetchModule`, but we want to externalize modules ourselves,
|
|
5661
|
+
// so we do this first to resolve the module and check its `id`. The next call of
|
|
5662
|
+
// `ensureEntryFromUrl` inside `fetchModule` is cached and should take no time
|
|
5663
|
+
// This also makes it so externalized modules are inside the module graph.
|
|
5664
|
+
const moduleGraphModule = await environment.moduleGraph.ensureEntryFromUrl(unwrapId(url)), cached = !!moduleGraphModule.transformResult;
|
|
5665
|
+
// if url is already cached, we can just confirm it's also cached on the server
|
|
5666
|
+
if (options?.cached && cached) return { cache: true };
|
|
5667
|
+
if (moduleGraphModule.id) {
|
|
5668
|
+
const externalize = await resolver.shouldExternalize(moduleGraphModule.id);
|
|
5669
|
+
if (externalize) return {
|
|
5670
|
+
externalize,
|
|
5671
|
+
type: "module"
|
|
5672
|
+
};
|
|
5673
|
+
}
|
|
5674
|
+
let moduleRunnerModule;
|
|
5675
|
+
if (dump?.dumpFolder && dump.readFromDump) {
|
|
5676
|
+
const path = resolve(dump?.dumpFolder, url.replace(/[^\w+]/g, "-"));
|
|
5677
|
+
if (existsSync(path)) {
|
|
5678
|
+
const code = await readFile(path, "utf-8"), matchIndex = code.lastIndexOf("\n//");
|
|
5679
|
+
if (matchIndex !== -1) {
|
|
5680
|
+
const { id, file } = JSON.parse(code.slice(matchIndex + 4));
|
|
5681
|
+
moduleRunnerModule = {
|
|
5682
|
+
code,
|
|
5683
|
+
id,
|
|
5684
|
+
url,
|
|
5685
|
+
file,
|
|
5686
|
+
invalidate: false
|
|
5687
|
+
};
|
|
5688
|
+
}
|
|
5689
|
+
}
|
|
5690
|
+
}
|
|
5691
|
+
if (!moduleRunnerModule) moduleRunnerModule = await fetchModule(environment, url, importer, {
|
|
5692
|
+
...options,
|
|
5693
|
+
inlineSourceMap: false
|
|
5694
|
+
}).catch(handleRollupError);
|
|
5695
|
+
const result = processResultSource(environment, moduleRunnerModule);
|
|
5696
|
+
if (dump?.dumpFolder && "code" in result) {
|
|
5697
|
+
const path = resolve(dump?.dumpFolder, result.url.replace(/[^\w+]/g, "-"));
|
|
5698
|
+
await writeFile(path, `${result.code}\n// ${JSON.stringify({
|
|
5699
|
+
id: result.id,
|
|
5700
|
+
file: result.file
|
|
5701
|
+
})}`, "utf-8");
|
|
5702
|
+
}
|
|
5703
|
+
if (!cacheFs || !("code" in result)) return result;
|
|
5704
|
+
const code = result.code, transformResult = result.transformResult;
|
|
5705
|
+
if (!transformResult) throw new Error(`"transformResult" in not defined. This is a bug in Vitest.`);
|
|
5706
|
+
// to avoid serialising large chunks of code,
|
|
5707
|
+
// we store them in a tmp file and read in the test thread
|
|
5708
|
+
if ("_vitestTmp" in transformResult) return getCachedResult(result, Reflect.get(transformResult, "_vitestTmp"));
|
|
5709
|
+
const dir = join(tmpDir, environment.name), name = hash("sha1", result.id, "hex"), tmp = join(dir, name);
|
|
5710
|
+
if (!created.has(dir)) mkdirSync(dir, { recursive: true }), created.add(dir);
|
|
5711
|
+
return promises.has(tmp) ? (await promises.get(tmp), Reflect.set(transformResult, "_vitestTmp", tmp), getCachedResult(result, tmp)) : (promises.set(tmp, atomicWriteFile(tmp, code).catch(() => writeFile(tmp, code, "utf-8")).finally(() => promises.delete(tmp))), await promises.get(tmp), getCachedResult(result, tmp));
|
|
5712
|
+
};
|
|
5713
|
+
}
|
|
5714
|
+
let SOURCEMAPPING_URL = "sourceMa";
|
|
5715
|
+
SOURCEMAPPING_URL += "ppingURL";
|
|
5716
|
+
const MODULE_RUNNER_SOURCEMAPPING_SOURCE = "//# sourceMappingSource=vite-generated";
|
|
5717
|
+
function processResultSource(environment, result) {
|
|
5718
|
+
if (!("code" in result)) return result;
|
|
5719
|
+
const node = environment.moduleGraph.getModuleById(result.id);
|
|
5720
|
+
if (node?.transformResult)
|
|
5721
|
+
// this also overrides node.transformResult.code which is also what the module
|
|
5722
|
+
// runner does under the hood by default (we disable source maps inlining)
|
|
5723
|
+
inlineSourceMap(node.transformResult);
|
|
5724
|
+
return {
|
|
5725
|
+
...result,
|
|
5726
|
+
code: node?.transformResult?.code || result.code,
|
|
5727
|
+
transformResult: node?.transformResult
|
|
5728
|
+
};
|
|
5729
|
+
}
|
|
5730
|
+
const OTHER_SOURCE_MAP_REGEXP = new RegExp(`//# ${SOURCEMAPPING_URL}=data:application/json[^,]+base64,([A-Za-z0-9+/=]+)$`, "gm");
|
|
5731
|
+
// we have to inline the source map ourselves, because
|
|
5732
|
+
// - we don't need //# sourceURL since we are running code in VM
|
|
5733
|
+
// - important in stack traces and the V8 coverage
|
|
5734
|
+
// - we need to inject an empty line for --inspect-brk
|
|
5735
|
+
function inlineSourceMap(result) {
|
|
5736
|
+
const map = result.map;
|
|
5737
|
+
let code = result.code;
|
|
5738
|
+
if (!map || !("version" in map) || code.includes(MODULE_RUNNER_SOURCEMAPPING_SOURCE)) return result;
|
|
5739
|
+
if (OTHER_SOURCE_MAP_REGEXP.lastIndex = 0, OTHER_SOURCE_MAP_REGEXP.test(code)) code = code.replace(OTHER_SOURCE_MAP_REGEXP, "");
|
|
5740
|
+
const sourceMap = { ...map };
|
|
5741
|
+
// If the first line is not present on source maps, add simple 1:1 mapping ([0,0,0,0], [1,0,0,0])
|
|
5742
|
+
// so that debuggers can be set to break on first line
|
|
5743
|
+
if (sourceMap.mappings[0] === ";") sourceMap.mappings = `AAAA,CAAA${sourceMap.mappings}`;
|
|
5744
|
+
return result.code = `${code.trimEnd()}\n${MODULE_RUNNER_SOURCEMAPPING_SOURCE}\n//# ${SOURCEMAPPING_URL}=${genSourceMapUrl(sourceMap)}\n`, result;
|
|
5745
|
+
}
|
|
5746
|
+
function genSourceMapUrl(map) {
|
|
5747
|
+
if (typeof map !== "string") map = JSON.stringify(map);
|
|
5748
|
+
return `data:application/json;base64,${Buffer.from(map).toString("base64")}`;
|
|
5749
|
+
}
|
|
5750
|
+
function getCachedResult(result, tmp) {
|
|
5751
|
+
return {
|
|
5752
|
+
cached: true,
|
|
5753
|
+
file: result.file,
|
|
5754
|
+
id: result.id,
|
|
5755
|
+
tmp,
|
|
5756
|
+
url: result.url,
|
|
5757
|
+
invalidate: result.invalidate
|
|
5758
|
+
};
|
|
5759
|
+
}
|
|
5760
|
+
// serialize rollup error on server to preserve details as a test error
|
|
5761
|
+
function handleRollupError(e) {
|
|
5762
|
+
throw e instanceof Error && ("plugin" in e || "frame" in e || "id" in e) ? {
|
|
5763
|
+
name: e.name,
|
|
5764
|
+
message: e.message,
|
|
5765
|
+
stack: e.stack,
|
|
5766
|
+
cause: e.cause,
|
|
5767
|
+
__vitest_rollup_error__: {
|
|
5768
|
+
plugin: e.plugin,
|
|
5769
|
+
id: e.id,
|
|
5770
|
+
loc: e.loc,
|
|
5771
|
+
frame: e.frame
|
|
5772
|
+
}
|
|
5773
|
+
} : e;
|
|
5774
|
+
}
|
|
5775
|
+
/**
|
|
5776
|
+
* Performs an atomic write operation using the write-then-rename pattern.
|
|
5777
|
+
*
|
|
5778
|
+
* Why we need this:
|
|
5779
|
+
* - Ensures file integrity by never leaving partially written files on disk
|
|
5780
|
+
* - Prevents other processes from reading incomplete data during writes
|
|
5781
|
+
* - Particularly important for test files where incomplete writes could cause test failures
|
|
5782
|
+
*
|
|
5783
|
+
* The implementation writes to a temporary file first, then renames it to the target path.
|
|
5784
|
+
* This rename operation is atomic on most filesystems (including POSIX-compliant ones),
|
|
5785
|
+
* guaranteeing that other processes will only ever see the complete file.
|
|
5786
|
+
*
|
|
5787
|
+
* Added in https://github.com/vitest-dev/vitest/pull/7531
|
|
5788
|
+
*/
|
|
5789
|
+
async function atomicWriteFile(realFilePath, data) {
|
|
5790
|
+
const dir = dirname(realFilePath), tmpFilePath = join(dir, `.tmp-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
5791
|
+
try {
|
|
5792
|
+
await writeFile(tmpFilePath, data, "utf-8"), await rename(tmpFilePath, realFilePath);
|
|
5793
|
+
} finally {
|
|
5794
|
+
try {
|
|
5795
|
+
if (await stat(tmpFilePath)) await unlink(tmpFilePath);
|
|
5796
|
+
} catch {}
|
|
5797
|
+
}
|
|
5798
|
+
}
|
|
5799
|
+
|
|
5800
|
+
// this is copy pasted from vite
|
|
5801
|
+
function normalizeResolvedIdToUrl(environment, resolvedId) {
|
|
5802
|
+
const root = environment.config.root, depsOptimizer = environment.depsOptimizer;
|
|
5803
|
+
let url;
|
|
5804
|
+
// normalize all imports into resolved URLs
|
|
5805
|
+
// e.g. `import 'foo'` -> `import '/@fs/.../node_modules/foo/index.js'`
|
|
5806
|
+
if (resolvedId.startsWith(withTrailingSlash(root)))
|
|
5807
|
+
// in root: infer short absolute path from root
|
|
5808
|
+
url = resolvedId.slice(root.length);
|
|
5809
|
+
else if (depsOptimizer?.isOptimizedDepFile(resolvedId) || resolvedId !== "/@react-refresh" && path.isAbsolute(resolvedId) && existsSync(cleanUrl(resolvedId)))
|
|
5810
|
+
// an optimized deps may not yet exists in the filesystem, or
|
|
5811
|
+
// a regular file exists but is out of root: rewrite to absolute /@fs/ paths
|
|
5812
|
+
url = path.posix.join("/@fs/", resolvedId);
|
|
5813
|
+
else url = resolvedId;
|
|
5814
|
+
// if the resolved id is not a valid browser import specifier,
|
|
5815
|
+
// prefix it to make it valid. We will strip this before feeding it
|
|
5816
|
+
// back into the transform pipeline
|
|
5817
|
+
if (url[0] !== "." && url[0] !== "/") url = wrapId(resolvedId);
|
|
5818
|
+
return url;
|
|
5819
|
+
}
|
|
5820
|
+
|
|
5614
5821
|
class ServerModuleRunner extends ModuleRunner {
|
|
5615
|
-
constructor(environment,
|
|
5616
|
-
const fetchModule = createFetchModuleFunction(resolver, false);
|
|
5822
|
+
constructor(environment, fetcher, config) {
|
|
5617
5823
|
super({
|
|
5618
5824
|
hmr: false,
|
|
5619
|
-
sourcemapInterceptor: "node",
|
|
5620
5825
|
transport: { async invoke(event) {
|
|
5621
5826
|
if (event.type !== "custom") throw new Error(`Vitest Module Runner doesn't support Vite HMR events.`);
|
|
5622
5827
|
const { data } = event.data;
|
|
5623
5828
|
try {
|
|
5624
|
-
|
|
5625
|
-
return { result };
|
|
5829
|
+
return { result: await fetcher(data[0], data[1], environment, false, data[2]) };
|
|
5626
5830
|
} catch (error) {
|
|
5627
5831
|
return { error };
|
|
5628
5832
|
}
|
|
@@ -5761,8 +5965,8 @@ class Logger {
|
|
|
5761
5965
|
const projectsFilter = toArray(config.project);
|
|
5762
5966
|
if (projectsFilter.length) this.console.error(c.dim("projects: ") + c.yellow(projectsFilter.join(comma)));
|
|
5763
5967
|
this.ctx.projects.forEach((project) => {
|
|
5764
|
-
const config = project.config
|
|
5765
|
-
if (
|
|
5968
|
+
const config = project.config;
|
|
5969
|
+
if (!project.isRootProject() && project.name) this.console.error(`\n${formatProjectName(project)}\n`);
|
|
5766
5970
|
if (config.include) this.console.error(c.dim("include: ") + c.yellow(config.include.join(comma)));
|
|
5767
5971
|
if (config.exclude) this.console.error(c.dim("exclude: ") + c.yellow(config.exclude.join(comma)));
|
|
5768
5972
|
if (config.typecheck.enabled) this.console.error(c.dim("typecheck include: ") + c.yellow(config.typecheck.include.join(comma))), this.console.error(c.dim("typecheck exclude: ") + c.yellow(config.typecheck.exclude.join(comma)));
|
|
@@ -5787,7 +5991,7 @@ class Logger {
|
|
|
5787
5991
|
if (!project.browser) return;
|
|
5788
5992
|
const resolvedUrls = project.browser.vite.resolvedUrls, origin = resolvedUrls?.local[0] ?? resolvedUrls?.network[0];
|
|
5789
5993
|
if (!origin) return;
|
|
5790
|
-
const output = project.isRootProject() ? "" : formatProjectName(project), provider = project.browser.provider
|
|
5994
|
+
const output = project.isRootProject() ? "" : formatProjectName(project), provider = project.browser.provider?.name, providerString = provider === "preview" ? "" : ` by ${c.reset(c.bold(provider))}`;
|
|
5791
5995
|
this.log(c.dim(`${output}Browser runner started${providerString} ${c.dim("at")} ${c.blue(new URL("/__vitest_test__/", origin))}\n`));
|
|
5792
5996
|
}
|
|
5793
5997
|
printUnhandledErrors(errors) {
|
|
@@ -5819,7 +6023,8 @@ This might cause false positive tests. Resolve unhandled errors to make sure you
|
|
|
5819
6023
|
// Interrupted signals don't set exit code automatically.
|
|
5820
6024
|
// Use same exit code as node: https://nodejs.org/api/process.html#signal-events
|
|
5821
6025
|
if (cleanup(), process.exitCode === void 0) process.exitCode = exitCode !== void 0 ? 128 + exitCode : Number(signal);
|
|
5822
|
-
|
|
6026
|
+
// Timeout to flush stderr
|
|
6027
|
+
setTimeout(() => process.exit(), 1);
|
|
5823
6028
|
};
|
|
5824
6029
|
process.once("SIGINT", onExit), process.once("SIGTERM", onExit), process.once("exit", onExit), this.ctx.onClose(() => {
|
|
5825
6030
|
process.off("SIGINT", onExit), process.off("SIGTERM", onExit), process.off("exit", onExit), cleanup();
|
|
@@ -5853,7 +6058,7 @@ class VitestPackageInstaller {
|
|
|
5853
6058
|
}
|
|
5854
6059
|
if (/* @__PURE__ */ isPackageExists(dependency, { paths: [root, __dirname] })) return true;
|
|
5855
6060
|
if (process.stderr.write(c.red(`${c.inverse(c.red(" MISSING DEPENDENCY "))} Cannot find dependency '${dependency}'\n\n`)), !isTTY) return false;
|
|
5856
|
-
const
|
|
6061
|
+
const { install } = await (await import('./index.Dc3xnDvT.js').then(function (n) { return n.i; })).default({
|
|
5857
6062
|
type: "confirm",
|
|
5858
6063
|
name: "install",
|
|
5859
6064
|
message: c.reset(`Do you want to install ${c.green(dependency)}?`)
|
|
@@ -5866,12 +6071,1057 @@ class VitestPackageInstaller {
|
|
|
5866
6071
|
}
|
|
5867
6072
|
}
|
|
5868
6073
|
|
|
6074
|
+
function getDefaultThreadsCount(config) {
|
|
6075
|
+
const numCpus = typeof nodeos.availableParallelism === "function" ? nodeos.availableParallelism() : nodeos.cpus().length;
|
|
6076
|
+
return config.watch ? Math.max(Math.floor(numCpus / 2), 1) : Math.max(numCpus - 1, 1);
|
|
6077
|
+
}
|
|
6078
|
+
function getWorkerMemoryLimit(config) {
|
|
6079
|
+
return config.vmMemoryLimit ? config.vmMemoryLimit : 1 / (config.maxWorkers ?? getDefaultThreadsCount(config));
|
|
6080
|
+
}
|
|
6081
|
+
/**
|
|
6082
|
+
* Converts a string representing an amount of memory to bytes.
|
|
6083
|
+
*
|
|
6084
|
+
* @param input The value to convert to bytes.
|
|
6085
|
+
* @param percentageReference The reference value to use when a '%' value is supplied.
|
|
6086
|
+
*/
|
|
6087
|
+
function stringToBytes(input, percentageReference) {
|
|
6088
|
+
if (input === null || input === void 0) return input;
|
|
6089
|
+
if (typeof input === "string") if (Number.isNaN(Number.parseFloat(input.slice(-1)))) {
|
|
6090
|
+
let [, numericString, trailingChars] = input.match(/(.*?)([^0-9.-]+)$/) || [];
|
|
6091
|
+
if (trailingChars && numericString) {
|
|
6092
|
+
const numericValue = Number.parseFloat(numericString);
|
|
6093
|
+
switch (trailingChars = trailingChars.toLowerCase(), trailingChars) {
|
|
6094
|
+
case "%":
|
|
6095
|
+
input = numericValue / 100;
|
|
6096
|
+
break;
|
|
6097
|
+
case "kb":
|
|
6098
|
+
case "k": return numericValue * 1e3;
|
|
6099
|
+
case "kib": return numericValue * 1024;
|
|
6100
|
+
case "mb":
|
|
6101
|
+
case "m": return numericValue * 1e3 * 1e3;
|
|
6102
|
+
case "mib": return numericValue * 1024 * 1024;
|
|
6103
|
+
case "gb":
|
|
6104
|
+
case "g": return numericValue * 1e3 * 1e3 * 1e3;
|
|
6105
|
+
case "gib": return numericValue * 1024 * 1024 * 1024;
|
|
6106
|
+
}
|
|
6107
|
+
}
|
|
6108
|
+
} else input = Number.parseFloat(input);
|
|
6109
|
+
if (typeof input === "number") if (input <= 1 && input > 0) {
|
|
6110
|
+
if (percentageReference) return Math.floor(input * percentageReference);
|
|
6111
|
+
throw new Error("For a percentage based memory limit a percentageReference must be supplied");
|
|
6112
|
+
} else if (input > 1) return Math.floor(input);
|
|
6113
|
+
else throw new Error("Unexpected numerical input for \"memoryLimit\"");
|
|
6114
|
+
return null;
|
|
6115
|
+
}
|
|
6116
|
+
|
|
6117
|
+
async function getSpecificationsEnvironments(specifications) {
|
|
6118
|
+
const environments = /* @__PURE__ */ new WeakMap(), cache = /* @__PURE__ */ new Map();
|
|
6119
|
+
return await Promise.all(specifications.map(async (spec) => {
|
|
6120
|
+
const { moduleId: filepath, project } = spec;
|
|
6121
|
+
// reuse if projects have the same test files
|
|
6122
|
+
let code = cache.get(filepath);
|
|
6123
|
+
if (!code) code = await promises$1.readFile(filepath, "utf-8"), cache.set(filepath, code);
|
|
6124
|
+
// 1. Check for control comments in the file
|
|
6125
|
+
let env = code.match(/@(?:vitest|jest)-environment\s+([\w-]+)\b/)?.[1];
|
|
6126
|
+
// 2. Fallback to global env
|
|
6127
|
+
env ||= project.config.environment || "node";
|
|
6128
|
+
let envOptionsJson = code.match(/@(?:vitest|jest)-environment-options\s+(.+)/)?.[1];
|
|
6129
|
+
if (envOptionsJson?.endsWith("*/"))
|
|
6130
|
+
// Trim closing Docblock characters the above regex might have captured
|
|
6131
|
+
envOptionsJson = envOptionsJson.slice(0, -2);
|
|
6132
|
+
const envOptions = JSON.parse(envOptionsJson || "null"), environment = {
|
|
6133
|
+
name: env,
|
|
6134
|
+
options: envOptions ? { [env === "happy-dom" ? "happyDOM" : env]: envOptions } : null
|
|
6135
|
+
};
|
|
6136
|
+
environments.set(spec, environment);
|
|
6137
|
+
})), environments;
|
|
6138
|
+
}
|
|
6139
|
+
|
|
6140
|
+
const debug = createDebugger("vitest:browser:pool");
|
|
6141
|
+
function createBrowserPool(vitest) {
|
|
6142
|
+
const providers = /* @__PURE__ */ new Set(), numCpus = typeof nodeos.availableParallelism === "function" ? nodeos.availableParallelism() : nodeos.cpus().length, maxThreadsCount = Math.min(12, numCpus - 1), threadsCount = vitest.config.watch ? Math.max(Math.floor(maxThreadsCount / 2), 1) : Math.max(maxThreadsCount, 1), projectPools = /* @__PURE__ */ new WeakMap(), ensurePool = (project) => {
|
|
6143
|
+
if (projectPools.has(project)) return projectPools.get(project);
|
|
6144
|
+
debug?.("creating pool for project %s", project.name);
|
|
6145
|
+
const resolvedUrls = project.browser.vite.resolvedUrls, origin = resolvedUrls?.local[0] ?? resolvedUrls?.network[0];
|
|
6146
|
+
if (!origin) throw new Error(`Can't find browser origin URL for project "${project.name}"`);
|
|
6147
|
+
const pool = new BrowserPool(project, {
|
|
6148
|
+
maxWorkers: getThreadsCount(project),
|
|
6149
|
+
origin
|
|
6150
|
+
});
|
|
6151
|
+
return projectPools.set(project, pool), vitest.onCancel(() => {
|
|
6152
|
+
pool.cancel();
|
|
6153
|
+
}), pool;
|
|
6154
|
+
}, runWorkspaceTests = async (method, specs) => {
|
|
6155
|
+
const groupedFiles = /* @__PURE__ */ new Map();
|
|
6156
|
+
for (const { project, moduleId, testLines } of specs) {
|
|
6157
|
+
const files = groupedFiles.get(project) || [];
|
|
6158
|
+
files.push({
|
|
6159
|
+
filepath: moduleId,
|
|
6160
|
+
testLocations: testLines
|
|
6161
|
+
}), groupedFiles.set(project, files);
|
|
6162
|
+
}
|
|
6163
|
+
let isCancelled = false;
|
|
6164
|
+
vitest.onCancel(() => {
|
|
6165
|
+
isCancelled = true;
|
|
6166
|
+
});
|
|
6167
|
+
const initialisedPools = await Promise.all([...groupedFiles.entries()].map(async ([project, files]) => {
|
|
6168
|
+
if (await project._initBrowserProvider(), !project.browser) throw new TypeError(`The browser server was not initialized${project.name ? ` for the "${project.name}" project` : ""}. This is a bug in Vitest. Please, open a new issue with reproduction.`);
|
|
6169
|
+
if (isCancelled) return;
|
|
6170
|
+
debug?.("provider is ready for %s project", project.name);
|
|
6171
|
+
const pool = ensurePool(project);
|
|
6172
|
+
return vitest.state.clearFiles(project, files.map((f) => f.filepath)), providers.add(project.browser.provider), {
|
|
6173
|
+
pool,
|
|
6174
|
+
provider: project.browser.provider,
|
|
6175
|
+
runTests: () => pool.runTests(method, files)
|
|
6176
|
+
};
|
|
6177
|
+
}));
|
|
6178
|
+
if (isCancelled) return;
|
|
6179
|
+
const parallelPools = [], nonParallelPools = [];
|
|
6180
|
+
for (const pool of initialisedPools) {
|
|
6181
|
+
if (!pool)
|
|
6182
|
+
// this means it was cancelled
|
|
6183
|
+
return;
|
|
6184
|
+
if (pool.provider.mocker && pool.provider.supportsParallelism) parallelPools.push(pool.runTests);
|
|
6185
|
+
else nonParallelPools.push(pool.runTests);
|
|
6186
|
+
}
|
|
6187
|
+
await Promise.all(parallelPools.map((runTests) => runTests()));
|
|
6188
|
+
for (const runTests of nonParallelPools) {
|
|
6189
|
+
if (isCancelled) return;
|
|
6190
|
+
await runTests();
|
|
6191
|
+
}
|
|
6192
|
+
};
|
|
6193
|
+
function getThreadsCount(project) {
|
|
6194
|
+
const config = project.config.browser;
|
|
6195
|
+
return !config.headless || !config.fileParallelism || !project.browser.provider.supportsParallelism ? 1 : project.config.maxWorkers ? project.config.maxWorkers : threadsCount;
|
|
6196
|
+
}
|
|
6197
|
+
return {
|
|
6198
|
+
name: "browser",
|
|
6199
|
+
async close() {
|
|
6200
|
+
await Promise.all([...providers].map((provider) => provider.close())), vitest._browserSessions.sessionIds.clear(), providers.clear(), vitest.projects.forEach((project) => {
|
|
6201
|
+
project.browser?.state.orchestrators.forEach((orchestrator) => {
|
|
6202
|
+
orchestrator.$close();
|
|
6203
|
+
});
|
|
6204
|
+
}), debug?.("browser pool closed all providers");
|
|
6205
|
+
},
|
|
6206
|
+
runTests: (files) => runWorkspaceTests("run", files),
|
|
6207
|
+
collectTests: (files) => runWorkspaceTests("collect", files)
|
|
6208
|
+
};
|
|
6209
|
+
}
|
|
6210
|
+
function escapePathToRegexp(path) {
|
|
6211
|
+
return path.replace(/[/\\.?*()^${}|[\]+]/g, "\\$&");
|
|
6212
|
+
}
|
|
6213
|
+
class BrowserPool {
|
|
6214
|
+
_queue = [];
|
|
6215
|
+
_promise;
|
|
6216
|
+
_providedContext;
|
|
6217
|
+
readySessions = /* @__PURE__ */ new Set();
|
|
6218
|
+
constructor(project, options) {
|
|
6219
|
+
this.project = project, this.options = options;
|
|
6220
|
+
}
|
|
6221
|
+
cancel() {
|
|
6222
|
+
this._queue = [];
|
|
6223
|
+
}
|
|
6224
|
+
reject(error) {
|
|
6225
|
+
this._promise?.reject(error), this._promise = void 0, this.cancel();
|
|
6226
|
+
}
|
|
6227
|
+
get orchestrators() {
|
|
6228
|
+
return this.project.browser.state.orchestrators;
|
|
6229
|
+
}
|
|
6230
|
+
async runTests(method, files) {
|
|
6231
|
+
if (this._promise ??= createDefer(), !files.length) return debug?.("no tests found, finishing test run immediately"), this._promise.resolve(), this._promise;
|
|
6232
|
+
if (this._providedContext = stringify(this.project.getProvidedContext()), this._queue.push(...files), this.readySessions.forEach((sessionId) => {
|
|
6233
|
+
if (this._queue.length) this.readySessions.delete(sessionId), this.runNextTest(method, sessionId);
|
|
6234
|
+
}), this.orchestrators.size >= this.options.maxWorkers) return debug?.("all orchestrators are ready, not creating more"), this._promise;
|
|
6235
|
+
// open the minimum amount of tabs
|
|
6236
|
+
// if there is only 1 file running, we don't need 8 tabs running
|
|
6237
|
+
const workerCount = Math.min(this.options.maxWorkers - this.orchestrators.size, files.length), promises = [];
|
|
6238
|
+
for (let i = 0; i < workerCount; i++) {
|
|
6239
|
+
const sessionId = crypto.randomUUID();
|
|
6240
|
+
this.project.vitest._browserSessions.sessionIds.add(sessionId);
|
|
6241
|
+
const project = this.project.name;
|
|
6242
|
+
debug?.("[%s] creating session for %s", sessionId, project);
|
|
6243
|
+
const page = this.openPage(sessionId).then(() => {
|
|
6244
|
+
// start running tests on the page when it's ready
|
|
6245
|
+
this.runNextTest(method, sessionId);
|
|
6246
|
+
});
|
|
6247
|
+
promises.push(page);
|
|
6248
|
+
}
|
|
6249
|
+
return await Promise.all(promises), debug?.("all sessions are created"), this._promise;
|
|
6250
|
+
}
|
|
6251
|
+
async openPage(sessionId) {
|
|
6252
|
+
const sessionPromise = this.project.vitest._browserSessions.createSession(sessionId, this.project, this), browser = this.project.browser, url = new URL("/__vitest_test__/", this.options.origin);
|
|
6253
|
+
url.searchParams.set("sessionId", sessionId);
|
|
6254
|
+
const pagePromise = browser.provider.openPage(sessionId, url.toString());
|
|
6255
|
+
await Promise.all([sessionPromise, pagePromise]);
|
|
6256
|
+
}
|
|
6257
|
+
getOrchestrator(sessionId) {
|
|
6258
|
+
const orchestrator = this.orchestrators.get(sessionId);
|
|
6259
|
+
if (!orchestrator) throw new Error(`Orchestrator not found for session ${sessionId}. This is a bug in Vitest. Please, open a new issue with reproduction.`);
|
|
6260
|
+
return orchestrator;
|
|
6261
|
+
}
|
|
6262
|
+
finishSession(sessionId) {
|
|
6263
|
+
// the last worker finished running tests
|
|
6264
|
+
if (this.readySessions.add(sessionId), this.readySessions.size === this.orchestrators.size) this._promise?.resolve(), this._promise = void 0, debug?.("[%s] all tests finished running", sessionId);
|
|
6265
|
+
else debug?.(`did not finish sessions for ${sessionId}: |ready - %s| |overall - %s|`, [...this.readySessions].join(", "), [...this.orchestrators.keys()].join(", "));
|
|
6266
|
+
}
|
|
6267
|
+
runNextTest(method, sessionId) {
|
|
6268
|
+
const file = this._queue.shift();
|
|
6269
|
+
if (!file) {
|
|
6270
|
+
// we don't need to cleanup testers if isolation is enabled,
|
|
6271
|
+
// because cleanup is done at the end of every test
|
|
6272
|
+
if (debug?.("[%s] no more tests to run", sessionId), this.project.config.browser.isolate) {
|
|
6273
|
+
this.finishSession(sessionId);
|
|
6274
|
+
return;
|
|
6275
|
+
}
|
|
6276
|
+
this.getOrchestrator(sessionId).cleanupTesters().catch((error) => this.reject(error)).finally(() => this.finishSession(sessionId));
|
|
6277
|
+
return;
|
|
6278
|
+
}
|
|
6279
|
+
if (!this._promise) throw new Error(`Unexpected empty queue`);
|
|
6280
|
+
const orchestrator = this.getOrchestrator(sessionId);
|
|
6281
|
+
debug?.("[%s] run test %s", sessionId, file), this.setBreakpoint(sessionId, file.filepath).then(() => {
|
|
6282
|
+
// this starts running tests inside the orchestrator
|
|
6283
|
+
orchestrator.createTesters({
|
|
6284
|
+
method,
|
|
6285
|
+
files: [file],
|
|
6286
|
+
providedContext: this._providedContext || "[{}]"
|
|
6287
|
+
}).then(() => {
|
|
6288
|
+
debug?.("[%s] test %s finished running", sessionId, file), this.runNextTest(method, sessionId);
|
|
6289
|
+
}).catch((error) => {
|
|
6290
|
+
// if user cancels the test run manually, ignore the error and exit gracefully
|
|
6291
|
+
if (this.project.vitest.isCancelling && error instanceof Error && error.message.startsWith("Browser connection was closed while running tests")) {
|
|
6292
|
+
this.cancel(), this._promise?.resolve(), this._promise = void 0, debug?.("[%s] browser connection was closed", sessionId);
|
|
6293
|
+
return;
|
|
6294
|
+
}
|
|
6295
|
+
debug?.("[%s] error during %s test run: %s", sessionId, file, error), this.reject(error);
|
|
6296
|
+
});
|
|
6297
|
+
}).catch((err) => this.reject(err));
|
|
6298
|
+
}
|
|
6299
|
+
async setBreakpoint(sessionId, file) {
|
|
6300
|
+
if (!this.project.config.inspector.waitForDebugger) return;
|
|
6301
|
+
const provider = this.project.browser.provider, browser = this.project.config.browser.name;
|
|
6302
|
+
if (shouldIgnoreDebugger(provider.name, browser)) {
|
|
6303
|
+
debug?.("[$s] ignoring debugger in %s browser because it is not supported", sessionId, browser);
|
|
6304
|
+
return;
|
|
6305
|
+
}
|
|
6306
|
+
if (!provider.getCDPSession) throw new Error("Unable to set breakpoint, CDP not supported");
|
|
6307
|
+
debug?.("[%s] set breakpoint for %s", sessionId, file);
|
|
6308
|
+
const session = await provider.getCDPSession(sessionId);
|
|
6309
|
+
await session.send("Debugger.enable", {}), await session.send("Debugger.setBreakpointByUrl", {
|
|
6310
|
+
lineNumber: 0,
|
|
6311
|
+
urlRegex: escapePathToRegexp(file)
|
|
6312
|
+
});
|
|
6313
|
+
}
|
|
6314
|
+
}
|
|
6315
|
+
function shouldIgnoreDebugger(provider, browser) {
|
|
6316
|
+
return provider === "webdriverio" ? browser !== "chrome" && browser !== "edge" : browser !== "chromium";
|
|
6317
|
+
}
|
|
6318
|
+
|
|
6319
|
+
function createMethodsRPC(project, options = {}) {
|
|
6320
|
+
const vitest = project.vitest, cacheFs = options.cacheFs ?? false;
|
|
6321
|
+
if (project.vitest.state.metadata[project.name] ??= {
|
|
6322
|
+
externalized: {},
|
|
6323
|
+
duration: {},
|
|
6324
|
+
tmps: {}
|
|
6325
|
+
}, project.config.dumpDir && !existsSync(project.config.dumpDir)) mkdirSync(project.config.dumpDir, { recursive: true });
|
|
6326
|
+
return project.vitest.state.metadata[project.name].dumpDir = project.config.dumpDir, {
|
|
6327
|
+
async fetch(url, importer, environmentName, options) {
|
|
6328
|
+
const environment = project.vite.environments[environmentName];
|
|
6329
|
+
if (!environment) throw new Error(`The environment ${environmentName} was not defined in the Vite config.`);
|
|
6330
|
+
const start = performance.now();
|
|
6331
|
+
return await project._fetcher(url, importer, environment, cacheFs, options).then((result) => {
|
|
6332
|
+
const duration = performance.now() - start;
|
|
6333
|
+
project.vitest.state.transformTime += duration;
|
|
6334
|
+
const metadata = project.vitest.state.metadata[project.name];
|
|
6335
|
+
if ("externalize" in result) metadata.externalized[url] = result.externalize;
|
|
6336
|
+
if ("tmp" in result) metadata.tmps[url] = result.tmp;
|
|
6337
|
+
return metadata.duration[url] ??= [], metadata.duration[url].push(duration), result;
|
|
6338
|
+
});
|
|
6339
|
+
},
|
|
6340
|
+
async resolve(id, importer, environmentName) {
|
|
6341
|
+
const environment = project.vite.environments[environmentName];
|
|
6342
|
+
if (!environment) throw new Error(`The environment ${environmentName} was not defined in the Vite config.`);
|
|
6343
|
+
const resolved = await environment.pluginContainer.resolveId(id, importer);
|
|
6344
|
+
return resolved ? {
|
|
6345
|
+
file: cleanUrl(resolved.id),
|
|
6346
|
+
url: normalizeResolvedIdToUrl(environment, resolved.id),
|
|
6347
|
+
id: resolved.id
|
|
6348
|
+
} : null;
|
|
6349
|
+
},
|
|
6350
|
+
snapshotSaved(snapshot) {
|
|
6351
|
+
vitest.snapshot.add(snapshot);
|
|
6352
|
+
},
|
|
6353
|
+
resolveSnapshotPath(testPath) {
|
|
6354
|
+
return vitest.snapshot.resolvePath(testPath, { config: project.serializedConfig });
|
|
6355
|
+
},
|
|
6356
|
+
async transform(id) {
|
|
6357
|
+
const environment = project.vite.environments.__vitest_vm__;
|
|
6358
|
+
if (!environment) throw new Error(`The VM environment was not defined in the Vite config. This is a bug in Vitest. Please, open a new issue with reproduction.`);
|
|
6359
|
+
const url = normalizeResolvedIdToUrl(environment, fileURLToPath(id));
|
|
6360
|
+
return { code: (await environment.transformRequest(url).catch(handleRollupError))?.code };
|
|
6361
|
+
},
|
|
6362
|
+
async onQueued(file) {
|
|
6363
|
+
if (options.collect) vitest.state.collectFiles(project, [file]);
|
|
6364
|
+
else await vitest._testRun.enqueued(project, file);
|
|
6365
|
+
},
|
|
6366
|
+
async onCollected(files) {
|
|
6367
|
+
if (options.collect) vitest.state.collectFiles(project, files);
|
|
6368
|
+
else await vitest._testRun.collected(project, files);
|
|
6369
|
+
},
|
|
6370
|
+
onAfterSuiteRun(meta) {
|
|
6371
|
+
vitest.coverageProvider?.onAfterSuiteRun(meta);
|
|
6372
|
+
},
|
|
6373
|
+
async onTaskAnnotate(testId, annotation) {
|
|
6374
|
+
return vitest._testRun.annotate(testId, annotation);
|
|
6375
|
+
},
|
|
6376
|
+
async onTaskUpdate(packs, events) {
|
|
6377
|
+
if (options.collect) vitest.state.updateTasks(packs);
|
|
6378
|
+
else await vitest._testRun.updated(packs, events);
|
|
6379
|
+
},
|
|
6380
|
+
async onUserConsoleLog(log) {
|
|
6381
|
+
if (options.collect) vitest.state.updateUserLog(log);
|
|
6382
|
+
else await vitest._testRun.log(log);
|
|
6383
|
+
},
|
|
6384
|
+
onUnhandledError(err, type) {
|
|
6385
|
+
vitest.state.catchError(err, type);
|
|
6386
|
+
},
|
|
6387
|
+
onCancel(reason) {
|
|
6388
|
+
vitest.cancelCurrentRun(reason);
|
|
6389
|
+
},
|
|
6390
|
+
getCountOfFailedTests() {
|
|
6391
|
+
return vitest.state.getCountOfFailedTests();
|
|
6392
|
+
}
|
|
6393
|
+
};
|
|
6394
|
+
}
|
|
6395
|
+
|
|
6396
|
+
var RunnerState = /* @__PURE__ */ function(RunnerState) {
|
|
6397
|
+
return RunnerState["IDLE"] = "idle", RunnerState["STARTING"] = "starting", RunnerState["STARTED"] = "started", RunnerState["STOPPING"] = "stopping", RunnerState["STOPPED"] = "stopped", RunnerState;
|
|
6398
|
+
}(RunnerState || {});
|
|
6399
|
+
const START_TIMEOUT = 1e4, STOP_TIMEOUT = 1e4;
|
|
6400
|
+
/** @experimental */
|
|
6401
|
+
class PoolRunner {
|
|
6402
|
+
/** Exposed to test runner as `VITEST_POOL_ID`. Value is between 1-`maxWorkers`. */
|
|
6403
|
+
poolId = void 0;
|
|
6404
|
+
project;
|
|
6405
|
+
environment;
|
|
6406
|
+
_state = RunnerState.IDLE;
|
|
6407
|
+
_operationLock = null;
|
|
6408
|
+
_eventEmitter = new EventEmitter();
|
|
6409
|
+
_rpc;
|
|
6410
|
+
get isTerminated() {
|
|
6411
|
+
return this._state === RunnerState.STOPPED;
|
|
6412
|
+
}
|
|
6413
|
+
get isStarted() {
|
|
6414
|
+
return this._state === RunnerState.STARTED;
|
|
6415
|
+
}
|
|
6416
|
+
constructor(options, worker) {
|
|
6417
|
+
this.worker = worker, this.project = options.project, this.environment = options.environment, this._rpc = createBirpc(createMethodsRPC(this.project, {
|
|
6418
|
+
collect: options.method === "collect",
|
|
6419
|
+
cacheFs: worker.cacheFs
|
|
6420
|
+
}), {
|
|
6421
|
+
eventNames: ["onCancel"],
|
|
6422
|
+
post: (request) => this.postMessage(request),
|
|
6423
|
+
on: (callback) => this._eventEmitter.on("rpc", callback),
|
|
6424
|
+
timeout: -1
|
|
6425
|
+
}), this.project.vitest.onCancel((reason) => this._rpc.onCancel(reason));
|
|
6426
|
+
}
|
|
6427
|
+
postMessage(message) {
|
|
6428
|
+
// Only send messages when runner is active (not fully stopped)
|
|
6429
|
+
// Allow sending during STOPPING state for the 'stop' message itself
|
|
6430
|
+
if (this._state !== RunnerState.STOPPED) return this.worker.send(message);
|
|
6431
|
+
}
|
|
6432
|
+
async start() {
|
|
6433
|
+
// Wait for any ongoing operation to complete
|
|
6434
|
+
if (this._operationLock) await this._operationLock;
|
|
6435
|
+
if (!(this._state === RunnerState.STARTED || this._state === RunnerState.STARTING)) {
|
|
6436
|
+
if (this._state === RunnerState.STOPPED) throw new Error("[vitest-pool-runner]: Cannot start a stopped runner");
|
|
6437
|
+
// Create operation lock to prevent concurrent start/stop
|
|
6438
|
+
this._operationLock = createDefer();
|
|
6439
|
+
try {
|
|
6440
|
+
this._state = RunnerState.STARTING, await this.worker.start(), this.worker.on("error", this.emitWorkerError), this.worker.on("exit", this.emitUnexpectedExit), this.worker.on("message", this.emitWorkerMessage);
|
|
6441
|
+
const startPromise = this.withTimeout(this.waitForStart(), START_TIMEOUT);
|
|
6442
|
+
this.postMessage({
|
|
6443
|
+
type: "start",
|
|
6444
|
+
__vitest_worker_request__: true,
|
|
6445
|
+
options: { reportMemory: this.worker.reportMemory ?? false }
|
|
6446
|
+
}), await startPromise, this._state = RunnerState.STARTED;
|
|
6447
|
+
} catch (error) {
|
|
6448
|
+
throw this._state = RunnerState.IDLE, error;
|
|
6449
|
+
} finally {
|
|
6450
|
+
this._operationLock.resolve(), this._operationLock = null;
|
|
6451
|
+
}
|
|
6452
|
+
}
|
|
6453
|
+
}
|
|
6454
|
+
async stop() {
|
|
6455
|
+
// Wait for any ongoing operation to complete
|
|
6456
|
+
if (this._operationLock) await this._operationLock;
|
|
6457
|
+
if (!(this._state === RunnerState.STOPPED || this._state === RunnerState.STOPPING)) {
|
|
6458
|
+
if (this._state === RunnerState.IDLE) {
|
|
6459
|
+
this._state = RunnerState.STOPPED;
|
|
6460
|
+
return;
|
|
6461
|
+
}
|
|
6462
|
+
// Create operation lock to prevent concurrent start/stop
|
|
6463
|
+
this._operationLock = createDefer();
|
|
6464
|
+
try {
|
|
6465
|
+
this._state = RunnerState.STOPPING, this.worker.off("exit", this.emitUnexpectedExit), await this.withTimeout(new Promise((resolve) => {
|
|
6466
|
+
const onStop = (response) => {
|
|
6467
|
+
if (response.type === "stopped") {
|
|
6468
|
+
if (response.error) this.project.vitest.state.catchError(response.error, "Teardown Error");
|
|
6469
|
+
resolve(), this.off("message", onStop);
|
|
6470
|
+
}
|
|
6471
|
+
};
|
|
6472
|
+
this.on("message", onStop), this.postMessage({
|
|
6473
|
+
type: "stop",
|
|
6474
|
+
__vitest_worker_request__: true
|
|
6475
|
+
});
|
|
6476
|
+
}), STOP_TIMEOUT), this._eventEmitter.removeAllListeners(), this._rpc.$close(/* @__PURE__ */ new Error("[vitest-pool-runner]: Pending methods while closing rpc")), await this.worker.stop(), this._state = RunnerState.STOPPED;
|
|
6477
|
+
} catch (error) {
|
|
6478
|
+
throw this._state = RunnerState.STOPPED, error;
|
|
6479
|
+
} finally {
|
|
6480
|
+
this._operationLock.resolve(), this._operationLock = null;
|
|
6481
|
+
}
|
|
6482
|
+
}
|
|
6483
|
+
}
|
|
6484
|
+
on(event, callback) {
|
|
6485
|
+
this._eventEmitter.on(event, callback);
|
|
6486
|
+
}
|
|
6487
|
+
off(event, callback) {
|
|
6488
|
+
this._eventEmitter.off(event, callback);
|
|
6489
|
+
}
|
|
6490
|
+
emitWorkerError = (maybeError) => {
|
|
6491
|
+
const error = maybeError instanceof Error ? maybeError : new Error(String(maybeError));
|
|
6492
|
+
this._eventEmitter.emit("error", error);
|
|
6493
|
+
};
|
|
6494
|
+
emitWorkerMessage = (response) => {
|
|
6495
|
+
try {
|
|
6496
|
+
const message = this.worker.deserialize(response);
|
|
6497
|
+
if (typeof message === "object" && message != null && message.__vitest_worker_response__) this._eventEmitter.emit("message", message);
|
|
6498
|
+
else this._eventEmitter.emit("rpc", message);
|
|
6499
|
+
} catch (error) {
|
|
6500
|
+
this._eventEmitter.emit("error", error);
|
|
6501
|
+
}
|
|
6502
|
+
};
|
|
6503
|
+
emitUnexpectedExit = () => {
|
|
6504
|
+
const error = /* @__PURE__ */ new Error("Worker exited unexpectedly");
|
|
6505
|
+
this._eventEmitter.emit("error", error);
|
|
6506
|
+
};
|
|
6507
|
+
waitForStart() {
|
|
6508
|
+
return new Promise((resolve) => {
|
|
6509
|
+
const onStart = (message) => {
|
|
6510
|
+
if (message.type === "started") this.off("message", onStart), resolve();
|
|
6511
|
+
};
|
|
6512
|
+
this.on("message", onStart);
|
|
6513
|
+
});
|
|
6514
|
+
}
|
|
6515
|
+
withTimeout(promise, timeout) {
|
|
6516
|
+
return new Promise((resolve_, reject_) => {
|
|
6517
|
+
const timer = setTimeout(() => reject(/* @__PURE__ */ new Error("[vitest-pool-runner]: Timeout waiting for worker to respond")), timeout);
|
|
6518
|
+
function resolve(value) {
|
|
6519
|
+
clearTimeout(timer), resolve_(value);
|
|
6520
|
+
}
|
|
6521
|
+
function reject(error) {
|
|
6522
|
+
clearTimeout(timer), reject_(error);
|
|
6523
|
+
}
|
|
6524
|
+
promise.then(resolve, reject);
|
|
6525
|
+
});
|
|
6526
|
+
}
|
|
6527
|
+
}
|
|
6528
|
+
|
|
6529
|
+
const SIGKILL_TIMEOUT = 500;
|
|
6530
|
+
/** @experimental */
|
|
6531
|
+
class ForksPoolWorker {
|
|
6532
|
+
name = "forks";
|
|
6533
|
+
cacheFs = true;
|
|
6534
|
+
entrypoint;
|
|
6535
|
+
execArgv;
|
|
6536
|
+
env;
|
|
6537
|
+
_fork;
|
|
6538
|
+
constructor(options) {
|
|
6539
|
+
/** Loads {@link file://./../../../runtime/workers/forks.ts} */
|
|
6540
|
+
this.execArgv = options.execArgv, this.env = options.env, this.entrypoint = resolve$1(options.distPath, "workers/forks.js");
|
|
6541
|
+
}
|
|
6542
|
+
on(event, callback) {
|
|
6543
|
+
this.fork.on(event, callback);
|
|
6544
|
+
}
|
|
6545
|
+
off(event, callback) {
|
|
6546
|
+
this.fork.off(event, callback);
|
|
6547
|
+
}
|
|
6548
|
+
send(message) {
|
|
6549
|
+
if ("context" in message) message = {
|
|
6550
|
+
...message,
|
|
6551
|
+
context: {
|
|
6552
|
+
...message.context,
|
|
6553
|
+
config: wrapSerializableConfig(message.context.config)
|
|
6554
|
+
}
|
|
6555
|
+
};
|
|
6556
|
+
this.fork.send(v8.serialize(message));
|
|
6557
|
+
}
|
|
6558
|
+
async start() {
|
|
6559
|
+
this._fork ||= fork(this.entrypoint, [], {
|
|
6560
|
+
env: this.env,
|
|
6561
|
+
execArgv: this.execArgv
|
|
6562
|
+
});
|
|
6563
|
+
}
|
|
6564
|
+
async stop() {
|
|
6565
|
+
const fork = this.fork, waitForExit = new Promise((resolve) => {
|
|
6566
|
+
if (fork.exitCode != null) resolve();
|
|
6567
|
+
else fork.once("exit", resolve);
|
|
6568
|
+
}), sigkillTimeout = setTimeout(() => fork.kill("SIGKILL"), SIGKILL_TIMEOUT);
|
|
6569
|
+
fork.kill(), await waitForExit, clearTimeout(sigkillTimeout), this._fork = void 0;
|
|
6570
|
+
}
|
|
6571
|
+
deserialize(data) {
|
|
6572
|
+
try {
|
|
6573
|
+
return v8.deserialize(Buffer.from(data));
|
|
6574
|
+
} catch (error) {
|
|
6575
|
+
let stringified = "";
|
|
6576
|
+
try {
|
|
6577
|
+
stringified = `\nReceived value: ${JSON.stringify(data)}`;
|
|
6578
|
+
} catch {}
|
|
6579
|
+
throw new Error(`[vitest-pool]: Unexpected call to process.send(). Make sure your test cases are not interfering with process's channel.${stringified}`, { cause: error });
|
|
6580
|
+
}
|
|
6581
|
+
}
|
|
6582
|
+
get fork() {
|
|
6583
|
+
if (!this._fork) throw new Error(`The child process was torn down or never initialized. This is a bug in Vitest.`);
|
|
6584
|
+
return this._fork;
|
|
6585
|
+
}
|
|
6586
|
+
}
|
|
6587
|
+
/**
|
|
6588
|
+
* Prepares `SerializedConfig` for serialization, e.g. `node:v8.serialize`
|
|
6589
|
+
* - Unwrapping done in {@link file://./../../../runtime/workers/init-forks.ts}
|
|
6590
|
+
*/
|
|
6591
|
+
function wrapSerializableConfig(config) {
|
|
6592
|
+
let testNamePattern = config.testNamePattern, defines = config.defines;
|
|
6593
|
+
// v8 serialize does not support regex
|
|
6594
|
+
if (testNamePattern && typeof testNamePattern !== "string") testNamePattern = `$$vitest:${testNamePattern.toString()}`;
|
|
6595
|
+
// v8 serialize drops properties with undefined value
|
|
6596
|
+
if (defines) defines = {
|
|
6597
|
+
keys: Object.keys(defines),
|
|
6598
|
+
original: defines
|
|
6599
|
+
};
|
|
6600
|
+
return {
|
|
6601
|
+
...config,
|
|
6602
|
+
testNamePattern,
|
|
6603
|
+
defines
|
|
6604
|
+
};
|
|
6605
|
+
}
|
|
6606
|
+
|
|
6607
|
+
/** @experimental */
|
|
6608
|
+
class ThreadsPoolWorker {
|
|
6609
|
+
name = "threads";
|
|
6610
|
+
entrypoint;
|
|
6611
|
+
execArgv;
|
|
6612
|
+
env;
|
|
6613
|
+
_thread;
|
|
6614
|
+
constructor(options) {
|
|
6615
|
+
/** Loads {@link file://./../../../runtime/workers/threads.ts} */
|
|
6616
|
+
this.execArgv = options.execArgv, this.env = options.env, this.entrypoint = resolve$1(options.distPath, "workers/threads.js");
|
|
6617
|
+
}
|
|
6618
|
+
on(event, callback) {
|
|
6619
|
+
this.thread.on(event, callback);
|
|
6620
|
+
}
|
|
6621
|
+
off(event, callback) {
|
|
6622
|
+
this.thread.off(event, callback);
|
|
6623
|
+
}
|
|
6624
|
+
send(message) {
|
|
6625
|
+
this.thread.postMessage(message);
|
|
6626
|
+
}
|
|
6627
|
+
async start() {
|
|
6628
|
+
// This can be called multiple times if the runtime is shared.
|
|
6629
|
+
this._thread ||= new Worker(this.entrypoint, {
|
|
6630
|
+
env: this.env,
|
|
6631
|
+
execArgv: this.execArgv
|
|
6632
|
+
});
|
|
6633
|
+
}
|
|
6634
|
+
async stop() {
|
|
6635
|
+
await this.thread.terminate().then(() => {
|
|
6636
|
+
this._thread = void 0;
|
|
6637
|
+
});
|
|
6638
|
+
}
|
|
6639
|
+
deserialize(data) {
|
|
6640
|
+
return data;
|
|
6641
|
+
}
|
|
6642
|
+
get thread() {
|
|
6643
|
+
if (!this._thread) throw new Error(`The worker thread was torn down or never initialized. This is a bug in Vitest.`);
|
|
6644
|
+
return this._thread;
|
|
6645
|
+
}
|
|
6646
|
+
}
|
|
6647
|
+
|
|
6648
|
+
/** @experimental */
|
|
6649
|
+
class TypecheckPoolWorker {
|
|
6650
|
+
name = "typecheck";
|
|
6651
|
+
project;
|
|
6652
|
+
_eventEmitter = new EventEmitter$1();
|
|
6653
|
+
constructor(options) {
|
|
6654
|
+
this.project = options.project;
|
|
6655
|
+
}
|
|
6656
|
+
async start() {
|
|
6657
|
+
// noop, onMessage handles it
|
|
6658
|
+
}
|
|
6659
|
+
async stop() {
|
|
6660
|
+
// noop, onMessage handles it
|
|
6661
|
+
}
|
|
6662
|
+
canReuse() {
|
|
6663
|
+
return true;
|
|
6664
|
+
}
|
|
6665
|
+
send(message) {
|
|
6666
|
+
onMessage(message, this.project).then((response) => {
|
|
6667
|
+
if (response) this._eventEmitter.emit("message", response);
|
|
6668
|
+
});
|
|
6669
|
+
}
|
|
6670
|
+
on(event, callback) {
|
|
6671
|
+
this._eventEmitter.on(event, callback);
|
|
6672
|
+
}
|
|
6673
|
+
off(event, callback) {
|
|
6674
|
+
this._eventEmitter.on(event, callback);
|
|
6675
|
+
}
|
|
6676
|
+
deserialize(data) {
|
|
6677
|
+
return data;
|
|
6678
|
+
}
|
|
6679
|
+
}
|
|
6680
|
+
const __vitest_worker_response__ = true, runners = /* @__PURE__ */ new WeakMap();
|
|
6681
|
+
async function onMessage(message, project) {
|
|
6682
|
+
if (message?.__vitest_worker_request__ !== true) return;
|
|
6683
|
+
let runner = runners.get(project.vitest);
|
|
6684
|
+
if (!runner) runner = createRunner(project.vitest), runners.set(project.vitest, runner);
|
|
6685
|
+
let runPromise;
|
|
6686
|
+
switch (message.type) {
|
|
6687
|
+
case "start": return {
|
|
6688
|
+
type: "started",
|
|
6689
|
+
__vitest_worker_response__
|
|
6690
|
+
};
|
|
6691
|
+
case "run": return runPromise = runner.runTests(message.context.files, project).catch((error) => error), {
|
|
6692
|
+
type: "testfileFinished",
|
|
6693
|
+
error: await runPromise,
|
|
6694
|
+
__vitest_worker_response__
|
|
6695
|
+
};
|
|
6696
|
+
case "collect": return runPromise = runner.collectTests(message.context.files, project).catch((error) => error), {
|
|
6697
|
+
type: "testfileFinished",
|
|
6698
|
+
error: await runPromise,
|
|
6699
|
+
__vitest_worker_response__
|
|
6700
|
+
};
|
|
6701
|
+
case "stop": return await runPromise, await project.typechecker?.stop(), {
|
|
6702
|
+
type: "stopped",
|
|
6703
|
+
__vitest_worker_response__
|
|
6704
|
+
};
|
|
6705
|
+
}
|
|
6706
|
+
throw new Error(`Unexpected message ${JSON.stringify(message, null, 2)}`);
|
|
6707
|
+
}
|
|
6708
|
+
function createRunner(vitest) {
|
|
6709
|
+
const promisesMap = /* @__PURE__ */ new WeakMap(), rerunTriggered = /* @__PURE__ */ new WeakSet();
|
|
6710
|
+
async function onParseEnd(project, { files, sourceErrors }) {
|
|
6711
|
+
const checker = project.typechecker, { packs, events } = checker.getTestPacksAndEvents();
|
|
6712
|
+
if (await vitest._testRun.updated(packs, events), !project.config.typecheck.ignoreSourceErrors) sourceErrors.forEach((error) => vitest.state.catchError(error, "Unhandled Source Error"));
|
|
6713
|
+
if (!hasFailed(files) && !sourceErrors.length && checker.getExitCode()) {
|
|
6714
|
+
const error = new Error(checker.getOutput());
|
|
6715
|
+
error.stack = "", vitest.state.catchError(error, "Typecheck Error");
|
|
6716
|
+
}
|
|
6717
|
+
// triggered by TSC watcher, not Vitest watcher, so we need to emulate what Vitest does in this case
|
|
6718
|
+
if (promisesMap.get(project)?.resolve(), rerunTriggered.delete(project), vitest.config.watch && !vitest.runningPromise) {
|
|
6719
|
+
const modules = files.map((file) => vitest.state.getReportedEntity(file)).filter((e) => e?.type === "module"), state = vitest.isCancelling ? "interrupted" : modules.some((m) => !m.ok()) ? "failed" : "passed";
|
|
6720
|
+
await vitest.report("onTestRunEnd", modules, [], state), await vitest.report("onWatcherStart", files, [...project.config.typecheck.ignoreSourceErrors ? [] : sourceErrors, ...vitest.state.getUnhandledErrors()]);
|
|
6721
|
+
}
|
|
6722
|
+
}
|
|
6723
|
+
async function createWorkspaceTypechecker(project, files) {
|
|
6724
|
+
const checker = project.typechecker ?? new Typechecker(project);
|
|
6725
|
+
return project.typechecker ? checker : (project.typechecker = checker, checker.setFiles(files), checker.onParseStart(async () => {
|
|
6726
|
+
const files = checker.getTestFiles();
|
|
6727
|
+
for (const file of files) await vitest._testRun.enqueued(project, file);
|
|
6728
|
+
await vitest._testRun.collected(project, files);
|
|
6729
|
+
}), checker.onParseEnd((result) => onParseEnd(project, result)), checker.onWatcherRerun(async () => {
|
|
6730
|
+
if (rerunTriggered.add(project), !vitest.runningPromise) vitest.state.clearErrors(), await vitest.report("onWatcherRerun", files, "File change detected. Triggering rerun.");
|
|
6731
|
+
await checker.collectTests();
|
|
6732
|
+
const testFiles = checker.getTestFiles();
|
|
6733
|
+
for (const file of testFiles) await vitest._testRun.enqueued(project, file);
|
|
6734
|
+
await vitest._testRun.collected(project, testFiles);
|
|
6735
|
+
const { packs, events } = checker.getTestPacksAndEvents();
|
|
6736
|
+
await vitest._testRun.updated(packs, events);
|
|
6737
|
+
}), checker);
|
|
6738
|
+
}
|
|
6739
|
+
async function startTypechecker(project, files) {
|
|
6740
|
+
if (project.typechecker) return;
|
|
6741
|
+
const checker = await createWorkspaceTypechecker(project, files);
|
|
6742
|
+
await checker.collectTests(), await checker.start();
|
|
6743
|
+
}
|
|
6744
|
+
async function collectTests(specs, project) {
|
|
6745
|
+
const files = specs.map((spec) => spec.filepath), checker = await createWorkspaceTypechecker(project, files);
|
|
6746
|
+
checker.setFiles(files), await checker.collectTests();
|
|
6747
|
+
const testFiles = checker.getTestFiles();
|
|
6748
|
+
vitest.state.collectFiles(project, testFiles);
|
|
6749
|
+
}
|
|
6750
|
+
async function runTests(specs, project) {
|
|
6751
|
+
const promises = [], files = specs.map((spec) => spec.filepath), promise = createDefer(), triggered = await new Promise((resolve) => {
|
|
6752
|
+
const _i = setInterval(() => {
|
|
6753
|
+
if (!project.typechecker || rerunTriggered.has(project)) resolve(true), clearInterval(_i);
|
|
6754
|
+
});
|
|
6755
|
+
setTimeout(() => {
|
|
6756
|
+
resolve(false), clearInterval(_i);
|
|
6757
|
+
}, 500).unref();
|
|
6758
|
+
});
|
|
6759
|
+
if (project.typechecker && !triggered) {
|
|
6760
|
+
const testFiles = project.typechecker.getTestFiles();
|
|
6761
|
+
for (const file of testFiles) await vitest._testRun.enqueued(project, file);
|
|
6762
|
+
await vitest._testRun.collected(project, testFiles), await onParseEnd(project, project.typechecker.getResult());
|
|
6763
|
+
}
|
|
6764
|
+
promises.push(promise), promisesMap.set(project, promise), promises.push(startTypechecker(project, files)), await Promise.all(promises);
|
|
6765
|
+
}
|
|
6766
|
+
return {
|
|
6767
|
+
runTests,
|
|
6768
|
+
collectTests
|
|
6769
|
+
};
|
|
6770
|
+
}
|
|
6771
|
+
|
|
6772
|
+
/** @experimental */
|
|
6773
|
+
class VmForksPoolWorker extends ForksPoolWorker {
|
|
6774
|
+
name = "vmForks";
|
|
6775
|
+
reportMemory = true;
|
|
6776
|
+
entrypoint;
|
|
6777
|
+
constructor(options) {
|
|
6778
|
+
/** Loads {@link file://./../../../runtime/workers/vmForks.ts} */
|
|
6779
|
+
super(options), this.execArgv.push("--experimental-vm-modules"), this.entrypoint = resolve$1(options.distPath, "workers/vmForks.js");
|
|
6780
|
+
}
|
|
6781
|
+
}
|
|
6782
|
+
|
|
6783
|
+
/** @experimental */
|
|
6784
|
+
class VmThreadsPoolWorker extends ThreadsPoolWorker {
|
|
6785
|
+
name = "vmThreads";
|
|
6786
|
+
reportMemory = true;
|
|
6787
|
+
entrypoint;
|
|
6788
|
+
constructor(options) {
|
|
6789
|
+
/** Loads {@link file://./../../../runtime/workers/vmThreads.ts} */
|
|
6790
|
+
super(options), this.execArgv.push("--experimental-vm-modules"), this.entrypoint = resolve$1(options.distPath, "workers/vmThreads.js");
|
|
6791
|
+
}
|
|
6792
|
+
}
|
|
6793
|
+
|
|
6794
|
+
const WORKER_START_TIMEOUT = 5e3;
|
|
6795
|
+
class Pool {
|
|
6796
|
+
maxWorkers = 0;
|
|
6797
|
+
workerIds = /* @__PURE__ */ new Map();
|
|
6798
|
+
queue = [];
|
|
6799
|
+
activeTasks = [];
|
|
6800
|
+
sharedRunners = [];
|
|
6801
|
+
exitPromises = [];
|
|
6802
|
+
_isCancelling = false;
|
|
6803
|
+
constructor(options, logger) {
|
|
6804
|
+
this.options = options, this.logger = logger;
|
|
6805
|
+
}
|
|
6806
|
+
setMaxWorkers(maxWorkers) {
|
|
6807
|
+
this.maxWorkers = maxWorkers, this.workerIds = new Map(Array.from({ length: maxWorkers }).fill(0).map((_, i) => [i + 1, true]));
|
|
6808
|
+
}
|
|
6809
|
+
async run(task, method) {
|
|
6810
|
+
// Prevent new tasks from being queued during cancellation
|
|
6811
|
+
if (this._isCancelling) throw new Error("[vitest-pool]: Cannot run tasks while pool is cancelling");
|
|
6812
|
+
// Every runner related failure should make this promise reject so that it's picked by pool.
|
|
6813
|
+
// This resolver is used to make the error handling in recursive queue easier.
|
|
6814
|
+
const testFinish = withResolvers();
|
|
6815
|
+
this.queue.push({
|
|
6816
|
+
task,
|
|
6817
|
+
resolver: testFinish,
|
|
6818
|
+
method
|
|
6819
|
+
}), this.schedule(), await testFinish.promise;
|
|
6820
|
+
}
|
|
6821
|
+
async schedule() {
|
|
6822
|
+
if (this.queue.length === 0 || this.activeTasks.length >= this.maxWorkers) return;
|
|
6823
|
+
const { task, resolver, method } = this.queue.shift();
|
|
6824
|
+
try {
|
|
6825
|
+
let isMemoryLimitReached = false;
|
|
6826
|
+
const runner = this.getPoolRunner(task, method), activeTask = {
|
|
6827
|
+
task,
|
|
6828
|
+
resolver,
|
|
6829
|
+
method,
|
|
6830
|
+
cancelTask
|
|
6831
|
+
};
|
|
6832
|
+
this.activeTasks.push(activeTask), runner.on("error", (error) => {
|
|
6833
|
+
resolver.reject(new Error(`[vitest-pool]: Worker ${task.worker} emitted error.`, { cause: error }));
|
|
6834
|
+
});
|
|
6835
|
+
async function cancelTask() {
|
|
6836
|
+
await runner.stop(), resolver.reject(/* @__PURE__ */ new Error("Cancelled"));
|
|
6837
|
+
}
|
|
6838
|
+
const onFinished = (message) => {
|
|
6839
|
+
if (message?.__vitest_worker_response__ && message.type === "testfileFinished") {
|
|
6840
|
+
if (task.memoryLimit && message.usedMemory) isMemoryLimitReached = message.usedMemory >= task.memoryLimit;
|
|
6841
|
+
if (message.error) this.options.state.catchError(message.error, "Test Run Error");
|
|
6842
|
+
runner.off("message", onFinished), resolver.resolve();
|
|
6843
|
+
}
|
|
6844
|
+
};
|
|
6845
|
+
if (runner.on("message", onFinished), !runner.isStarted) {
|
|
6846
|
+
const id = setTimeout(() => resolver.reject(/* @__PURE__ */ new Error(`[vitest-pool]: Timeout starting ${task.worker} runner.`)), WORKER_START_TIMEOUT);
|
|
6847
|
+
await runner.start().finally(() => clearTimeout(id));
|
|
6848
|
+
}
|
|
6849
|
+
const poolId = runner.poolId ?? this.getWorkerId();
|
|
6850
|
+
runner.poolId = poolId, runner.postMessage({
|
|
6851
|
+
__vitest_worker_request__: true,
|
|
6852
|
+
type: method,
|
|
6853
|
+
context: task.context,
|
|
6854
|
+
poolId
|
|
6855
|
+
}), await resolver.promise;
|
|
6856
|
+
const index = this.activeTasks.indexOf(activeTask);
|
|
6857
|
+
if (index !== -1) this.activeTasks.splice(index, 1);
|
|
6858
|
+
if (!task.isolate && !isMemoryLimitReached && this.queue[0]?.task.isolate === false && isEqualRunner(runner, this.queue[0].task)) return this.sharedRunners.push(runner), this.schedule();
|
|
6859
|
+
// Runner terminations are started but not awaited until the end of full run.
|
|
6860
|
+
// Runner termination can also already start from task cancellation.
|
|
6861
|
+
if (!runner.isTerminated) {
|
|
6862
|
+
const id = setTimeout(() => this.logger.error(`[vitest-pool]: Timeout terminating ${task.worker} worker for test files ${formatFiles(task)}.`), this.options.teardownTimeout);
|
|
6863
|
+
this.exitPromises.push(runner.stop().then(() => clearTimeout(id)).catch((error) => this.logger.error(`[vitest-pool]: Failed to terminate ${task.worker} worker for test files ${formatFiles(task)}.`, error)));
|
|
6864
|
+
}
|
|
6865
|
+
this.freeWorkerId(poolId);
|
|
6866
|
+
}
|
|
6867
|
+
// This is mostly to avoid zombie workers when/if Vitest internals run into errors
|
|
6868
|
+
catch (error) {
|
|
6869
|
+
return resolver.reject(error);
|
|
6870
|
+
}
|
|
6871
|
+
return this.schedule();
|
|
6872
|
+
}
|
|
6873
|
+
async cancel() {
|
|
6874
|
+
// Set flag to prevent new tasks from being queued
|
|
6875
|
+
this._isCancelling = true;
|
|
6876
|
+
const pendingTasks = this.queue.splice(0);
|
|
6877
|
+
if (pendingTasks.length) {
|
|
6878
|
+
const error = /* @__PURE__ */ new Error("Cancelled");
|
|
6879
|
+
pendingTasks.forEach((task) => task.resolver.reject(error));
|
|
6880
|
+
}
|
|
6881
|
+
const activeTasks = this.activeTasks.splice(0);
|
|
6882
|
+
await Promise.all(activeTasks.map((task) => task.cancelTask()));
|
|
6883
|
+
const sharedRunners = this.sharedRunners.splice(0);
|
|
6884
|
+
// Reset flag after cancellation completes
|
|
6885
|
+
await Promise.all(sharedRunners.map((runner) => runner.stop())), await Promise.all(this.exitPromises.splice(0)), this.workerIds.forEach((_, id) => this.freeWorkerId(id)), this._isCancelling = false;
|
|
6886
|
+
}
|
|
6887
|
+
async close() {
|
|
6888
|
+
await this.cancel();
|
|
6889
|
+
}
|
|
6890
|
+
getPoolRunner(task, method) {
|
|
6891
|
+
if (task.isolate === false) {
|
|
6892
|
+
const index = this.sharedRunners.findIndex((runner) => isEqualRunner(runner, task));
|
|
6893
|
+
if (index !== -1) return this.sharedRunners.splice(index, 1)[0];
|
|
6894
|
+
}
|
|
6895
|
+
const options = {
|
|
6896
|
+
distPath: this.options.distPath,
|
|
6897
|
+
project: task.project,
|
|
6898
|
+
method,
|
|
6899
|
+
environment: task.context.environment.name,
|
|
6900
|
+
env: task.env,
|
|
6901
|
+
execArgv: task.execArgv
|
|
6902
|
+
};
|
|
6903
|
+
switch (task.worker) {
|
|
6904
|
+
case "forks": return new PoolRunner(options, new ForksPoolWorker(options));
|
|
6905
|
+
case "vmForks": return new PoolRunner(options, new VmForksPoolWorker(options));
|
|
6906
|
+
case "threads": return new PoolRunner(options, new ThreadsPoolWorker(options));
|
|
6907
|
+
case "vmThreads": return new PoolRunner(options, new VmThreadsPoolWorker(options));
|
|
6908
|
+
case "typescript": return new PoolRunner(options, new TypecheckPoolWorker(options));
|
|
6909
|
+
}
|
|
6910
|
+
const customPool = task.project.config.poolRunner;
|
|
6911
|
+
if (customPool != null && customPool.name === task.worker) return new PoolRunner(options, customPool.createPoolWorker(options));
|
|
6912
|
+
throw new Error(`Runner ${task.worker} is not supported. Test files: ${formatFiles(task)}.`);
|
|
6913
|
+
}
|
|
6914
|
+
getWorkerId() {
|
|
6915
|
+
let workerId = 0;
|
|
6916
|
+
return this.workerIds.forEach((state, id) => {
|
|
6917
|
+
if (state && !workerId) workerId = id, this.workerIds.set(id, false);
|
|
6918
|
+
}), workerId;
|
|
6919
|
+
}
|
|
6920
|
+
freeWorkerId(id) {
|
|
6921
|
+
this.workerIds.set(id, true);
|
|
6922
|
+
}
|
|
6923
|
+
}
|
|
6924
|
+
function withResolvers() {
|
|
6925
|
+
let resolve = () => {}, reject = (_error) => {};
|
|
6926
|
+
const promise = new Promise((res, rej) => {
|
|
6927
|
+
resolve = res, reject = rej;
|
|
6928
|
+
});
|
|
6929
|
+
return {
|
|
6930
|
+
resolve,
|
|
6931
|
+
reject,
|
|
6932
|
+
promise
|
|
6933
|
+
};
|
|
6934
|
+
}
|
|
6935
|
+
function formatFiles(task) {
|
|
6936
|
+
return task.context.files.map((file) => file.filepath).join(", ");
|
|
6937
|
+
}
|
|
6938
|
+
function isEqualRunner(runner, task) {
|
|
6939
|
+
if (task.isolate) throw new Error("Isolated tasks should not share runners");
|
|
6940
|
+
return runner.worker.name === task.worker && runner.project === task.project && runner.environment === task.context.environment.name && (!runner.worker.canReuse || runner.worker.canReuse(task));
|
|
6941
|
+
}
|
|
6942
|
+
|
|
6943
|
+
const suppressWarningsPath = resolve(rootDir, "./suppress-warnings.cjs");
|
|
6944
|
+
function getFilePoolName(project) {
|
|
6945
|
+
return project.config.browser.enabled ? "browser" : project.config.pool;
|
|
6946
|
+
}
|
|
6947
|
+
function createPool(ctx) {
|
|
6948
|
+
const pool = new Pool({
|
|
6949
|
+
distPath: ctx.distPath,
|
|
6950
|
+
teardownTimeout: ctx.config.teardownTimeout,
|
|
6951
|
+
state: ctx.state
|
|
6952
|
+
}, ctx.logger), options = resolveOptions(ctx), Sequencer = ctx.config.sequence.sequencer, sequencer = new Sequencer(ctx);
|
|
6953
|
+
let browserPool;
|
|
6954
|
+
async function executeTests(method, specs, invalidates) {
|
|
6955
|
+
if (ctx.onCancel(() => pool.cancel()), ctx.config.shard) {
|
|
6956
|
+
if (!ctx.config.passWithNoTests && ctx.config.shard.count > specs.length) throw new Error(`--shard <count> must be a smaller than count of test files. Resolved ${specs.length} test files for --shard=${ctx.config.shard.index}/${ctx.config.shard.count}.`);
|
|
6957
|
+
specs = await sequencer.shard(Array.from(specs));
|
|
6958
|
+
}
|
|
6959
|
+
const taskGroups = [];
|
|
6960
|
+
let workerId = 0;
|
|
6961
|
+
const sorted = await sequencer.sort(specs), environments = await getSpecificationsEnvironments(specs), groups = groupSpecs(sorted);
|
|
6962
|
+
for (const group of groups) {
|
|
6963
|
+
if (!group) continue;
|
|
6964
|
+
const taskGroup = [], browserSpecs = [];
|
|
6965
|
+
taskGroups.push({
|
|
6966
|
+
tasks: taskGroup,
|
|
6967
|
+
maxWorkers: group.maxWorkers,
|
|
6968
|
+
browserSpecs
|
|
6969
|
+
});
|
|
6970
|
+
for (const specs of group.specs) {
|
|
6971
|
+
const { project, pool } = specs[0];
|
|
6972
|
+
if (pool === "browser") {
|
|
6973
|
+
browserSpecs.push(...specs);
|
|
6974
|
+
continue;
|
|
6975
|
+
}
|
|
6976
|
+
const environment = environments.get(specs[0]);
|
|
6977
|
+
if (!environment) throw new Error(`Cannot find the environment. This is a bug in Vitest.`);
|
|
6978
|
+
taskGroup.push({
|
|
6979
|
+
context: {
|
|
6980
|
+
pool,
|
|
6981
|
+
config: project.serializedConfig,
|
|
6982
|
+
files: specs.map((spec) => ({
|
|
6983
|
+
filepath: spec.moduleId,
|
|
6984
|
+
testLocations: spec.testLines
|
|
6985
|
+
})),
|
|
6986
|
+
invalidates,
|
|
6987
|
+
environment,
|
|
6988
|
+
projectName: project.name,
|
|
6989
|
+
providedContext: project.getProvidedContext(),
|
|
6990
|
+
workerId: workerId++
|
|
6991
|
+
},
|
|
6992
|
+
project,
|
|
6993
|
+
env: {
|
|
6994
|
+
...options.env,
|
|
6995
|
+
...project.config.env
|
|
6996
|
+
},
|
|
6997
|
+
execArgv: [...options.execArgv, ...project.config.execArgv],
|
|
6998
|
+
worker: pool,
|
|
6999
|
+
isolate: project.config.isolate,
|
|
7000
|
+
memoryLimit: getMemoryLimit(ctx.config, pool) ?? null
|
|
7001
|
+
});
|
|
7002
|
+
}
|
|
7003
|
+
}
|
|
7004
|
+
const results = [];
|
|
7005
|
+
for (const { tasks, browserSpecs, maxWorkers } of taskGroups) {
|
|
7006
|
+
pool.setMaxWorkers(maxWorkers);
|
|
7007
|
+
const promises = tasks.map(async (task) => {
|
|
7008
|
+
if (ctx.isCancelling) return ctx.state.cancelFiles(task.context.files, task.project);
|
|
7009
|
+
try {
|
|
7010
|
+
await pool.run(task, method);
|
|
7011
|
+
} catch (error) {
|
|
7012
|
+
// Intentionally cancelled
|
|
7013
|
+
if (ctx.isCancelling && error instanceof Error && error.message === "Cancelled") ctx.state.cancelFiles(task.context.files, task.project);
|
|
7014
|
+
else throw error;
|
|
7015
|
+
}
|
|
7016
|
+
});
|
|
7017
|
+
if (browserSpecs.length) if (browserPool ??= createBrowserPool(ctx), method === "collect") promises.push(browserPool.collectTests(browserSpecs));
|
|
7018
|
+
else promises.push(browserPool.runTests(browserSpecs));
|
|
7019
|
+
const groupResults = await Promise.allSettled(promises);
|
|
7020
|
+
results.push(...groupResults);
|
|
7021
|
+
}
|
|
7022
|
+
const errors = results.filter((result) => result.status === "rejected").map((result) => result.reason);
|
|
7023
|
+
if (errors.length > 0) throw new AggregateError(errors, "Errors occurred while running tests. For more information, see serialized error.");
|
|
7024
|
+
}
|
|
7025
|
+
return {
|
|
7026
|
+
name: "default",
|
|
7027
|
+
runTests: (files, invalidates) => executeTests("run", files, invalidates),
|
|
7028
|
+
collectTests: (files, invalidates) => executeTests("collect", files, invalidates),
|
|
7029
|
+
async close() {
|
|
7030
|
+
await Promise.all([pool.close(), browserPool?.close?.()]);
|
|
7031
|
+
}
|
|
7032
|
+
};
|
|
7033
|
+
}
|
|
7034
|
+
function resolveOptions(ctx) {
|
|
7035
|
+
// in addition to resolve.conditions Vite also adds production/development,
|
|
7036
|
+
// see: https://github.com/vitejs/vite/blob/af2aa09575229462635b7cbb6d248ca853057ba2/packages/vite/src/node/plugins/resolve.ts#L1056-L1080
|
|
7037
|
+
const viteMajor = Number(version.split(".")[0]), conditions = [...new Set(viteMajor >= 6 ? ctx.vite.config.ssr.resolve?.conditions ?? [] : [
|
|
7038
|
+
"production",
|
|
7039
|
+
"development",
|
|
7040
|
+
...ctx.vite.config.resolve.conditions
|
|
7041
|
+
])].filter((condition) => {
|
|
7042
|
+
return condition === "production" ? ctx.vite.config.isProduction : condition === "development" ? !ctx.vite.config.isProduction : true;
|
|
7043
|
+
}).map((condition) => {
|
|
7044
|
+
return viteMajor >= 6 && condition === "development|production" ? ctx.vite.config.isProduction ? "production" : "development" : condition;
|
|
7045
|
+
}).flatMap((c) => ["--conditions", c]), options = {
|
|
7046
|
+
execArgv: [
|
|
7047
|
+
...process.execArgv.filter((execArg) => execArg.startsWith("--cpu-prof") || execArg.startsWith("--heap-prof") || execArg.startsWith("--diagnostic-dir")),
|
|
7048
|
+
...conditions,
|
|
7049
|
+
"--experimental-import-meta-resolve",
|
|
7050
|
+
"--require",
|
|
7051
|
+
suppressWarningsPath
|
|
7052
|
+
],
|
|
7053
|
+
env: {
|
|
7054
|
+
TEST: "true",
|
|
7055
|
+
VITEST: "true",
|
|
7056
|
+
NODE_ENV: process.env.NODE_ENV || "test",
|
|
7057
|
+
VITEST_MODE: ctx.config.watch ? "WATCH" : "RUN",
|
|
7058
|
+
FORCE_TTY: isatty(1) ? "true" : "",
|
|
7059
|
+
...process.env,
|
|
7060
|
+
...ctx.config.env
|
|
7061
|
+
}
|
|
7062
|
+
};
|
|
7063
|
+
// env are case-insensitive on Windows, but spawned processes don't support it
|
|
7064
|
+
if (isWindows) for (const name in options.env) options.env[name.toUpperCase()] = options.env[name];
|
|
7065
|
+
return options;
|
|
7066
|
+
}
|
|
7067
|
+
function resolveMaxWorkers(project) {
|
|
7068
|
+
if (project.config.maxWorkers) return project.config.maxWorkers;
|
|
7069
|
+
if (project.vitest.config.maxWorkers) return project.vitest.config.maxWorkers;
|
|
7070
|
+
const numCpus = typeof nodeos.availableParallelism === "function" ? nodeos.availableParallelism() : nodeos.cpus().length;
|
|
7071
|
+
return project.vitest.config.watch ? Math.max(Math.floor(numCpus / 2), 1) : Math.max(numCpus - 1, 1);
|
|
7072
|
+
}
|
|
7073
|
+
function getMemoryLimit(config, pool) {
|
|
7074
|
+
if (pool !== "vmForks" && pool !== "vmThreads") return null;
|
|
7075
|
+
const memory = nodeos.totalmem(), limit = getWorkerMemoryLimit(config);
|
|
7076
|
+
// just ignore "memoryLimit" value because we cannot detect memory limit
|
|
7077
|
+
return typeof memory === "number" ? stringToBytes(limit, config.watch ? memory / 2 : memory) : typeof limit === "number" && limit > 1 || typeof limit === "string" && limit.at(-1) !== "%" ? stringToBytes(limit) : null;
|
|
7078
|
+
}
|
|
7079
|
+
function groupSpecs(specs) {
|
|
7080
|
+
const groups = [], sequential = {
|
|
7081
|
+
specs: [],
|
|
7082
|
+
maxWorkers: 1
|
|
7083
|
+
}, typechecks = {};
|
|
7084
|
+
specs.forEach((spec) => {
|
|
7085
|
+
if (spec.pool === "typescript") {
|
|
7086
|
+
typechecks[spec.project.name] ||= [], typechecks[spec.project.name].push(spec);
|
|
7087
|
+
return;
|
|
7088
|
+
}
|
|
7089
|
+
const order = spec.project.config.sequence.groupOrder;
|
|
7090
|
+
// Files that have disabled parallelism and default groupId are set into their own group
|
|
7091
|
+
if (order === 0 && spec.project.config.fileParallelism === false) return sequential.specs.push([spec]);
|
|
7092
|
+
const maxWorkers = resolveMaxWorkers(spec.project);
|
|
7093
|
+
// Multiple projects with different maxWorkers but same groupId
|
|
7094
|
+
if (groups[order] ||= {
|
|
7095
|
+
specs: [],
|
|
7096
|
+
maxWorkers
|
|
7097
|
+
}, groups[order].maxWorkers !== maxWorkers) {
|
|
7098
|
+
const last = groups[order].specs.at(-1)?.at(-1)?.project.name;
|
|
7099
|
+
throw new Error(`Projects "${last}" and "${spec.project.name}" have different 'maxWorkers' but same 'sequence.groupId'.\nProvide unique 'sequence.groupId' for them.`);
|
|
7100
|
+
}
|
|
7101
|
+
groups[order].specs.push([spec]);
|
|
7102
|
+
});
|
|
7103
|
+
let order = Math.max(0, ...groups.keys()) + 1;
|
|
7104
|
+
for (const projectName in typechecks) {
|
|
7105
|
+
const maxWorkers = resolveMaxWorkers(typechecks[projectName][0].project), previous = groups[order - 1];
|
|
7106
|
+
if (previous && previous.typecheck && maxWorkers !== previous.maxWorkers) order += 1;
|
|
7107
|
+
groups[order] ||= {
|
|
7108
|
+
specs: [],
|
|
7109
|
+
maxWorkers,
|
|
7110
|
+
typecheck: true
|
|
7111
|
+
}, groups[order].specs.push(typechecks[projectName]);
|
|
7112
|
+
}
|
|
7113
|
+
if (sequential.specs.length) groups.push(sequential);
|
|
7114
|
+
return groups;
|
|
7115
|
+
}
|
|
7116
|
+
|
|
5869
7117
|
function serializeConfig(project) {
|
|
5870
|
-
const { config, globalConfig } = project, viteConfig = project._vite?.config, optimizer = config.deps?.optimizer || {}
|
|
7118
|
+
const { config, globalConfig } = project, viteConfig = project._vite?.config, optimizer = config.deps?.optimizer || {};
|
|
5871
7119
|
return {
|
|
5872
7120
|
environmentOptions: config.environmentOptions,
|
|
5873
7121
|
mode: config.mode,
|
|
5874
7122
|
isolate: config.isolate,
|
|
7123
|
+
fileParallelism: config.fileParallelism,
|
|
7124
|
+
maxWorkers: config.maxWorkers,
|
|
5875
7125
|
base: config.base,
|
|
5876
7126
|
logHeapUsage: config.logHeapUsage,
|
|
5877
7127
|
runner: config.runner,
|
|
@@ -5911,18 +7161,6 @@ function serializeConfig(project) {
|
|
|
5911
7161
|
};
|
|
5912
7162
|
})(config.coverage),
|
|
5913
7163
|
fakeTimers: config.fakeTimers,
|
|
5914
|
-
poolOptions: {
|
|
5915
|
-
forks: {
|
|
5916
|
-
singleFork: poolOptions?.forks?.singleFork ?? globalConfig.poolOptions?.forks?.singleFork ?? false,
|
|
5917
|
-
isolate: poolOptions?.forks?.isolate ?? isolate ?? globalConfig.poolOptions?.forks?.isolate ?? true
|
|
5918
|
-
},
|
|
5919
|
-
threads: {
|
|
5920
|
-
singleThread: poolOptions?.threads?.singleThread ?? globalConfig.poolOptions?.threads?.singleThread ?? false,
|
|
5921
|
-
isolate: poolOptions?.threads?.isolate ?? isolate ?? globalConfig.poolOptions?.threads?.isolate ?? true
|
|
5922
|
-
},
|
|
5923
|
-
vmThreads: { singleThread: poolOptions?.vmThreads?.singleThread ?? globalConfig.poolOptions?.vmThreads?.singleThread ?? false },
|
|
5924
|
-
vmForks: { singleFork: poolOptions?.vmForks?.singleFork ?? globalConfig.poolOptions?.vmForks?.singleFork ?? false }
|
|
5925
|
-
},
|
|
5926
7164
|
deps: {
|
|
5927
7165
|
web: config.deps.web || {},
|
|
5928
7166
|
optimizer: Object.entries(optimizer).reduce((acc, [name, option]) => {
|
|
@@ -5954,6 +7192,7 @@ function serializeConfig(project) {
|
|
|
5954
7192
|
...config.env
|
|
5955
7193
|
},
|
|
5956
7194
|
browser: ((browser) => {
|
|
7195
|
+
const provider = project.browser?.provider;
|
|
5957
7196
|
return {
|
|
5958
7197
|
name: browser.name,
|
|
5959
7198
|
headless: browser.headless,
|
|
@@ -5963,8 +7202,9 @@ function serializeConfig(project) {
|
|
|
5963
7202
|
viewport: browser.viewport,
|
|
5964
7203
|
screenshotFailures: browser.screenshotFailures,
|
|
5965
7204
|
locators: { testIdAttribute: browser.locators.testIdAttribute },
|
|
5966
|
-
providerOptions:
|
|
5967
|
-
trackUnhandledErrors: browser.trackUnhandledErrors ?? true
|
|
7205
|
+
providerOptions: provider?.name === "playwright" ? { actionTimeout: provider?.options?.actionTimeout } : {},
|
|
7206
|
+
trackUnhandledErrors: browser.trackUnhandledErrors ?? true,
|
|
7207
|
+
trace: browser.trace.mode
|
|
5968
7208
|
};
|
|
5969
7209
|
})(config.browser),
|
|
5970
7210
|
standalone: config.standalone,
|
|
@@ -6500,7 +7740,7 @@ function MetaEnvReplacerPlugin() {
|
|
|
6500
7740
|
transform(code, id) {
|
|
6501
7741
|
if (!/\bimport\.meta\.env\b/.test(code)) return null;
|
|
6502
7742
|
let s = null;
|
|
6503
|
-
const
|
|
7743
|
+
const envs = stripLiteral(code).matchAll(/\bimport\.meta\.env\b/g);
|
|
6504
7744
|
for (const env of envs) {
|
|
6505
7745
|
s ||= new MagicString(code);
|
|
6506
7746
|
const startIndex = env.index, endIndex = startIndex + env[0].length;
|
|
@@ -6632,9 +7872,7 @@ function isInline(id) {
|
|
|
6632
7872
|
return cssInlineRE.test(id);
|
|
6633
7873
|
}
|
|
6634
7874
|
function getCSSModuleProxyReturn(strategy, filename) {
|
|
6635
|
-
|
|
6636
|
-
const hash = generateCssFilenameHash(filename);
|
|
6637
|
-
return `\`_\${style}_${hash}\``;
|
|
7875
|
+
return strategy === "non-scoped" ? "style" : `\`_\${style}_${generateCssFilenameHash(filename)}\``;
|
|
6638
7876
|
}
|
|
6639
7877
|
function CSSEnablerPlugin(ctx) {
|
|
6640
7878
|
const shouldProcessCSS = (id) => {
|
|
@@ -6656,12 +7894,12 @@ function CSSEnablerPlugin(ctx) {
|
|
|
6656
7894
|
// return proxy for css modules, so that imported module has names:
|
|
6657
7895
|
// styles.foo returns a "foo" instead of "undefined"
|
|
6658
7896
|
// we don't use code content to generate hash for "scoped", because it's empty
|
|
6659
|
-
const scopeStrategy = typeof ctx.config.css !== "boolean" && ctx.config.css.modules?.classNameStrategy || "stable"
|
|
7897
|
+
const scopeStrategy = typeof ctx.config.css !== "boolean" && ctx.config.css.modules?.classNameStrategy || "stable";
|
|
7898
|
+
return { code: `export default new Proxy(Object.create(null), {
|
|
6660
7899
|
get(_, style) {
|
|
6661
|
-
return ${
|
|
7900
|
+
return ${getCSSModuleProxyReturn(scopeStrategy, relative(ctx.config.root, id))};
|
|
6662
7901
|
},
|
|
6663
|
-
})
|
|
6664
|
-
return { code };
|
|
7902
|
+
})` };
|
|
6665
7903
|
}
|
|
6666
7904
|
return { code: "export default \"\"" };
|
|
6667
7905
|
}
|
|
@@ -6776,12 +8014,7 @@ function getDefaultResolveOptions() {
|
|
|
6776
8014
|
};
|
|
6777
8015
|
}
|
|
6778
8016
|
function getDefaultServerConditions() {
|
|
6779
|
-
|
|
6780
|
-
if (viteMajor >= 6) {
|
|
6781
|
-
const conditions = vite.defaultServerConditions;
|
|
6782
|
-
return conditions.filter((c) => c !== "module");
|
|
6783
|
-
}
|
|
6784
|
-
return ["node"];
|
|
8017
|
+
return Number(version.split(".")[0]) >= 6 ? vite.defaultServerConditions.filter((c) => c !== "module") : ["node"];
|
|
6785
8018
|
}
|
|
6786
8019
|
|
|
6787
8020
|
function ModuleRunnerTransform() {
|
|
@@ -6838,16 +8071,17 @@ function VitestProjectResolver(ctx) {
|
|
|
6838
8071
|
const plugin = {
|
|
6839
8072
|
name: "vitest:resolve-root",
|
|
6840
8073
|
enforce: "pre",
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
const resolved = await ctx.vite.pluginContainer.resolveId(id, void 0, {
|
|
6846
|
-
skip: new Set([plugin]),
|
|
6847
|
-
ssr
|
|
6848
|
-
});
|
|
6849
|
-
return resolved;
|
|
8074
|
+
config: {
|
|
8075
|
+
order: "post",
|
|
8076
|
+
handler() {
|
|
8077
|
+
return { base: "/" };
|
|
6850
8078
|
}
|
|
8079
|
+
},
|
|
8080
|
+
async resolveId(id, _, { ssr }) {
|
|
8081
|
+
if (id === "vitest" || id.startsWith("@vitest/") || id.startsWith("vitest/")) return await ctx.vite.pluginContainer.resolveId(id, void 0, {
|
|
8082
|
+
skip: new Set([plugin]),
|
|
8083
|
+
ssr
|
|
8084
|
+
});
|
|
6851
8085
|
}
|
|
6852
8086
|
};
|
|
6853
8087
|
return plugin;
|
|
@@ -6856,6 +8090,12 @@ function VitestCoreResolver(ctx) {
|
|
|
6856
8090
|
return {
|
|
6857
8091
|
name: "vitest:resolve-core",
|
|
6858
8092
|
enforce: "pre",
|
|
8093
|
+
config: {
|
|
8094
|
+
order: "post",
|
|
8095
|
+
handler() {
|
|
8096
|
+
return { base: "/" };
|
|
8097
|
+
}
|
|
8098
|
+
},
|
|
6859
8099
|
async resolveId(id) {
|
|
6860
8100
|
if (id === "vitest") return resolve(distDir, "index.js");
|
|
6861
8101
|
if (id.startsWith("@vitest/") || id.startsWith("vitest/"))
|
|
@@ -6882,24 +8122,22 @@ function WorkspaceVitestPlugin(project, options) {
|
|
|
6882
8122
|
if (existsSync(pkgJsonPath)) name = JSON.parse(readFileSync(pkgJsonPath, "utf-8")).name;
|
|
6883
8123
|
if (typeof name !== "string" || !name) name = basename(dir);
|
|
6884
8124
|
} else name = options.workspacePath.toString();
|
|
6885
|
-
const
|
|
8125
|
+
const isBrowserEnabled = viteConfig.test?.browser?.enabled ?? (viteConfig.test?.browser && project.vitest._cliOptions.browser?.enabled), workspaceNames = [name], browser = viteConfig.test.browser || {};
|
|
6886
8126
|
if (isBrowserEnabled && browser.name && !browser.instances?.length)
|
|
6887
8127
|
// vitest injects `instances` in this case later on
|
|
6888
8128
|
workspaceNames.push(name ? `${name} (${browser.name})` : browser.name);
|
|
6889
|
-
viteConfig.test?.browser?.instances?.forEach((instance) => {
|
|
6890
|
-
if (instance.name ??= name ? `${name} (${instance.browser})` : instance.browser, isBrowserEnabled) workspaceNames.push(instance.name);
|
|
6891
|
-
});
|
|
6892
|
-
const filters = project.vitest.config.project;
|
|
6893
8129
|
// if there is `--project=...` filter, check if any of the potential projects match
|
|
6894
8130
|
// if projects don't match, we ignore the test project altogether
|
|
6895
8131
|
// if some of them match, they will later be filtered again by `resolveWorkspace`
|
|
6896
|
-
if (
|
|
6897
|
-
|
|
8132
|
+
if (viteConfig.test?.browser?.instances?.forEach((instance) => {
|
|
8133
|
+
if (instance.name ??= name ? `${name} (${instance.browser})` : instance.browser, isBrowserEnabled) workspaceNames.push(instance.name);
|
|
8134
|
+
}), project.vitest.config.project.length) {
|
|
8135
|
+
if (!workspaceNames.some((name) => {
|
|
6898
8136
|
return project.vitest.matchesProjectFilter(name);
|
|
6899
|
-
});
|
|
6900
|
-
if (!hasProject) throw new VitestFilteredOutProjectError();
|
|
8137
|
+
})) throw new VitestFilteredOutProjectError();
|
|
6901
8138
|
}
|
|
6902
8139
|
return {
|
|
8140
|
+
base: "/",
|
|
6903
8141
|
environments: { __vitest__: { dev: {} } },
|
|
6904
8142
|
test: { name: {
|
|
6905
8143
|
label: name,
|
|
@@ -7038,13 +8276,11 @@ async function isValidNodeImport(id) {
|
|
|
7038
8276
|
const extension = extname(id);
|
|
7039
8277
|
if (BUILTIN_EXTENSIONS.has(extension)) return true;
|
|
7040
8278
|
if (extension !== ".js") return false;
|
|
7041
|
-
id = id.replace("file:///", "");
|
|
7042
|
-
const package_ = findNearestPackageData(dirname(id));
|
|
7043
|
-
if (package_.type === "module") return true;
|
|
8279
|
+
if (id = id.replace("file:///", ""), findNearestPackageData(dirname(id)).type === "module") return true;
|
|
7044
8280
|
if (/\.(?:\w+-)?esm?(?:-\w+)?\.js$|\/esm?\//.test(id)) return false;
|
|
7045
8281
|
try {
|
|
7046
8282
|
await esModuleLexer.init;
|
|
7047
|
-
const code = await promises.readFile(id, "utf8"), [, , , hasModuleSyntax] = esModuleLexer.parse(code);
|
|
8283
|
+
const code = await promises$1.readFile(id, "utf8"), [, , , hasModuleSyntax] = esModuleLexer.parse(code);
|
|
7048
8284
|
return !hasModuleSyntax;
|
|
7049
8285
|
} catch {
|
|
7050
8286
|
return false;
|
|
@@ -7064,8 +8300,8 @@ async function _shouldExternalize(id, options) {
|
|
|
7064
8300
|
// Unless the user explicitly opted to inline them, externalize Vite deps.
|
|
7065
8301
|
// They are too big to inline by default.
|
|
7066
8302
|
if (matchExternalizePattern(id, moduleDirectories, options?.external) || options?.cacheDir && id.includes(options.cacheDir)) return id;
|
|
7067
|
-
const isLibraryModule = moduleDirectories.some((dir) => id.includes(dir))
|
|
7068
|
-
return id =
|
|
8303
|
+
const isLibraryModule = moduleDirectories.some((dir) => id.includes(dir));
|
|
8304
|
+
return id = isLibraryModule && options?.fallbackCJS ? guessCJSversion(id) || id : id, matchExternalizePattern(id, moduleDirectories, defaultInline) ? false : matchExternalizePattern(id, moduleDirectories, depsExternal) || isLibraryModule && await isValidNodeImport(id) ? id : false;
|
|
7069
8305
|
}
|
|
7070
8306
|
function matchExternalizePattern(id, moduleDirectories, patterns) {
|
|
7071
8307
|
if (patterns == null) return false;
|
|
@@ -7107,7 +8343,7 @@ class TestSpecification {
|
|
|
7107
8343
|
*/
|
|
7108
8344
|
get testModule() {
|
|
7109
8345
|
const task = this.project.vitest.state.idMap.get(this.taskId);
|
|
7110
|
-
|
|
8346
|
+
if (task) return this.project.vitest.state.getReportedEntity(task);
|
|
7111
8347
|
}
|
|
7112
8348
|
toJSON() {
|
|
7113
8349
|
return [
|
|
@@ -7138,7 +8374,6 @@ async function createViteServer(inlineConfig) {
|
|
|
7138
8374
|
class TestProject {
|
|
7139
8375
|
/**
|
|
7140
8376
|
* The global Vitest instance.
|
|
7141
|
-
* @experimental The public Vitest API is experimental and does not follow semver.
|
|
7142
8377
|
*/
|
|
7143
8378
|
vitest;
|
|
7144
8379
|
/**
|
|
@@ -7152,12 +8387,13 @@ class TestProject {
|
|
|
7152
8387
|
/**
|
|
7153
8388
|
* Temporary directory for the project. This is unique for each project. Vitest stores transformed content here.
|
|
7154
8389
|
*/
|
|
7155
|
-
tmpDir
|
|
8390
|
+
tmpDir;
|
|
7156
8391
|
/** @internal */ typechecker;
|
|
7157
8392
|
/** @internal */ _config;
|
|
7158
8393
|
/** @internal */ _vite;
|
|
7159
8394
|
/** @internal */ _hash;
|
|
7160
8395
|
/** @internal */ _resolver;
|
|
8396
|
+
/** @internal */ _fetcher;
|
|
7161
8397
|
/** @internal */ _serializedDefines;
|
|
7162
8398
|
/** @inetrnal */ testFilesList = null;
|
|
7163
8399
|
runner;
|
|
@@ -7165,8 +8401,8 @@ class TestProject {
|
|
|
7165
8401
|
typecheckFilesList = null;
|
|
7166
8402
|
_globalSetups;
|
|
7167
8403
|
_provided = {};
|
|
7168
|
-
constructor(vitest, options) {
|
|
7169
|
-
this.options = options, this.vitest = vitest, this.globalConfig = vitest.config;
|
|
8404
|
+
constructor(vitest, options, tmpDir) {
|
|
8405
|
+
this.options = options, this.vitest = vitest, this.globalConfig = vitest.config, this.tmpDir = tmpDir || join(tmpdir(), nanoid());
|
|
7170
8406
|
}
|
|
7171
8407
|
/**
|
|
7172
8408
|
* The unique hash of this project. This value is consistent between the reruns.
|
|
@@ -7301,7 +8537,7 @@ class TestProject {
|
|
|
7301
8537
|
const files = await this.globFiles(includeSource, exclude, cwd);
|
|
7302
8538
|
await Promise.all(files.map(async (file) => {
|
|
7303
8539
|
try {
|
|
7304
|
-
const code = await promises.readFile(file, "utf-8");
|
|
8540
|
+
const code = await promises$1.readFile(file, "utf-8");
|
|
7305
8541
|
if (this.isInSourceTestCode(code)) testFiles.push(file);
|
|
7306
8542
|
} catch {
|
|
7307
8543
|
return null;
|
|
@@ -7336,16 +8572,15 @@ class TestProject {
|
|
|
7336
8572
|
}
|
|
7337
8573
|
/** @internal */
|
|
7338
8574
|
async globFiles(include, exclude, cwd) {
|
|
7339
|
-
|
|
8575
|
+
// keep the slashes consistent with Vite
|
|
8576
|
+
// we are not using the pathe here because it normalizes the drive letter on Windows
|
|
8577
|
+
// and we want to keep it the same as working dir
|
|
8578
|
+
return (await glob(include, {
|
|
7340
8579
|
dot: true,
|
|
7341
8580
|
cwd,
|
|
7342
8581
|
ignore: exclude,
|
|
7343
8582
|
expandDirectories: false
|
|
7344
|
-
}
|
|
7345
|
-
// keep the slashes consistent with Vite
|
|
7346
|
-
// we are not using the pathe here because it normalizes the drive letter on Windows
|
|
7347
|
-
// and we want to keep it the same as working dir
|
|
7348
|
-
return files.map((file) => slash(path.resolve(cwd, file)));
|
|
8583
|
+
})).map((file) => slash(path.resolve(cwd, file)));
|
|
7349
8584
|
}
|
|
7350
8585
|
/**
|
|
7351
8586
|
* Test if a file matches the test globs. This does the actual glob matching if the test is not cached, unlike `isCachedTestFile`.
|
|
@@ -7380,28 +8615,22 @@ class TestProject {
|
|
|
7380
8615
|
/** @internal */
|
|
7381
8616
|
_parent;
|
|
7382
8617
|
/** @internal */
|
|
7383
|
-
_initParentBrowser = deduped(async () => {
|
|
8618
|
+
_initParentBrowser = deduped(async (childProject) => {
|
|
7384
8619
|
if (!this.isBrowserEnabled() || this._parentBrowser) return;
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
|
|
7388
|
-
const browser = await
|
|
7389
|
-
|
|
7390
|
-
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
|
|
7394
|
-
},
|
|
7395
|
-
...MocksPlugins({ filter(id) {
|
|
7396
|
-
return !(id.includes(distRoot) || id.includes(cacheDir));
|
|
7397
|
-
} }),
|
|
7398
|
-
MetaEnvReplacerPlugin()
|
|
7399
|
-
], [CoverageTransform(this.vitest)]);
|
|
8620
|
+
const provider = this.config.browser.provider || childProject.config.browser.provider;
|
|
8621
|
+
if (provider == null) throw new Error(`Proider was not specified in the "browser.provider" setting. Please, pass down playwright(), webdriverio() or preview() from "@vitest/browser-playwright", "@vitest/browser-webdriverio" or "@vitest/browser-preview" package respectively.`);
|
|
8622
|
+
if (typeof provider.serverFactory !== "function") throw new TypeError(`The browser provider options do not return a "serverFactory" function. Are you using the latest "@vitest/browser-${provider.name}" package?`);
|
|
8623
|
+
const browser = await provider.serverFactory({
|
|
8624
|
+
project: this,
|
|
8625
|
+
mocksPlugins: (options) => MocksPlugins(options),
|
|
8626
|
+
metaEnvReplacer: () => MetaEnvReplacerPlugin(),
|
|
8627
|
+
coveragePlugin: () => CoverageTransform(this.vitest)
|
|
8628
|
+
});
|
|
7400
8629
|
if (this._parentBrowser = browser, this.config.browser.ui) setup(this.vitest, browser.vite);
|
|
7401
8630
|
});
|
|
7402
8631
|
/** @internal */
|
|
7403
8632
|
_initBrowserServer = deduped(async () => {
|
|
7404
|
-
if (await this._parent?._initParentBrowser(), !this.browser && this._parent?._parentBrowser) this.browser = this._parent._parentBrowser.spawn(this), await this.vitest.report("onBrowserInit", this);
|
|
8633
|
+
if (await this._parent?._initParentBrowser(this), !this.browser && this._parent?._parentBrowser) this.browser = this._parent._parentBrowser.spawn(this), await this.vitest.report("onBrowserInit", this);
|
|
7405
8634
|
});
|
|
7406
8635
|
/**
|
|
7407
8636
|
* Closes the project and all associated resources. This can only be called once; the closing promise is cached until the server restarts.
|
|
@@ -7414,6 +8643,8 @@ class TestProject {
|
|
|
7414
8643
|
this.browser?.close(),
|
|
7415
8644
|
this.clearTmpDir()
|
|
7416
8645
|
].filter(Boolean)).then(() => {
|
|
8646
|
+
if (!this.runner.isClosed()) return this.runner.close();
|
|
8647
|
+
}).then(() => {
|
|
7417
8648
|
this._provided = {}, this._vite = void 0;
|
|
7418
8649
|
});
|
|
7419
8650
|
return this.closingPromise;
|
|
@@ -7438,9 +8669,12 @@ class TestProject {
|
|
|
7438
8669
|
// type is very strict here, so we cast it to any
|
|
7439
8670
|
this.provide(providedKey, this.config.provide[providedKey]);
|
|
7440
8671
|
}
|
|
7441
|
-
this.closingPromise = void 0, this._resolver = new VitestResolver(server.config.cacheDir, this._config), this._vite = server, this._serializedDefines = createDefinesScript(server.config.define)
|
|
8672
|
+
this.closingPromise = void 0, this._resolver = new VitestResolver(server.config.cacheDir, this._config), this._vite = server, this._serializedDefines = createDefinesScript(server.config.define), this._fetcher = createFetchModuleFunction(this._resolver, this.tmpDir, {
|
|
8673
|
+
dumpFolder: this.config.dumpDir,
|
|
8674
|
+
readFromDump: this.config.server.debug?.load ?? process.env.VITEST_DEBUG_LOAD_DUMP != null
|
|
8675
|
+
});
|
|
7442
8676
|
const environment = server.environments.__vitest__;
|
|
7443
|
-
this.runner = new ServerModuleRunner(environment, this.
|
|
8677
|
+
this.runner = new ServerModuleRunner(environment, this._fetcher, this._config);
|
|
7444
8678
|
}
|
|
7445
8679
|
_serializeOverriddenConfig() {
|
|
7446
8680
|
// TODO: serialize the config _once_ or when needed
|
|
@@ -7469,13 +8703,13 @@ class TestProject {
|
|
|
7469
8703
|
}
|
|
7470
8704
|
/** @internal */
|
|
7471
8705
|
static _createBasicProject(vitest) {
|
|
7472
|
-
const project = new TestProject(vitest);
|
|
7473
|
-
return project.runner = vitest.runner, project._vite = vitest.vite, project._config = vitest.config, project._resolver = vitest._resolver, project._serializedDefines = createDefinesScript(vitest.vite.config.define), project._setHash(), project._provideObject(vitest.config.provide), project;
|
|
8706
|
+
const project = new TestProject(vitest, void 0, vitest._tmpDir);
|
|
8707
|
+
return project.runner = vitest.runner, project._vite = vitest.vite, project._config = vitest.config, project._resolver = vitest._resolver, project._fetcher = vitest._fetcher, project._serializedDefines = createDefinesScript(vitest.vite.config.define), project._setHash(), project._provideObject(vitest.config.provide), project;
|
|
7474
8708
|
}
|
|
7475
8709
|
/** @internal */
|
|
7476
8710
|
static _cloneBrowserProject(parent, config) {
|
|
7477
|
-
const clone = new TestProject(parent.vitest);
|
|
7478
|
-
return clone.runner = parent.runner, clone._vite = parent._vite, clone._resolver = parent._resolver, clone._config = config, clone._setHash(), clone._parent = parent, clone._serializedDefines = parent._serializedDefines, clone._provideObject(config.provide), clone;
|
|
8711
|
+
const clone = new TestProject(parent.vitest, void 0, parent.tmpDir);
|
|
8712
|
+
return clone.runner = parent.runner, clone._vite = parent._vite, clone._resolver = parent._resolver, clone._fetcher = parent._fetcher, clone._config = config, clone._setHash(), clone._parent = parent, clone._serializedDefines = parent._serializedDefines, clone._provideObject(config.provide), clone;
|
|
7479
8713
|
}
|
|
7480
8714
|
}
|
|
7481
8715
|
function deduped(cb) {
|
|
@@ -7510,8 +8744,13 @@ function generateHash(str) {
|
|
|
7510
8744
|
return `${hash}`;
|
|
7511
8745
|
}
|
|
7512
8746
|
|
|
8747
|
+
// vitest.config.*
|
|
8748
|
+
// vite.config.*
|
|
8749
|
+
// vitest.unit.config.*
|
|
8750
|
+
// vite.unit.config.*
|
|
8751
|
+
const CONFIG_REGEXP = /^vite(?:st)?(?:\.\w+)?\.config\./;
|
|
7513
8752
|
async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projectsDefinition, names) {
|
|
7514
|
-
const { configFiles, projectConfigs, nonConfigDirectories } = await resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDefinition),
|
|
8753
|
+
const { configFiles, projectConfigs, nonConfigDirectories } = await resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDefinition), cliOverrides = [
|
|
7515
8754
|
"logHeapUsage",
|
|
7516
8755
|
"allowOnly",
|
|
7517
8756
|
"sequence",
|
|
@@ -7530,7 +8769,7 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
|
|
|
7530
8769
|
"inspect",
|
|
7531
8770
|
"inspectBrk",
|
|
7532
8771
|
"fileParallelism"
|
|
7533
|
-
]
|
|
8772
|
+
].reduce((acc, name) => {
|
|
7534
8773
|
if (name in cliOptions) acc[name] = cliOptions[name];
|
|
7535
8774
|
return acc;
|
|
7536
8775
|
}, {}), projectPromises = [], fileProjects = [...configFiles, ...nonConfigDirectories], concurrent = limitConcurrency(nodeos__default.availableParallelism?.() || nodeos__default.cpus().length || 5);
|
|
@@ -7598,19 +8837,13 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
|
|
|
7598
8837
|
}
|
|
7599
8838
|
async function resolveBrowserProjects(vitest, names, resolvedProjects) {
|
|
7600
8839
|
const removeProjects = /* @__PURE__ */ new Set();
|
|
7601
|
-
resolvedProjects.forEach((project) => {
|
|
8840
|
+
return resolvedProjects.forEach((project) => {
|
|
7602
8841
|
if (!project.config.browser.enabled) return;
|
|
7603
|
-
const instances = project.config.browser.instances || []
|
|
7604
|
-
if (instances.length === 0
|
|
7605
|
-
|
|
7606
|
-
|
|
7607
|
-
}
|
|
7608
|
-
`No browser "instances" were defined`,
|
|
7609
|
-
project.name ? ` for the "${project.name}" project. ` : ". ",
|
|
7610
|
-
`Running tests in "${project.config.browser.name}" browser. `,
|
|
7611
|
-
"The \"browser.name\" field is deprecated since Vitest 3. ",
|
|
7612
|
-
"Read more: https://vitest.dev/guide/browser/config#browser-instances"
|
|
7613
|
-
].filter(Boolean).join("")));
|
|
8842
|
+
const instances = project.config.browser.instances || [];
|
|
8843
|
+
if (instances.length === 0) {
|
|
8844
|
+
removeProjects.add(project);
|
|
8845
|
+
return;
|
|
8846
|
+
}
|
|
7614
8847
|
const originalName = project.config.name, filteredInstances = vitest.matchesProjectFilter(originalName) ? instances : instances.filter((instance) => {
|
|
7615
8848
|
const newName = instance.name;
|
|
7616
8849
|
return vitest.matchesProjectFilter(newName);
|
|
@@ -7620,7 +8853,6 @@ async function resolveBrowserProjects(vitest, names, resolvedProjects) {
|
|
|
7620
8853
|
removeProjects.add(project);
|
|
7621
8854
|
return;
|
|
7622
8855
|
}
|
|
7623
|
-
if (project.config.browser.providerOptions) vitest.logger.warn(withLabel("yellow", "Vitest", `"providerOptions"${originalName ? ` in "${originalName}" project` : ""} is ignored because it's overridden by the configs. To hide this warning, remove the "providerOptions" property from the browser configuration.`));
|
|
7624
8856
|
filteredInstances.forEach((config, index) => {
|
|
7625
8857
|
const browser = config.browser;
|
|
7626
8858
|
if (!browser) {
|
|
@@ -7629,6 +8861,7 @@ async function resolveBrowserProjects(vitest, names, resolvedProjects) {
|
|
|
7629
8861
|
}
|
|
7630
8862
|
const name = config.name;
|
|
7631
8863
|
if (name == null) throw new Error(`The browser configuration must have a "name" property. This is a bug in Vitest. Please, open a new issue with reproduction`);
|
|
8864
|
+
if (config.provider?.name != null && project.config.browser.provider?.name != null && config.provider?.name !== project.config.browser.provider?.name) throw new Error(`The instance cannot have a different provider from its parent. The "${name}" instance specifies "${config.provider?.name}" provider, but its parent has a "${project.config.browser.provider?.name}" provider.`);
|
|
7632
8865
|
if (names.has(name)) throw new Error([
|
|
7633
8866
|
`Cannot define a nested project for a ${browser} browser. The project name "${name}" was already defined. `,
|
|
7634
8867
|
"If you have multiple instances for the same browser, make sure to define a custom \"name\". ",
|
|
@@ -7640,29 +8873,10 @@ async function resolveBrowserProjects(vitest, names, resolvedProjects) {
|
|
|
7640
8873
|
const clone = TestProject._cloneBrowserProject(project, clonedConfig);
|
|
7641
8874
|
resolvedProjects.push(clone);
|
|
7642
8875
|
}), removeProjects.add(project);
|
|
7643
|
-
}), resolvedProjects
|
|
7644
|
-
const headedBrowserProjects = resolvedProjects.filter((project) => {
|
|
7645
|
-
return project.config.browser.enabled && !project.config.browser.headless;
|
|
7646
|
-
});
|
|
7647
|
-
if (headedBrowserProjects.length > 1) {
|
|
7648
|
-
const message = [`Found multiple projects that run browser tests in headed mode: "${headedBrowserProjects.map((p) => p.name).join("\", \"")}".`, ` Vitest cannot run multiple headed browsers at the same time.`].join("");
|
|
7649
|
-
if (!isTTY) throw new Error(`${message} Please, filter projects with --browser=name or --project=name flag or run tests with "headless: true" option.`);
|
|
7650
|
-
const prompts = await import('./index.X0nbfr6-.js').then(function (n) { return n.i; }), { projectName } = await prompts.default({
|
|
7651
|
-
type: "select",
|
|
7652
|
-
name: "projectName",
|
|
7653
|
-
choices: headedBrowserProjects.map((project) => ({
|
|
7654
|
-
title: project.name,
|
|
7655
|
-
value: project.name
|
|
7656
|
-
})),
|
|
7657
|
-
message: `${message} Select a single project to run or cancel and run tests with "headless: true" option. Note that you can also start tests with --browser=name or --project=name flag.`
|
|
7658
|
-
});
|
|
7659
|
-
if (!projectName) throw new Error("The test run was aborted.");
|
|
7660
|
-
return resolvedProjects.filter((project) => project.name === projectName);
|
|
7661
|
-
}
|
|
7662
|
-
return resolvedProjects;
|
|
8876
|
+
}), resolvedProjects.filter((project) => !removeProjects.has(project));
|
|
7663
8877
|
}
|
|
7664
8878
|
function cloneConfig(project, { browser,...config }) {
|
|
7665
|
-
const { locators, viewport, testerHtmlPath, headless, screenshotDirectory, screenshotFailures, browser: _browser, name,...overrideConfig } = config, currentConfig = project.config.browser, clonedConfig = deepClone(project.config);
|
|
8879
|
+
const { locators, viewport, testerHtmlPath, headless, screenshotDirectory, screenshotFailures, browser: _browser, name, provider,...overrideConfig } = config, currentConfig = project.config.browser, clonedConfig = deepClone(project.config);
|
|
7666
8880
|
return mergeConfig({
|
|
7667
8881
|
...clonedConfig,
|
|
7668
8882
|
browser: {
|
|
@@ -7673,9 +8887,9 @@ function cloneConfig(project, { browser,...config }) {
|
|
|
7673
8887
|
screenshotDirectory: screenshotDirectory ?? currentConfig.screenshotDirectory,
|
|
7674
8888
|
screenshotFailures: screenshotFailures ?? currentConfig.screenshotFailures,
|
|
7675
8889
|
headless: headless ?? currentConfig.headless,
|
|
8890
|
+
provider: provider ?? currentConfig.provider,
|
|
7676
8891
|
name: browser,
|
|
7677
|
-
|
|
7678
|
-
instances: void 0
|
|
8892
|
+
instances: []
|
|
7679
8893
|
},
|
|
7680
8894
|
include: overrideConfig.include && overrideConfig.include.length > 0 ? [] : clonedConfig.include,
|
|
7681
8895
|
exclude: overrideConfig.exclude && overrideConfig.exclude.length > 0 ? [] : clonedConfig.exclude,
|
|
@@ -7695,14 +8909,17 @@ async function resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDe
|
|
|
7695
8909
|
const note = "Projects definition";
|
|
7696
8910
|
throw new Error(`${note} references a non-existing file or a directory: ${file}`);
|
|
7697
8911
|
}
|
|
7698
|
-
const stats =
|
|
8912
|
+
const stats = statSync(file);
|
|
7699
8913
|
// user can specify a config file directly
|
|
7700
|
-
if (stats.isFile())
|
|
7701
|
-
|
|
7702
|
-
|
|
8914
|
+
if (stats.isFile()) {
|
|
8915
|
+
const name = basename(file);
|
|
8916
|
+
if (!CONFIG_REGEXP.test(name)) throw new Error(`The file "${relative(vitest.config.root, file)}" must start with "vitest.config"/"vite.config" or match the pattern "(vitest|vite).*.config.*" to be a valid project config.`);
|
|
8917
|
+
projectsConfigFiles.push(file);
|
|
8918
|
+
} else if (stats.isDirectory()) {
|
|
8919
|
+
const configFile = resolveDirectoryConfig(file);
|
|
7703
8920
|
if (configFile) projectsConfigFiles.push(configFile);
|
|
7704
8921
|
else {
|
|
7705
|
-
const directory = file
|
|
8922
|
+
const directory = file.at(-1) === "/" ? file : `${file}/`;
|
|
7706
8923
|
nonConfigProjectDirectories.push(directory);
|
|
7707
8924
|
}
|
|
7708
8925
|
} else
|
|
@@ -7728,16 +8945,20 @@ async function resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDe
|
|
|
7728
8945
|
"**/*.timestamp-*",
|
|
7729
8946
|
"**/.DS_Store"
|
|
7730
8947
|
]
|
|
7731
|
-
}
|
|
7732
|
-
await
|
|
8948
|
+
};
|
|
8949
|
+
(await glob(projectsGlobMatches, globOptions)).forEach((path) => {
|
|
7733
8950
|
// directories are allowed with a glob like `packages/*`
|
|
7734
8951
|
// in this case every directory is treated as a project
|
|
7735
8952
|
if (path.endsWith("/")) {
|
|
7736
|
-
const configFile =
|
|
8953
|
+
const configFile = resolveDirectoryConfig(path);
|
|
7737
8954
|
if (configFile) projectsConfigFiles.push(configFile);
|
|
7738
8955
|
else nonConfigProjectDirectories.push(path);
|
|
7739
|
-
} else
|
|
7740
|
-
|
|
8956
|
+
} else {
|
|
8957
|
+
const name = basename(path);
|
|
8958
|
+
if (!CONFIG_REGEXP.test(name)) throw new Error(`The projects glob matched a file "${relative(vitest.config.root, path)}", but it should also either start with "vitest.config"/"vite.config" or match the pattern "(vitest|vite).*.config.*".`);
|
|
8959
|
+
projectsConfigFiles.push(path);
|
|
8960
|
+
}
|
|
8961
|
+
});
|
|
7741
8962
|
}
|
|
7742
8963
|
const projectConfigFiles = Array.from(new Set(projectsConfigFiles));
|
|
7743
8964
|
return {
|
|
@@ -7746,16 +8967,13 @@ async function resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDe
|
|
|
7746
8967
|
configFiles: projectConfigFiles
|
|
7747
8968
|
};
|
|
7748
8969
|
}
|
|
7749
|
-
|
|
7750
|
-
const files = new Set(
|
|
8970
|
+
function resolveDirectoryConfig(directory) {
|
|
8971
|
+
const files = new Set(readdirSync(directory)), configFile = configFiles.find((file) => files.has(file));
|
|
7751
8972
|
return configFile ? resolve(directory, configFile) : null;
|
|
7752
8973
|
}
|
|
7753
8974
|
function getDefaultTestProject(vitest) {
|
|
7754
8975
|
const filter = vitest.config.project, project = vitest._ensureRootProject();
|
|
7755
|
-
|
|
7756
|
-
// check for the project name and browser names
|
|
7757
|
-
const hasProjects = getPotentialProjectNames(project).some((p) => vitest.matchesProjectFilter(p));
|
|
7758
|
-
return hasProjects ? project : null;
|
|
8976
|
+
return !filter.length || getPotentialProjectNames(project).some((p) => vitest.matchesProjectFilter(p)) ? project : null;
|
|
7759
8977
|
}
|
|
7760
8978
|
function getPotentialProjectNames(project) {
|
|
7761
8979
|
const names = [project.name];
|
|
@@ -7778,17 +8996,11 @@ function createReporters(reporterReferences, ctx) {
|
|
|
7778
8996
|
const runner = ctx.runner, promisedReporters = reporterReferences.map(async (referenceOrInstance) => {
|
|
7779
8997
|
if (Array.isArray(referenceOrInstance)) {
|
|
7780
8998
|
const [reporterName, reporterOptions] = referenceOrInstance;
|
|
7781
|
-
if (reporterName === "html")
|
|
7782
|
-
|
|
7783
|
-
const CustomReporter = await loadCustomReporterModule("@vitest/ui/reporter", runner);
|
|
7784
|
-
return new CustomReporter(reporterOptions);
|
|
7785
|
-
} else if (reporterName in ReportersMap) {
|
|
8999
|
+
if (reporterName === "html") return await ctx.packageInstaller.ensureInstalled("@vitest/ui", ctx.config.root, ctx.version), new (await (loadCustomReporterModule("@vitest/ui/reporter", runner)))(reporterOptions);
|
|
9000
|
+
if (reporterName in ReportersMap) {
|
|
7786
9001
|
const BuiltinReporter = ReportersMap[reporterName];
|
|
7787
9002
|
return new BuiltinReporter(reporterOptions);
|
|
7788
|
-
} else
|
|
7789
|
-
const CustomReporter = await loadCustomReporterModule(reporterName, runner);
|
|
7790
|
-
return new CustomReporter(reporterOptions);
|
|
7791
|
-
}
|
|
9003
|
+
} else return new (await (loadCustomReporterModule(reporterName, runner)))(reporterOptions);
|
|
7792
9004
|
}
|
|
7793
9005
|
return referenceOrInstance;
|
|
7794
9006
|
});
|
|
@@ -7799,10 +9011,7 @@ function createBenchmarkReporters(reporterReferences, runner) {
|
|
|
7799
9011
|
if (typeof referenceOrInstance === "string") if (referenceOrInstance in BenchmarkReportsMap) {
|
|
7800
9012
|
const BuiltinReporter = BenchmarkReportsMap[referenceOrInstance];
|
|
7801
9013
|
return new BuiltinReporter();
|
|
7802
|
-
} else
|
|
7803
|
-
const CustomReporter = await loadCustomReporterModule(referenceOrInstance, runner);
|
|
7804
|
-
return new CustomReporter();
|
|
7805
|
-
}
|
|
9014
|
+
} else return new (await (loadCustomReporterModule(referenceOrInstance, runner)))();
|
|
7806
9015
|
return referenceOrInstance;
|
|
7807
9016
|
});
|
|
7808
9017
|
return Promise.all(promisedReporters);
|
|
@@ -7820,11 +9029,11 @@ function parseFilter(filter) {
|
|
|
7820
9029
|
return { filename: filter };
|
|
7821
9030
|
}
|
|
7822
9031
|
function groupFilters(filters) {
|
|
7823
|
-
const groupedFilters_ = groupBy(filters, (f) => f.filename)
|
|
9032
|
+
const groupedFilters_ = groupBy(filters, (f) => f.filename);
|
|
9033
|
+
return Object.fromEntries(Object.entries(groupedFilters_).map((entry) => {
|
|
7824
9034
|
const [filename, filters] = entry, testLocations = filters.map((f) => f.lineNumber);
|
|
7825
9035
|
return [filename, testLocations.filter((l) => l !== void 0)];
|
|
7826
9036
|
}));
|
|
7827
|
-
return groupedFilters;
|
|
7828
9037
|
}
|
|
7829
9038
|
|
|
7830
9039
|
class VitestSpecifications {
|
|
@@ -7885,7 +9094,7 @@ class VitestSpecifications {
|
|
|
7885
9094
|
}
|
|
7886
9095
|
async filterTestsBySource(specs) {
|
|
7887
9096
|
if (this.vitest.config.changed && !this.vitest.config.related) {
|
|
7888
|
-
const { VitestGit } = await import('./git.BFNcloKD.js'),
|
|
9097
|
+
const { VitestGit } = await import('./git.BFNcloKD.js'), related = await new VitestGit(this.vitest.config.root).findChangedFiles({ changedSince: this.vitest.config.changed });
|
|
7889
9098
|
if (!related) throw process.exitCode = 1, new GitNotFoundError();
|
|
7890
9099
|
this.vitest.config.related = Array.from(new Set(related));
|
|
7891
9100
|
}
|
|
@@ -7909,7 +9118,7 @@ class VitestSpecifications {
|
|
|
7909
9118
|
const addImports = async (project, filepath) => {
|
|
7910
9119
|
if (deps.has(filepath)) return;
|
|
7911
9120
|
deps.add(filepath);
|
|
7912
|
-
const
|
|
9121
|
+
const transformed = project.vite.environments.ssr.moduleGraph.getModuleById(filepath)?.transformResult || await project.vite.environments.ssr.transformRequest(filepath);
|
|
7913
9122
|
if (!transformed) return;
|
|
7914
9123
|
const dependencies = [...transformed.deps || [], ...transformed.dynamicDeps || []];
|
|
7915
9124
|
await Promise.all(dependencies.map(async (dep) => {
|
|
@@ -8047,10 +9256,10 @@ class TestCase extends ReportedTaskImplementation {
|
|
|
8047
9256
|
diagnostic() {
|
|
8048
9257
|
const result = this.task.result;
|
|
8049
9258
|
// startTime should always be available if the test has properly finished
|
|
8050
|
-
if (!result || !result.startTime) return
|
|
8051
|
-
const duration = result.duration || 0
|
|
9259
|
+
if (!result || !result.startTime) return;
|
|
9260
|
+
const duration = result.duration || 0;
|
|
8052
9261
|
return {
|
|
8053
|
-
slow,
|
|
9262
|
+
slow: duration > this.project.globalConfig.slowTestThreshold,
|
|
8054
9263
|
heap: result.heap,
|
|
8055
9264
|
duration,
|
|
8056
9265
|
startTime: result.startTime,
|
|
@@ -8189,16 +9398,19 @@ class TestModule extends SuiteImplementation {
|
|
|
8189
9398
|
* This value corresponds to the ID in the Vite's module graph.
|
|
8190
9399
|
*/
|
|
8191
9400
|
moduleId;
|
|
9401
|
+
/**
|
|
9402
|
+
* Module id relative to the project. This is the same as `task.name`.
|
|
9403
|
+
*/
|
|
9404
|
+
relativeModuleId;
|
|
8192
9405
|
/** @internal */
|
|
8193
9406
|
constructor(task, project) {
|
|
8194
|
-
super(task, project), this.moduleId = task.filepath;
|
|
9407
|
+
super(task, project), this.moduleId = task.filepath, this.relativeModuleId = task.name;
|
|
8195
9408
|
}
|
|
8196
9409
|
/**
|
|
8197
9410
|
* Checks the running state of the test file.
|
|
8198
9411
|
*/
|
|
8199
9412
|
state() {
|
|
8200
|
-
|
|
8201
|
-
return state === "queued" ? "queued" : getSuiteState(this.task);
|
|
9413
|
+
return this.task.result?.state === "queued" ? "queued" : getSuiteState(this.task);
|
|
8202
9414
|
}
|
|
8203
9415
|
/**
|
|
8204
9416
|
* Useful information about the module like duration, memory usage, etc.
|
|
@@ -8257,10 +9469,10 @@ class StateManager {
|
|
|
8257
9469
|
idMap = /* @__PURE__ */ new Map();
|
|
8258
9470
|
taskFileMap = /* @__PURE__ */ new WeakMap();
|
|
8259
9471
|
errorsSet = /* @__PURE__ */ new Set();
|
|
8260
|
-
processTimeoutCauses = /* @__PURE__ */ new Set();
|
|
8261
9472
|
reportedTasksMap = /* @__PURE__ */ new WeakMap();
|
|
8262
9473
|
blobs;
|
|
8263
9474
|
transformTime = 0;
|
|
9475
|
+
metadata = {};
|
|
8264
9476
|
onUnhandledError;
|
|
8265
9477
|
/** @internal */
|
|
8266
9478
|
_data = {
|
|
@@ -8291,12 +9503,6 @@ class StateManager {
|
|
|
8291
9503
|
getUnhandledErrors() {
|
|
8292
9504
|
return Array.from(this.errorsSet.values());
|
|
8293
9505
|
}
|
|
8294
|
-
addProcessTimeoutCause(cause) {
|
|
8295
|
-
this.processTimeoutCauses.add(cause);
|
|
8296
|
-
}
|
|
8297
|
-
getProcessTimeoutCauses() {
|
|
8298
|
-
return Array.from(this.processTimeoutCauses.values());
|
|
8299
|
-
}
|
|
8300
9506
|
getPaths() {
|
|
8301
9507
|
return Array.from(this.pathsSet);
|
|
8302
9508
|
}
|
|
@@ -8357,6 +9563,10 @@ class StateManager {
|
|
|
8357
9563
|
getReportedEntity(task) {
|
|
8358
9564
|
return this.reportedTasksMap.get(task);
|
|
8359
9565
|
}
|
|
9566
|
+
getReportedEntityById(taskId) {
|
|
9567
|
+
const task = this.idMap.get(taskId);
|
|
9568
|
+
return task ? this.reportedTasksMap.get(task) : void 0;
|
|
9569
|
+
}
|
|
8360
9570
|
updateTasks(packs) {
|
|
8361
9571
|
for (const [id, result, meta] of packs) {
|
|
8362
9572
|
const task = this.idMap.get(id);
|
|
@@ -8377,7 +9587,12 @@ class StateManager {
|
|
|
8377
9587
|
return Array.from(this.idMap.values()).filter((t) => t.result?.state === "fail").length;
|
|
8378
9588
|
}
|
|
8379
9589
|
cancelFiles(files, project) {
|
|
8380
|
-
|
|
9590
|
+
// if we don't filter existing modules, they will be overriden by `collectFiles`
|
|
9591
|
+
const nonRegisteredFiles = files.filter(({ filepath }) => {
|
|
9592
|
+
const relativePath = relative(project.config.root, filepath), id = generateFileHash(relativePath, project.name);
|
|
9593
|
+
return !this.idMap.has(id);
|
|
9594
|
+
});
|
|
9595
|
+
this.collectFiles(project, nonRegisteredFiles.map((file) => createFileTask$1(file.filepath, project.config.root, project.config.name)));
|
|
8381
9596
|
}
|
|
8382
9597
|
}
|
|
8383
9598
|
|
|
@@ -8862,7 +10077,7 @@ class TestRun {
|
|
|
8862
10077
|
async updated(update, events) {
|
|
8863
10078
|
this.syncUpdateStacks(update), this.vitest.state.updateTasks(update);
|
|
8864
10079
|
for (const [id, event, data] of events) await this.reportEvent(id, event, data).catch((error) => {
|
|
8865
|
-
this.vitest.state.catchError(
|
|
10080
|
+
this.vitest.state.catchError(serializeValue(error), "Unhandled Reporter Error");
|
|
8866
10081
|
});
|
|
8867
10082
|
// TODO: what is the order or reports here?
|
|
8868
10083
|
// "onTaskUpdate" in parallel with others or before all or after all?
|
|
@@ -8874,11 +10089,20 @@ class TestRun {
|
|
|
8874
10089
|
// specification won't have the File task if they were filtered by the --shard command
|
|
8875
10090
|
const modules = specifications.map((spec) => spec.testModule).filter((s) => s != null), state = this.vitest.isCancelling ? "interrupted" : this.hasFailed(modules) ? "failed" : "passed";
|
|
8876
10091
|
if (state !== "passed") process.exitCode = 1;
|
|
8877
|
-
await this.vitest.report("onTestRunEnd", modules, [...errors], state)
|
|
10092
|
+
for (const project in await this.vitest.report("onTestRunEnd", modules, [...errors], state), this.vitest.state.metadata) {
|
|
10093
|
+
const meta = this.vitest.state.metadata[project];
|
|
10094
|
+
if (!meta?.dumpDir) continue;
|
|
10095
|
+
const path = resolve(meta.dumpDir, "vitest-metadata.json");
|
|
10096
|
+
meta.outline = {
|
|
10097
|
+
externalized: Object.keys(meta.externalized).length,
|
|
10098
|
+
inlined: Object.keys(meta.tmps).length
|
|
10099
|
+
}, await writeFile(path, JSON.stringify(meta, null, 2), "utf-8"), this.vitest.logger.log(`Metadata written to ${path}`);
|
|
10100
|
+
}
|
|
8878
10101
|
}
|
|
8879
10102
|
hasFailed(modules) {
|
|
8880
10103
|
return modules.length ? modules.some((m) => !m.ok()) : !this.vitest.config.passWithNoTests;
|
|
8881
10104
|
}
|
|
10105
|
+
// make sure the error always has a "stacks" property
|
|
8882
10106
|
syncUpdateStacks(update) {
|
|
8883
10107
|
update.forEach(([taskId, result]) => {
|
|
8884
10108
|
const task = this.vitest.state.idMap.get(taskId), isBrowser = task && task.file.pool === "browser";
|
|
@@ -8929,9 +10153,8 @@ class TestRun {
|
|
|
8929
10153
|
const path = attachment.path;
|
|
8930
10154
|
if (path && !path.startsWith("http://") && !path.startsWith("https://")) {
|
|
8931
10155
|
const currentPath = resolve(project.config.root, path), hash = createHash("sha1").update(currentPath).digest("hex"), newPath = resolve(project.config.attachmentsDir, `${sanitizeFilePath(annotation.message)}-${hash}${extname(currentPath)}`);
|
|
8932
|
-
await mkdir(
|
|
8933
|
-
|
|
8934
|
-
attachment.contentType = contentType || void 0;
|
|
10156
|
+
if (!existsSync(project.config.attachmentsDir)) await mkdir(project.config.attachmentsDir, { recursive: true });
|
|
10157
|
+
await copyFile(currentPath, newPath), attachment.path = newPath, attachment.contentType = (attachment.contentType ?? mime.getType(basename(currentPath))) || void 0;
|
|
8935
10158
|
}
|
|
8936
10159
|
return attachment;
|
|
8937
10160
|
}
|
|
@@ -8991,21 +10214,14 @@ class VitestWatcher {
|
|
|
8991
10214
|
}), triggered;
|
|
8992
10215
|
}
|
|
8993
10216
|
onFileChange = (id) => {
|
|
8994
|
-
id = slash(id), this.vitest.logger.clearHighlightCache(id), this.vitest.invalidateFile(id);
|
|
8995
|
-
|
|
8996
|
-
if (testFiles) this.scheduleRerun(id);
|
|
8997
|
-
else {
|
|
8998
|
-
const needsRerun = this.handleFileChanged(id);
|
|
8999
|
-
if (needsRerun) this.scheduleRerun(id);
|
|
9000
|
-
}
|
|
10217
|
+
if (id = slash(id), this.vitest.logger.clearHighlightCache(id), this.vitest.invalidateFile(id), this.getTestFilesFromWatcherTrigger(id)) this.scheduleRerun(id);
|
|
10218
|
+
else if (this.handleFileChanged(id)) this.scheduleRerun(id);
|
|
9001
10219
|
};
|
|
9002
10220
|
onFileDelete = (id) => {
|
|
9003
10221
|
if (id = slash(id), this.vitest.logger.clearHighlightCache(id), this.invalidates.add(id), this.vitest.state.filesMap.has(id)) this.vitest.projects.forEach((project) => project._removeCachedTestFile(id)), this.vitest.state.filesMap.delete(id), this.vitest.cache.results.removeFromCache(id), this.vitest.cache.stats.removeStats(id), this.changedTests.delete(id), this.vitest.report("onTestRemoved", id);
|
|
9004
10222
|
};
|
|
9005
10223
|
onFileCreate = (id) => {
|
|
9006
|
-
id = slash(id), this.vitest.invalidateFile(id)
|
|
9007
|
-
const testFiles = this.getTestFilesFromWatcherTrigger(id);
|
|
9008
|
-
if (testFiles) {
|
|
10224
|
+
if (id = slash(id), this.vitest.invalidateFile(id), this.getTestFilesFromWatcherTrigger(id)) {
|
|
9009
10225
|
this.scheduleRerun(id);
|
|
9010
10226
|
return;
|
|
9011
10227
|
}
|
|
@@ -9014,11 +10230,7 @@ class VitestWatcher {
|
|
|
9014
10230
|
if (this.vitest.projects.forEach((project) => {
|
|
9015
10231
|
if (project.matchesTestGlob(id, () => fileContent ??= readFileSync(id, "utf-8"))) matchingProjects.push(project);
|
|
9016
10232
|
}), matchingProjects.length > 0) this.changedTests.add(id), this.scheduleRerun(id);
|
|
9017
|
-
else
|
|
9018
|
-
// it's possible that file was already there but watcher triggered "add" event instead
|
|
9019
|
-
const needsRerun = this.handleFileChanged(id);
|
|
9020
|
-
if (needsRerun) this.scheduleRerun(id);
|
|
9021
|
-
}
|
|
10233
|
+
else if (this.handleFileChanged(id)) this.scheduleRerun(id);
|
|
9022
10234
|
};
|
|
9023
10235
|
handleSetupFile(filepath) {
|
|
9024
10236
|
let isSetupFile = false;
|
|
@@ -9038,8 +10250,7 @@ class VitestWatcher {
|
|
|
9038
10250
|
if (pm.isMatch(filepath, this.vitest.config.forceRerunTriggers)) return this.vitest.state.getFilepaths().forEach((file) => this.changedTests.add(file)), true;
|
|
9039
10251
|
if (this.handleSetupFile(filepath)) return true;
|
|
9040
10252
|
const projects = this.vitest.projects.filter((project) => {
|
|
9041
|
-
|
|
9042
|
-
return moduleGraph.getModulesByFile(filepath)?.size;
|
|
10253
|
+
return (project.browser?.vite.moduleGraph || project.vite.moduleGraph).getModulesByFile(filepath)?.size;
|
|
9043
10254
|
});
|
|
9044
10255
|
if (!projects.length) return this.vitest.state.filesMap.has(filepath) || this.vitest.projects.some((project) => project._isCachedTestFile(filepath)) ? (this.changedTests.add(filepath), true) : false;
|
|
9045
10256
|
const files = [];
|
|
@@ -9053,9 +10264,7 @@ class VitestWatcher {
|
|
|
9053
10264
|
}
|
|
9054
10265
|
let rerun = false;
|
|
9055
10266
|
for (const mod of mods) mod.importers.forEach((i) => {
|
|
9056
|
-
if (
|
|
9057
|
-
const needsRerun = this.handleFileChanged(i.file);
|
|
9058
|
-
if (needsRerun) rerun = true;
|
|
10267
|
+
if (i.file && this.handleFileChanged(i.file)) rerun = true;
|
|
9059
10268
|
});
|
|
9060
10269
|
if (rerun) files.push(filepath);
|
|
9061
10270
|
}
|
|
@@ -9115,6 +10324,8 @@ class Vitest {
|
|
|
9115
10324
|
/** @internal */ runner;
|
|
9116
10325
|
/** @internal */ _testRun = void 0;
|
|
9117
10326
|
/** @internal */ _resolver;
|
|
10327
|
+
/** @internal */ _fetcher;
|
|
10328
|
+
/** @internal */ _tmpDir = join(tmpdir(), nanoid());
|
|
9118
10329
|
isFirstRun = true;
|
|
9119
10330
|
restartsCount = 0;
|
|
9120
10331
|
specifications;
|
|
@@ -9170,18 +10381,19 @@ class Vitest {
|
|
|
9170
10381
|
this.watcher.unregisterWatcher(), clearTimeout(this._rerunTimer), this.restartsCount += 1, this.pool?.close?.(), this.pool = void 0, this.closingPromise = void 0, this.projects = [], this.runningPromise = void 0, this.coreWorkspaceProject = void 0, this.specifications.clearCache(), this._coverageProvider = void 0, this._onUserTestsRerun = [], this._vite = server;
|
|
9171
10382
|
const resolved = resolveConfig(this, options, server.config);
|
|
9172
10383
|
if (this._config = resolved, this._state = new StateManager({ onUnhandledError: resolved.onUnhandledError }), this._cache = new VitestCache(this.version), this._snapshot = new SnapshotManager({ ...resolved.snapshotOptions }), this._testRun = new TestRun(this), this.config.watch) this.watcher.registerWatcher();
|
|
9173
|
-
this._resolver = new VitestResolver(server.config.cacheDir, resolved)
|
|
10384
|
+
this._resolver = new VitestResolver(server.config.cacheDir, resolved), this._fetcher = createFetchModuleFunction(this._resolver, this._tmpDir, {
|
|
10385
|
+
dumpFolder: this.config.dumpDir,
|
|
10386
|
+
readFromDump: this.config.server.debug?.load ?? process.env.VITEST_DEBUG_LOAD_DUMP != null
|
|
10387
|
+
});
|
|
9174
10388
|
const environment = server.environments.__vitest__;
|
|
9175
|
-
if (this.runner = new ServerModuleRunner(environment, this.
|
|
10389
|
+
if (this.runner = new ServerModuleRunner(environment, this._fetcher, resolved), this.config.watch) {
|
|
9176
10390
|
// hijack server restart
|
|
9177
10391
|
const serverRestart = server.restart;
|
|
9178
10392
|
// since we set `server.hmr: false`, Vite does not auto restart itself
|
|
9179
10393
|
server.restart = async (...args) => {
|
|
9180
10394
|
await Promise.all(this._onRestartListeners.map((fn) => fn())), this.report("onServerRestart"), await this.close(), await serverRestart(...args);
|
|
9181
10395
|
}, server.watcher.on("change", async (file) => {
|
|
9182
|
-
file = normalize(file);
|
|
9183
|
-
const isConfig = file === server.config.configFile || this.projects.some((p) => p.vite.config.configFile === file);
|
|
9184
|
-
if (isConfig) await Promise.all(this._onRestartListeners.map((fn) => fn("config"))), this.report("onServerRestart", "config"), await this.close(), await serverRestart();
|
|
10396
|
+
if (file = normalize(file), file === server.config.configFile || this.projects.some((p) => p.vite.config.configFile === file)) await Promise.all(this._onRestartListeners.map((fn) => fn("config"))), this.report("onServerRestart", "config"), await this.close(), await serverRestart();
|
|
9185
10397
|
});
|
|
9186
10398
|
}
|
|
9187
10399
|
this.cache.results.setConfig(resolved.root, resolved.cache);
|
|
@@ -9190,19 +10402,22 @@ class Vitest {
|
|
|
9190
10402
|
} catch {}
|
|
9191
10403
|
const projects = await this.resolveProjects(this._cliOptions);
|
|
9192
10404
|
if (this.projects = projects, await Promise.all(projects.flatMap((project) => {
|
|
9193
|
-
|
|
9194
|
-
return hooks.map((hook) => hook({
|
|
10405
|
+
return project.vite.config.getSortedPluginHooks("configureVitest").map((hook) => hook({
|
|
9195
10406
|
project,
|
|
9196
10407
|
vitest: this,
|
|
9197
10408
|
injectTestProjects: this.injectTestProject
|
|
9198
10409
|
}));
|
|
9199
10410
|
})), this._cliOptions.browser?.enabled) {
|
|
9200
|
-
|
|
9201
|
-
if (!browserProjects.length) throw new Error(`Vitest received --browser flag, but no project had a browser configuration.`);
|
|
10411
|
+
if (!this.projects.filter((p) => p.config.browser.enabled).length) throw new Error(`Vitest received --browser flag, but no project had a browser configuration.`);
|
|
9202
10412
|
}
|
|
9203
10413
|
if (!this.projects.length) {
|
|
9204
10414
|
const filter = toArray(resolved.project).join("\", \"");
|
|
9205
|
-
|
|
10415
|
+
if (filter) throw new Error(`No projects matched the filter "${filter}".`);
|
|
10416
|
+
{
|
|
10417
|
+
let error = `Vitest wasn't able to resolve any project.`;
|
|
10418
|
+
if (this.config.browser.enabled && !this.config.browser.instances?.length) error += ` Please, check that you specified the "browser.instances" option.`;
|
|
10419
|
+
throw new Error(error);
|
|
10420
|
+
}
|
|
9206
10421
|
}
|
|
9207
10422
|
if (!this.coreWorkspaceProject) this.coreWorkspaceProject = TestProject._createBasicProject(this);
|
|
9208
10423
|
if (this.config.testNamePattern) this.configOverride.testNamePattern = this.config.testNamePattern;
|
|
@@ -9317,7 +10532,7 @@ class Vitest {
|
|
|
9317
10532
|
}, await this.report("onInit", this);
|
|
9318
10533
|
const specifications = [];
|
|
9319
10534
|
for (const file of files) {
|
|
9320
|
-
const
|
|
10535
|
+
const specification = this.getProjectByName(file.projectName || "").createSpecification(file.filepath, void 0, file.pool);
|
|
9321
10536
|
specifications.push(specification);
|
|
9322
10537
|
}
|
|
9323
10538
|
await this._testRun.start(specifications).catch(noop);
|
|
@@ -9327,6 +10542,12 @@ class Vitest {
|
|
|
9327
10542
|
unhandledErrors: this.state.getUnhandledErrors()
|
|
9328
10543
|
};
|
|
9329
10544
|
}
|
|
10545
|
+
/**
|
|
10546
|
+
* Returns the seed, if tests are running in a random order.
|
|
10547
|
+
*/
|
|
10548
|
+
getSeed() {
|
|
10549
|
+
return this.config.sequence.seed ?? null;
|
|
10550
|
+
}
|
|
9330
10551
|
/** @internal */
|
|
9331
10552
|
async _reportFileTask(file) {
|
|
9332
10553
|
const project = this.getProjectByName(file.projectName || "");
|
|
@@ -9650,10 +10871,8 @@ class Vitest {
|
|
|
9650
10871
|
*/
|
|
9651
10872
|
invalidateFile(filepath) {
|
|
9652
10873
|
this.projects.forEach(({ vite, browser }) => {
|
|
9653
|
-
|
|
9654
|
-
|
|
9655
|
-
const modules = moduleGraph.getModulesByFile(filepath);
|
|
9656
|
-
modules?.forEach((module) => moduleGraph.invalidateModule(module));
|
|
10874
|
+
[...Object.values(vite.environments), ...Object.values(browser?.vite.environments || {})].forEach(({ moduleGraph }) => {
|
|
10875
|
+
moduleGraph.getModulesByFile(filepath)?.forEach((module) => moduleGraph.invalidateModule(module));
|
|
9657
10876
|
});
|
|
9658
10877
|
});
|
|
9659
10878
|
}
|
|
@@ -9703,7 +10922,7 @@ class Vitest {
|
|
|
9703
10922
|
async exit(force = false) {
|
|
9704
10923
|
if (setTimeout(() => {
|
|
9705
10924
|
this.report("onProcessTimeout").then(() => {
|
|
9706
|
-
if (console.warn(`close timed out after ${this.config.teardownTimeout}ms`),
|
|
10925
|
+
if (console.warn(`close timed out after ${this.config.teardownTimeout}ms`), !this.pool) {
|
|
9707
10926
|
const runningServers = [this._vite, ...this.projects.map((p) => p._vite)].filter(Boolean).length;
|
|
9708
10927
|
if (runningServers === 1) console.warn("Tests closed successfully but something prevents Vite server from exiting");
|
|
9709
10928
|
else if (runningServers > 1) console.warn(`Tests closed successfully but something prevents ${runningServers} Vite servers from exiting`);
|
|
@@ -9776,8 +10995,7 @@ class Vitest {
|
|
|
9776
10995
|
matchesProjectFilter(name) {
|
|
9777
10996
|
const projects = this._config?.project || this._cliOptions?.project;
|
|
9778
10997
|
return !projects || !projects.length ? true : toArray(projects).some((project) => {
|
|
9779
|
-
|
|
9780
|
-
return regexp.test(name);
|
|
10998
|
+
return wildcardPatternToRegExp(project).test(name);
|
|
9781
10999
|
});
|
|
9782
11000
|
}
|
|
9783
11001
|
}
|
|
@@ -9806,15 +11024,12 @@ async function VitestPlugin(options = {}, vitest = new Vitest("test", deepClone(
|
|
|
9806
11024
|
// however to allow vitest plugins to modify vitest config values
|
|
9807
11025
|
// this is repeated in configResolved where the config is final
|
|
9808
11026
|
const testConfig = deepMerge({}, configDefaults, removeUndefinedValues(viteConfig.test ?? {}), options);
|
|
9809
|
-
testConfig.api = resolveApiServerConfig(testConfig, defaultPort);
|
|
9810
|
-
// store defines for globalThis to make them
|
|
9811
|
-
// reassignable when running in worker in src/runtime/setup.ts
|
|
9812
|
-
const defines = deleteDefineConfig(viteConfig);
|
|
9813
|
-
options.defines = defines;
|
|
11027
|
+
testConfig.api = resolveApiServerConfig(testConfig, defaultPort), options.defines = deleteDefineConfig(viteConfig);
|
|
9814
11028
|
let open = false;
|
|
9815
11029
|
if (testConfig.ui && testConfig.open) open = testConfig.uiBase ?? "/__vitest__/";
|
|
9816
11030
|
const resolveOptions = getDefaultResolveOptions();
|
|
9817
11031
|
let config = {
|
|
11032
|
+
base: "/",
|
|
9818
11033
|
root: viteConfig.test?.root || options.root,
|
|
9819
11034
|
define: { "process.env.NODE_ENV": "process.env.NODE_ENV" },
|
|
9820
11035
|
resolve: {
|
|
@@ -9838,10 +11053,6 @@ async function VitestPlugin(options = {}, vitest = new Vitest("test", deepClone(
|
|
|
9838
11053
|
__vitest__: { dev: {} }
|
|
9839
11054
|
},
|
|
9840
11055
|
test: {
|
|
9841
|
-
poolOptions: {
|
|
9842
|
-
threads: { isolate: options.poolOptions?.threads?.isolate ?? options.isolate ?? testConfig.poolOptions?.threads?.isolate ?? viteConfig.test?.isolate },
|
|
9843
|
-
forks: { isolate: options.poolOptions?.forks?.isolate ?? options.isolate ?? testConfig.poolOptions?.forks?.isolate ?? viteConfig.test?.isolate }
|
|
9844
|
-
},
|
|
9845
11056
|
root: testConfig.root ?? viteConfig.test?.root,
|
|
9846
11057
|
deps: testConfig.deps ?? viteConfig.test?.deps
|
|
9847
11058
|
}
|
|
@@ -9925,7 +11136,7 @@ function removeUndefinedValues(obj) {
|
|
|
9925
11136
|
}
|
|
9926
11137
|
|
|
9927
11138
|
async function createVitest(mode, options, viteOverrides = {}, vitestOptions = {}) {
|
|
9928
|
-
const ctx = new Vitest(mode, deepClone(options), vitestOptions), root = slash(resolve$1(options.root || process.cwd())), configPath = options.config === false ? false : options.config ? resolve$1(root, options.config) :
|
|
11139
|
+
const ctx = new Vitest(mode, deepClone(options), vitestOptions), root = slash(resolve$1(options.root || process.cwd())), configPath = options.config === false ? false : options.config ? resolve$1(root, options.config) : any(configFiles, { cwd: root });
|
|
9929
11140
|
options.config = configPath;
|
|
9930
11141
|
const { browser: _removeBrowser,...restOptions } = options, config = {
|
|
9931
11142
|
configFile: configPath,
|
|
@@ -9977,9 +11188,11 @@ class WatchFilter {
|
|
|
9977
11188
|
this.write(`${ESC}1G${ESC}0J`), onSubmit(void 0);
|
|
9978
11189
|
return;
|
|
9979
11190
|
case key?.name === "enter":
|
|
9980
|
-
case key?.name === "return":
|
|
9981
|
-
|
|
11191
|
+
case key?.name === "return": {
|
|
11192
|
+
const selection = this.results[this.selectionIndex], result = typeof selection === "string" ? selection : selection?.key;
|
|
11193
|
+
onSubmit(result || this.currentKeyword || ""), this.currentKeyword = void 0;
|
|
9982
11194
|
break;
|
|
11195
|
+
}
|
|
9983
11196
|
case key?.name === "up":
|
|
9984
11197
|
if (this.selectionIndex && this.selectionIndex > 0) this.selectionIndex--;
|
|
9985
11198
|
else this.selectionIndex = -1;
|
|
@@ -10041,7 +11254,7 @@ ${c.dim(` ...and ${remainingResultCount} more ${remainingResultCount === 1 ? "
|
|
|
10041
11254
|
this.stdout.write(data);
|
|
10042
11255
|
}
|
|
10043
11256
|
getLastResults() {
|
|
10044
|
-
return this.results;
|
|
11257
|
+
return this.results.map((r) => typeof r === "string" ? r : r.toString());
|
|
10045
11258
|
}
|
|
10046
11259
|
}
|
|
10047
11260
|
|
|
@@ -10067,6 +11280,26 @@ ${c.bold(" Watch Usage")}
|
|
|
10067
11280
|
${keys.map((i) => c.dim(" press ") + c.reset([i[0]].flat().map(c.bold).join(", ")) + c.dim(` to ${i[1]}`)).join("\n")}
|
|
10068
11281
|
`);
|
|
10069
11282
|
}
|
|
11283
|
+
function* traverseFilteredTestNames(parentName, filter, t) {
|
|
11284
|
+
if (isTestCase(t)) {
|
|
11285
|
+
if (t.name.match(filter)) {
|
|
11286
|
+
const displayName = `${parentName} > ${t.name}`;
|
|
11287
|
+
yield {
|
|
11288
|
+
key: t.name,
|
|
11289
|
+
toString: () => displayName
|
|
11290
|
+
};
|
|
11291
|
+
}
|
|
11292
|
+
} else {
|
|
11293
|
+
parentName = parentName.length ? `${parentName} > ${t.name}` : t.name;
|
|
11294
|
+
for (const task of t.tasks) yield* traverseFilteredTestNames(parentName, filter, task);
|
|
11295
|
+
}
|
|
11296
|
+
}
|
|
11297
|
+
function* getFilteredTestNames(pattern, suite) {
|
|
11298
|
+
try {
|
|
11299
|
+
const reg = new RegExp(pattern), files = /* @__PURE__ */ new Set();
|
|
11300
|
+
for (const file of suite) if (!files.has(file.name)) files.add(file.name), yield* traverseFilteredTestNames("", reg, file);
|
|
11301
|
+
} catch {}
|
|
11302
|
+
}
|
|
10070
11303
|
function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
|
|
10071
11304
|
let latestFilename = "";
|
|
10072
11305
|
async function _keypressHandler(str, key) {
|
|
@@ -10116,15 +11349,8 @@ function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
|
|
|
10116
11349
|
}
|
|
10117
11350
|
async function inputNamePattern() {
|
|
10118
11351
|
off();
|
|
10119
|
-
const
|
|
10120
|
-
|
|
10121
|
-
try {
|
|
10122
|
-
const reg = new RegExp(str);
|
|
10123
|
-
return tests.map((test) => test.name).filter((testName) => testName.match(reg));
|
|
10124
|
-
} catch {
|
|
10125
|
-
// `new RegExp` may throw error when input is invalid regexp
|
|
10126
|
-
return [];
|
|
10127
|
-
}
|
|
11352
|
+
const filter = await new WatchFilter("Input test name pattern (RegExp)", stdin, stdout).filter((str) => {
|
|
11353
|
+
return [...getFilteredTestNames(str, ctx.state.getFiles())];
|
|
10128
11354
|
});
|
|
10129
11355
|
if (on(), typeof filter === "undefined") return;
|
|
10130
11356
|
const files = ctx.state.getFilepaths(), cliFiles = ctx.config.standalone && !files.length ? await ctx._globTestFilepaths() : void 0;
|
|
@@ -10143,8 +11369,7 @@ function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
|
|
|
10143
11369
|
async function inputFilePattern() {
|
|
10144
11370
|
off();
|
|
10145
11371
|
const watchFilter = new WatchFilter("Input filename pattern", stdin, stdout), filter = await watchFilter.filter(async (str) => {
|
|
10146
|
-
|
|
10147
|
-
return specifications.map((specification) => relative(ctx.config.root, specification.moduleId)).filter((file, index, all) => all.indexOf(file) === index);
|
|
11372
|
+
return (await ctx.globTestSpecifications([str])).map((specification) => relative(ctx.config.root, specification.moduleId)).filter((file, index, all) => all.indexOf(file) === index);
|
|
10148
11373
|
});
|
|
10149
11374
|
if (on(), typeof filter === "undefined") return;
|
|
10150
11375
|
latestFilename = filter?.trim() || "";
|
|
@@ -10296,4 +11521,4 @@ var cliApi = /*#__PURE__*/Object.freeze({
|
|
|
10296
11521
|
startVitest: startVitest
|
|
10297
11522
|
});
|
|
10298
11523
|
|
|
10299
|
-
export { FilesNotFoundError as F, GitNotFoundError as G, Vitest as V, VitestPlugin as a, VitestPackageInstaller as b, createVitest as c,
|
|
11524
|
+
export { FilesNotFoundError as F, GitNotFoundError as G, ThreadsPoolWorker as T, Vitest as V, VitestPlugin as a, VitestPackageInstaller as b, createVitest as c, createMethodsRPC as d, escapeTestName as e, ForksPoolWorker as f, getFilePoolName as g, TypecheckPoolWorker as h, isValidApiRequest as i, VmForksPoolWorker as j, VmThreadsPoolWorker as k, experimental_getRunnerTask as l, registerConsoleShortcuts as m, createViteLogger as n, createDebugger as o, cliApi as p, resolveFsAllow as r, startVitest as s };
|