vitest 5.0.0-beta.4 → 5.0.0-beta.6
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 +0 -17
- package/dist/browser.d.ts +8 -6
- package/dist/browser.js +5 -3
- package/dist/chunks/artifact.BNnEI1sJ.js +2770 -0
- package/dist/chunks/{base.BEGVMQrS.js → base.BKfXyFN7.js} +12 -12
- package/dist/chunks/browser.d.DoM6vBOs.d.ts +64 -0
- package/dist/chunks/{cac.CyXAEMkE.js → cac.DKrA6qEB.js} +15 -7
- package/dist/chunks/{cli-api.DJMXq34b.js → cli-api.cZcvaUFI.js} +491 -852
- package/dist/chunks/config.d.CKWK3nld.d.ts +2920 -0
- package/dist/chunks/{console.B3IRP8fX.js → console.DjVIMaXT.js} +4 -4
- package/dist/chunks/{coverage.d.g2xbl2sP.d.ts → coverage.d.CCNrKR65.d.ts} +2 -0
- package/dist/chunks/{creator.D66cVXYh.js → creator.C4nK7Krt.js} +19 -4
- package/dist/chunks/{defaults.CUUnbOrq.js → defaults.B_pFOTYy.js} +1 -1
- package/dist/chunks/{globals.BuY-yD0m.js → globals.CzUA1xn8.js} +12 -12
- package/dist/chunks/{index.CcluKS59.js → index.B4UTDegh.js} +6 -6
- package/dist/chunks/{nativeModuleRunner.BOeMnHl4.js → index.CesbTg1C.js} +3 -34
- package/dist/chunks/{index.CE58PZNH.js → index.D6NDM5O1.js} +515 -121
- package/dist/chunks/{index.nQFVd50u.js → index.DNRmy2jU.js} +32 -10
- package/dist/chunks/{index.og1WyBLx.js → index.Djaij2xr.js} +660 -58
- package/dist/chunks/{init-forks.Ce3vGWgL.js → init-forks.BG4bCDny.js} +1 -1
- package/dist/chunks/{init-threads.8e1OLv5v.js → init-threads.DLtIP5Qq.js} +1 -1
- package/dist/chunks/{init.6qx-LaHs.js → init.DTAQFCjl.js} +10 -6
- package/dist/chunks/{nativeModuleMocker.DDZfQXLs.js → nativeModuleMocker.BKtCThoO.js} +2 -3
- package/dist/chunks/nativeModuleRunner.WlMdOB52.js +36 -0
- package/dist/chunks/{node.CwFbQqI1.js → node.C9I1sbE9.js} +1 -2
- package/dist/chunks/{plugin.d.B7MTG_Fe.d.ts → plugin.d.Bpge8Jye.d.ts} +94 -37
- package/dist/chunks/{rpc.DFRWVnRh.js → rpc.DZEh5xnQ.js} +1 -1
- package/dist/chunks/{rpc.d.OQ_EZi1Z.d.ts → rpc.d.C6-dGZ2f.d.ts} +1 -2
- package/dist/chunks/{setup-common.DdEF_hkE.js → setup-common.ZNdeu5wa.js} +2 -2
- package/dist/chunks/{global.d.BtKPuz2X.d.ts → task-utils.d.mMDXJF_I.d.ts} +30 -89
- package/dist/chunks/{utils.BX5Fg8C4.js → utils.DYj33du9.js} +1 -17
- package/dist/chunks/{vm.Bu7mmcZq.js → vm.CeHWcIrU.js} +4 -4
- package/dist/chunks/{worker.d.yR22cs6X.d.ts → worker.d.JES7ffPK.d.ts} +3 -3
- package/dist/cli.js +1 -1
- package/dist/config.cjs +1 -1
- package/dist/config.d.ts +12 -13
- package/dist/config.js +1 -1
- package/dist/index.d.ts +416 -21
- package/dist/index.js +9 -9
- package/dist/module-evaluator.d.ts +7 -4
- package/dist/module-evaluator.js +7 -0
- package/dist/node.d.ts +31 -18
- package/dist/node.js +22 -16
- package/dist/runtime.d.ts +9 -3
- package/dist/runtime.js +4 -5
- package/dist/task-utils.js +296 -0
- package/dist/worker.d.ts +7 -6
- package/dist/worker.js +15 -14
- package/dist/workers/forks.js +16 -15
- package/dist/workers/runVmTests.js +10 -10
- package/dist/workers/threads.js +16 -15
- package/dist/workers/vmForks.js +8 -8
- package/dist/workers/vmThreads.js +8 -8
- package/package.json +18 -19
- package/dist/chunks/browser.d.BGxB4Xum.d.ts +0 -878
- package/dist/chunks/config.d.DXq1aBpy.d.ts +0 -244
- package/dist/chunks/environment.d-DOJxxZV9.d.DOJxxZV9.d.ts +0 -17
- package/dist/chunks/general.d.DFAHgpC2.d.ts +0 -247
|
@@ -1,33 +1,31 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import fs__default, { promises, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync, statSync } from 'node:fs';
|
|
3
|
-
import { join, dirname, basename, isAbsolute, resolve as resolve$1, relative,
|
|
3
|
+
import { join, dirname, basename, isAbsolute, resolve as resolve$1, relative, extname, normalize } from 'pathe';
|
|
4
4
|
import { r as resolveCoverageProviderModule, C as CoverageProviderMap } from './coverage.CTzCuANN.js';
|
|
5
5
|
import path, { resolve as resolve$2 } from 'node:path';
|
|
6
|
-
import { unique, createDefer, slash,
|
|
7
|
-
import {
|
|
8
|
-
import { r as resolveModule, i as isPackageExists, N as NativeModuleRunner } from './nativeModuleRunner.BOeMnHl4.js';
|
|
6
|
+
import { unique, createDefer, slash, cleanUrl, withTrailingSlash, wrapId, isExternalUrl, unwrapId, isPrimitive, toArray, noop as noop$1, deepMerge, nanoid, deepClone, sanitizeFilePath, notNullish } from '@vitest/utils/helpers';
|
|
7
|
+
import { i as isPackageExists, r as resolveModule } from './index.CesbTg1C.js';
|
|
9
8
|
import * as vite from 'vite';
|
|
10
|
-
import { parseAst, searchForWorkspaceRoot,
|
|
11
|
-
import { c as
|
|
9
|
+
import { parseAst, searchForWorkspaceRoot, fetchModule, version, createServer, isFileLoadingAllowed, normalizePath as normalizePath$1, isRunnableDevEnvironment, mergeConfig } from 'vite';
|
|
10
|
+
import { h as hash, b as resolveCoverageReporters, A as API_TOKEN_FILE, c as isBrowserEnabled, r as resolveConfig$1, R as RandomSequencer, a as resolveApiServerConfig, f as findConfigFile, p as prompt } from './index.Djaij2xr.js';
|
|
12
11
|
import * as nodeos from 'node:os';
|
|
13
12
|
import nodeos__default, { tmpdir, hostname } from 'node:os';
|
|
14
|
-
import { getTests, createFileTask as createFileTask$1, createTaskName, validateTags, calculateSuiteHash, hasFailed, generateFileHash, limitConcurrency, getTestName, getSuites, getTasks, getFullName, createTagsFilter, someTasksAreOnly, interpretTaskModes, isTestCase } from '@vitest/runner/utils';
|
|
15
13
|
import { serializeValue } from '@vitest/utils/serialize';
|
|
16
|
-
import { v as version$1 } from './cac.
|
|
14
|
+
import { v as version$1 } from './cac.DKrA6qEB.js';
|
|
17
15
|
import { distDir, rootDir } from '../path.js';
|
|
16
|
+
import { w as validateTags, x as limitConcurrency, y as createTagsFilter } from './artifact.BNnEI1sJ.js';
|
|
17
|
+
import { N as NativeModuleRunner } from './nativeModuleRunner.WlMdOB52.js';
|
|
18
|
+
import { createFileTask as createFileTask$1, createTaskName, calculateSuiteHash, convertTasksToEvents, hasFailed, generateFileHash, getTestName, hasFailedSnapshot, getTests, getSuites, getTasks, getFullName, someTasksAreOnly, interpretTaskModes } from '../task-utils.js';
|
|
18
19
|
import { Traces } from '../traces.js';
|
|
19
20
|
import { createDebug } from 'obug';
|
|
20
|
-
import crypto, { createHash } from 'node:crypto';
|
|
21
21
|
import { rm, readFile, writeFile, rename, stat, unlink, mkdir, readdir, copyFile } from 'node:fs/promises';
|
|
22
22
|
import c from 'tinyrainbow';
|
|
23
|
-
import
|
|
24
|
-
import { isDynamicPattern, glob } from 'tinyglobby';
|
|
25
|
-
import { c as configDefaults, e as benchmarkConfigDefaults, a as coverageConfigDefaults } from './defaults.CUUnbOrq.js';
|
|
26
|
-
import { a as isTTY, b as isWindows, i as isForceColor } from './env.BKKtU2WC.js';
|
|
27
|
-
import { w as withLabel, t as truncateString, e as errorBanner, F as F_POINTER, d as divider, f as formatProjectName, a as formatTimeString, b as taskFail, s as separator, c as F_CHECK, g as F_DOWN_RIGHT, h as getStateSymbol, r as renderSnapshotSummary, p as padSummaryTitle, i as getStateString$1, j as formatTime, k as countTestErrors, l as F_TREE_NODE_END, m as F_TREE_NODE_MIDDLE, n as noun, o as F_RIGHT } from './utils.DKODp04v.js';
|
|
28
|
-
import { isAgent, isCI, provider } from 'std-env';
|
|
23
|
+
import crypto, { createHash } from 'node:crypto';
|
|
29
24
|
import module$1, { createRequire, builtinModules, isBuiltin as isBuiltin$1 } from 'node:module';
|
|
25
|
+
import url, { fileURLToPath, pathToFileURL } from 'node:url';
|
|
30
26
|
import pm from 'picomatch';
|
|
27
|
+
import { glob, isDynamicPattern } from 'tinyglobby';
|
|
28
|
+
import { a as coverageConfigDefaults, c as configDefaults } from './defaults.B_pFOTYy.js';
|
|
31
29
|
import { VitestModuleEvaluator } from '#module-evaluator';
|
|
32
30
|
import { ModuleRunner } from 'vite/module-runner';
|
|
33
31
|
import { Console } from 'node:console';
|
|
@@ -39,6 +37,8 @@ import { performance as performance$1 } from 'node:perf_hooks';
|
|
|
39
37
|
import { x } from 'tinyexec';
|
|
40
38
|
import { positionToOffset, lineSplitRE } from '@vitest/utils/offset';
|
|
41
39
|
import { defaultStackIgnorePatterns, parseErrorStacktrace, parseStacktrace } from '@vitest/utils/source-map';
|
|
40
|
+
import { t as truncateString, e as errorBanner, F as F_POINTER, d as divider, f as formatProjectName, w as withLabel, a as formatTimeString, b as taskFail, s as separator, c as F_CHECK, g as F_DOWN_RIGHT, h as getStateSymbol, r as renderSnapshotSummary, p as padSummaryTitle, i as getStateString$1, j as formatTime, k as countTestErrors, l as F_TREE_NODE_END, m as F_TREE_NODE_MIDDLE, n as noun, o as F_RIGHT } from './utils.DKODp04v.js';
|
|
41
|
+
import { a as isTTY, b as isWindows, i as isForceColor } from './env.BKKtU2WC.js';
|
|
42
42
|
import { isatty } from 'node:tty';
|
|
43
43
|
import EventEmitter$1, { EventEmitter } from 'node:events';
|
|
44
44
|
import { c as createBirpc } from './index.Chj8NDwU.js';
|
|
@@ -47,7 +47,7 @@ import { fork } from 'node:child_process';
|
|
|
47
47
|
import { Worker } from 'node:worker_threads';
|
|
48
48
|
import require$$0$3 from 'events';
|
|
49
49
|
import require$$1$1 from 'https';
|
|
50
|
-
import require$$2 from 'http';
|
|
50
|
+
import require$$2$1 from 'http';
|
|
51
51
|
import require$$3 from 'net';
|
|
52
52
|
import require$$4 from 'tls';
|
|
53
53
|
import require$$1 from 'crypto';
|
|
@@ -55,6 +55,9 @@ import require$$0$2 from 'stream';
|
|
|
55
55
|
import require$$7 from 'url';
|
|
56
56
|
import require$$0 from 'zlib';
|
|
57
57
|
import require$$0$1 from 'buffer';
|
|
58
|
+
import require$$2 from 'util';
|
|
59
|
+
import { A as API_PATH, c as configFiles, d as defaultBrowserPort, a as defaultPort } from './constants.-juJ8b_4.js';
|
|
60
|
+
import { isAgent } from 'std-env';
|
|
58
61
|
import MagicString from 'magic-string';
|
|
59
62
|
import { hoistMocksPlugin, automockPlugin } from '@vitest/mocker/node';
|
|
60
63
|
import { KNOWN_ASSET_RE } from '@vitest/utils/constants';
|
|
@@ -177,76 +180,6 @@ function createLocationsIndexMap(source) {
|
|
|
177
180
|
return map;
|
|
178
181
|
}
|
|
179
182
|
|
|
180
|
-
function hasFailedSnapshot(suite) {
|
|
181
|
-
return getTests(suite).some((s) => {
|
|
182
|
-
return s.result?.errors?.some((e) => typeof e?.message === "string" && e.message.match(/Snapshot .* mismatched/));
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
function convertTasksToEvents(file, onTask) {
|
|
186
|
-
const packs = [];
|
|
187
|
-
const events = [];
|
|
188
|
-
function visit(suite) {
|
|
189
|
-
onTask?.(suite);
|
|
190
|
-
packs.push([
|
|
191
|
-
suite.id,
|
|
192
|
-
suite.result,
|
|
193
|
-
suite.meta
|
|
194
|
-
]);
|
|
195
|
-
events.push([
|
|
196
|
-
suite.id,
|
|
197
|
-
"suite-prepare",
|
|
198
|
-
void 0
|
|
199
|
-
]);
|
|
200
|
-
suite.tasks.forEach((task) => {
|
|
201
|
-
if (task.type === "suite") visit(task);
|
|
202
|
-
else {
|
|
203
|
-
onTask?.(task);
|
|
204
|
-
if (suite.mode !== "skip" && suite.mode !== "todo") {
|
|
205
|
-
packs.push([
|
|
206
|
-
task.id,
|
|
207
|
-
task.result,
|
|
208
|
-
task.meta
|
|
209
|
-
]);
|
|
210
|
-
events.push([
|
|
211
|
-
task.id,
|
|
212
|
-
"test-prepare",
|
|
213
|
-
void 0
|
|
214
|
-
]);
|
|
215
|
-
task.annotations.forEach((annotation) => {
|
|
216
|
-
events.push([
|
|
217
|
-
task.id,
|
|
218
|
-
"test-annotation",
|
|
219
|
-
{ annotation }
|
|
220
|
-
]);
|
|
221
|
-
});
|
|
222
|
-
task.artifacts.forEach((artifact) => {
|
|
223
|
-
events.push([
|
|
224
|
-
task.id,
|
|
225
|
-
"test-artifact",
|
|
226
|
-
{ artifact }
|
|
227
|
-
]);
|
|
228
|
-
});
|
|
229
|
-
events.push([
|
|
230
|
-
task.id,
|
|
231
|
-
"test-finished",
|
|
232
|
-
void 0
|
|
233
|
-
]);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
events.push([
|
|
238
|
-
suite.id,
|
|
239
|
-
"suite-finished",
|
|
240
|
-
void 0
|
|
241
|
-
]);
|
|
242
|
-
}
|
|
243
|
-
visit(file);
|
|
244
|
-
return {
|
|
245
|
-
packs,
|
|
246
|
-
events
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
|
|
250
183
|
// src/vlq.ts
|
|
251
184
|
var comma = ",".charCodeAt(0);
|
|
252
185
|
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
@@ -1567,17 +1500,29 @@ class BrowserSessions {
|
|
|
1567
1500
|
this.sessions.delete(sessionId);
|
|
1568
1501
|
}
|
|
1569
1502
|
createSession(sessionId, project, pool, options) {
|
|
1570
|
-
// this promise
|
|
1503
|
+
// this promise waits until the orchestrator is ready to accept RPC calls
|
|
1571
1504
|
const defer = createDefer();
|
|
1505
|
+
let isConnected = false;
|
|
1506
|
+
let isReady = false;
|
|
1572
1507
|
const timeout = setTimeout(() => {
|
|
1573
1508
|
defer.reject(/* @__PURE__ */ new Error(`Failed to connect to the browser session "${sessionId}" [${project.name}] within the timeout.`));
|
|
1574
1509
|
}, project.vitest.config.browser.connectTimeout ?? 6e4).unref();
|
|
1510
|
+
const resolveIfReady = () => {
|
|
1511
|
+
if (!isConnected || !isReady) return;
|
|
1512
|
+
defer.resolve();
|
|
1513
|
+
clearTimeout(timeout);
|
|
1514
|
+
};
|
|
1575
1515
|
this.sessions.set(sessionId, {
|
|
1576
1516
|
project,
|
|
1577
1517
|
otelCarrier: options?.otelCarrier,
|
|
1518
|
+
concurrencyId: 0,
|
|
1578
1519
|
connected: () => {
|
|
1579
|
-
|
|
1580
|
-
|
|
1520
|
+
isConnected = true;
|
|
1521
|
+
resolveIfReady();
|
|
1522
|
+
},
|
|
1523
|
+
ready: () => {
|
|
1524
|
+
isReady = true;
|
|
1525
|
+
resolveIfReady();
|
|
1581
1526
|
},
|
|
1582
1527
|
fail: (error) => {
|
|
1583
1528
|
defer.resolve();
|
|
@@ -1589,8 +1534,6 @@ class BrowserSessions {
|
|
|
1589
1534
|
}
|
|
1590
1535
|
}
|
|
1591
1536
|
|
|
1592
|
-
const hash = crypto.hash ?? ((algorithm, data, outputEncoding) => crypto.createHash(algorithm).update(data).digest(outputEncoding));
|
|
1593
|
-
|
|
1594
1537
|
class FilesStatsCache {
|
|
1595
1538
|
cache = /* @__PURE__ */ new Map();
|
|
1596
1539
|
getStats(key) {
|
|
@@ -2151,597 +2094,6 @@ function tryStatSync(file) {
|
|
|
2151
2094
|
} catch {}
|
|
2152
2095
|
}
|
|
2153
2096
|
|
|
2154
|
-
function getWorkersCountByPercentage(percent) {
|
|
2155
|
-
const maxWorkersCount = nodeos__default.availableParallelism?.() ?? nodeos__default.cpus().length;
|
|
2156
|
-
const workersCountByPercentage = Math.round(Number.parseInt(percent) / 100 * maxWorkersCount);
|
|
2157
|
-
return Math.max(1, Math.min(maxWorkersCount, workersCountByPercentage));
|
|
2158
|
-
}
|
|
2159
|
-
|
|
2160
|
-
class BaseSequencer {
|
|
2161
|
-
ctx;
|
|
2162
|
-
constructor(ctx) {
|
|
2163
|
-
this.ctx = ctx;
|
|
2164
|
-
}
|
|
2165
|
-
// async so it can be extended by other sequencers
|
|
2166
|
-
async shard(files) {
|
|
2167
|
-
const { config } = this.ctx;
|
|
2168
|
-
const { index, count } = config.shard;
|
|
2169
|
-
const [shardStart, shardEnd] = this.calculateShardRange(files.length, index, count);
|
|
2170
|
-
return [...files].map((spec) => {
|
|
2171
|
-
const specPath = resolve$1(slash(config.root), slash(spec.moduleId))?.slice(config.root.length);
|
|
2172
|
-
return {
|
|
2173
|
-
spec,
|
|
2174
|
-
hash: hash("sha1", specPath, "hex")
|
|
2175
|
-
};
|
|
2176
|
-
}).sort((a, b) => a.hash < b.hash ? -1 : a.hash > b.hash ? 1 : 0).slice(shardStart, shardEnd).map(({ spec }) => spec);
|
|
2177
|
-
}
|
|
2178
|
-
// async so it can be extended by other sequencers
|
|
2179
|
-
async sort(files) {
|
|
2180
|
-
const cache = this.ctx.cache;
|
|
2181
|
-
return [...files].sort((a, b) => {
|
|
2182
|
-
// "sequence.groupOrder" is higher priority
|
|
2183
|
-
const groupOrderDiff = a.project.config.sequence.groupOrder - b.project.config.sequence.groupOrder;
|
|
2184
|
-
if (groupOrderDiff !== 0) return groupOrderDiff;
|
|
2185
|
-
// Projects run sequential
|
|
2186
|
-
if (a.project.name !== b.project.name) return a.project.name < b.project.name ? -1 : 1;
|
|
2187
|
-
// Isolated run first
|
|
2188
|
-
if (a.project.config.isolate && !b.project.config.isolate) return -1;
|
|
2189
|
-
if (!a.project.config.isolate && b.project.config.isolate) return 1;
|
|
2190
|
-
const keyA = `${a.project.name}:${relative(this.ctx.config.root, a.moduleId)}`;
|
|
2191
|
-
const keyB = `${b.project.name}:${relative(this.ctx.config.root, b.moduleId)}`;
|
|
2192
|
-
const aState = cache.getFileTestResults(keyA);
|
|
2193
|
-
const bState = cache.getFileTestResults(keyB);
|
|
2194
|
-
if (!aState || !bState) {
|
|
2195
|
-
const statsA = cache.getFileStats(keyA);
|
|
2196
|
-
const statsB = cache.getFileStats(keyB);
|
|
2197
|
-
// run unknown first
|
|
2198
|
-
if (!statsA || !statsB) return !statsA && statsB ? -1 : !statsB && statsA ? 1 : 0;
|
|
2199
|
-
// run larger files first
|
|
2200
|
-
return statsB.size - statsA.size;
|
|
2201
|
-
}
|
|
2202
|
-
// run failed first
|
|
2203
|
-
if (aState.failed && !bState.failed) return -1;
|
|
2204
|
-
if (!aState.failed && bState.failed) return 1;
|
|
2205
|
-
// run longer first
|
|
2206
|
-
return bState.duration - aState.duration;
|
|
2207
|
-
});
|
|
2208
|
-
}
|
|
2209
|
-
// Calculate distributed shard range [start, end] distributed equally
|
|
2210
|
-
calculateShardRange(filesCount, index, count) {
|
|
2211
|
-
const baseShardSize = Math.floor(filesCount / count);
|
|
2212
|
-
const remainderTestFilesCount = filesCount % count;
|
|
2213
|
-
if (remainderTestFilesCount >= index) {
|
|
2214
|
-
const shardSize = baseShardSize + 1;
|
|
2215
|
-
return [shardSize * (index - 1), shardSize * index];
|
|
2216
|
-
}
|
|
2217
|
-
const shardStart = remainderTestFilesCount * (baseShardSize + 1) + (index - remainderTestFilesCount - 1) * baseShardSize;
|
|
2218
|
-
return [shardStart, shardStart + baseShardSize];
|
|
2219
|
-
}
|
|
2220
|
-
}
|
|
2221
|
-
|
|
2222
|
-
class RandomSequencer extends BaseSequencer {
|
|
2223
|
-
async sort(files) {
|
|
2224
|
-
const { sequence } = this.ctx.config;
|
|
2225
|
-
return shuffle(files, sequence.seed);
|
|
2226
|
-
}
|
|
2227
|
-
}
|
|
2228
|
-
|
|
2229
|
-
function resolvePath(path, root) {
|
|
2230
|
-
// local-pkg (mlly)'s resolveModule("./file", { paths: ["/some/root"] }) tries
|
|
2231
|
-
// /some/file
|
|
2232
|
-
// /some/file.js
|
|
2233
|
-
// /some/root/file
|
|
2234
|
-
// /some/root/file.js
|
|
2235
|
-
// etc.
|
|
2236
|
-
// but we don't want to resolve files from parent directories,
|
|
2237
|
-
// so we ensure passing "/" suffix such as "/some/root/"
|
|
2238
|
-
// https://github.com/unjs/mlly/blob/401d42983f6f3a9112658d67b0a92ba4fb1d7efa/src/resolve.ts#L104-L110
|
|
2239
|
-
return normalize(/* @__PURE__ */ resolveModule(path, { paths: [join(root, "/")] }) ?? resolve$1(root, path));
|
|
2240
|
-
}
|
|
2241
|
-
function parseInspector(inspect) {
|
|
2242
|
-
if (typeof inspect === "boolean" || inspect === void 0) return {};
|
|
2243
|
-
if (typeof inspect === "number") return { port: inspect };
|
|
2244
|
-
if (inspect.match(/https?:\//)) throw new Error(`Inspector host cannot be a URL. Use "host:port" instead of "${inspect}"`);
|
|
2245
|
-
const [host, port] = inspect.split(":");
|
|
2246
|
-
if (!port) return { host };
|
|
2247
|
-
return {
|
|
2248
|
-
host,
|
|
2249
|
-
port: Number(port) || defaultInspectPort
|
|
2250
|
-
};
|
|
2251
|
-
}
|
|
2252
|
-
/**
|
|
2253
|
-
* @deprecated Internal function
|
|
2254
|
-
*/
|
|
2255
|
-
function resolveApiServerConfig(options, defaultPort, parentApi, logger) {
|
|
2256
|
-
let api;
|
|
2257
|
-
if (options.ui && !options.api) api = { port: defaultPort };
|
|
2258
|
-
else if (options.api === true) api = { port: defaultPort };
|
|
2259
|
-
else if (typeof options.api === "number") api = { port: options.api };
|
|
2260
|
-
if (typeof options.api === "object") if (api) {
|
|
2261
|
-
if (options.api.port) api.port = options.api.port;
|
|
2262
|
-
if (options.api.strictPort) api.strictPort = options.api.strictPort;
|
|
2263
|
-
if (options.api.host) api.host = options.api.host;
|
|
2264
|
-
} else api = { ...options.api };
|
|
2265
|
-
if (api) {
|
|
2266
|
-
if (!api.port && !api.middlewareMode) api.port = defaultPort;
|
|
2267
|
-
} else api = { middlewareMode: true };
|
|
2268
|
-
// if the API server is exposed to network, disable write operations by default
|
|
2269
|
-
if (!api.middlewareMode && api.host && api.host !== "localhost" && api.host !== "127.0.0.1") {
|
|
2270
|
-
// assigned to browser
|
|
2271
|
-
if (parentApi) {
|
|
2272
|
-
if (api.allowWrite == null && api.allowExec == null) logger?.error(c.yellow(`${c.yellowBright(" WARNING ")} API server is exposed to network, disabling write and exec operations by default for security reasons. This can cause some APIs to not work as expected. Set \`browser.api.allowExec\` manually to hide this warning. See https://vitest.dev/config/browser/api for more details.`));
|
|
2273
|
-
}
|
|
2274
|
-
api.allowWrite ??= parentApi?.allowWrite ?? false;
|
|
2275
|
-
api.allowExec ??= parentApi?.allowExec ?? false;
|
|
2276
|
-
} else {
|
|
2277
|
-
api.allowWrite ??= parentApi?.allowWrite ?? true;
|
|
2278
|
-
api.allowExec ??= parentApi?.allowExec ?? true;
|
|
2279
|
-
}
|
|
2280
|
-
return api;
|
|
2281
|
-
}
|
|
2282
|
-
function resolveInlineWorkerOption(value) {
|
|
2283
|
-
if (typeof value === "string" && value.trim().endsWith("%")) return getWorkersCountByPercentage(value);
|
|
2284
|
-
else return Number(value);
|
|
2285
|
-
}
|
|
2286
|
-
// warn only once, check one PER PROCESS, not per instance,
|
|
2287
|
-
// that's why it's on a module-level
|
|
2288
|
-
let warnedTypeCheck = false;
|
|
2289
|
-
function resolveConfig$1(vitest, options, viteConfig) {
|
|
2290
|
-
const logger = vitest.logger;
|
|
2291
|
-
if (options.dom) {
|
|
2292
|
-
if (viteConfig.test?.environment != null && viteConfig.test.environment !== "happy-dom") logger.console.warn(withLabel("yellow", "Vitest", `Your config.test.environment ("${viteConfig.test.environment}") conflicts with --dom flag ("happy-dom"), ignoring "${viteConfig.test.environment}"`));
|
|
2293
|
-
options.environment = "happy-dom";
|
|
2294
|
-
}
|
|
2295
|
-
const resolved = {
|
|
2296
|
-
...configDefaults,
|
|
2297
|
-
...options,
|
|
2298
|
-
root: viteConfig.root
|
|
2299
|
-
};
|
|
2300
|
-
resolved.mode ??= viteConfig.mode ?? "test";
|
|
2301
|
-
if (resolved.retry && typeof resolved.retry === "object" && typeof resolved.retry.condition === "function") {
|
|
2302
|
-
logger.console.warn(c.yellow("Warning: retry.condition function cannot be used inside a config file. Use a RegExp pattern instead, or define the function in your test file."));
|
|
2303
|
-
resolved.retry = {
|
|
2304
|
-
...resolved.retry,
|
|
2305
|
-
condition: void 0
|
|
2306
|
-
};
|
|
2307
|
-
}
|
|
2308
|
-
if (options.pool && typeof options.pool !== "string") {
|
|
2309
|
-
resolved.pool = options.pool.name;
|
|
2310
|
-
resolved.poolRunner = options.pool;
|
|
2311
|
-
}
|
|
2312
|
-
if ("poolOptions" in resolved) logger.deprecate("`test.poolOptions` was removed in Vitest 4. All previous `poolOptions` are now top-level options. Please, refer to the migration guide: https://vitest.dev/guide/migration#pool-rework");
|
|
2313
|
-
resolved.pool ??= "forks";
|
|
2314
|
-
resolved.project = toArray(resolved.project);
|
|
2315
|
-
resolved.provide ??= {};
|
|
2316
|
-
// shallow copy tags array to avoid mutating user config
|
|
2317
|
-
resolved.tags = [...resolved.tags || []];
|
|
2318
|
-
const definedTags = /* @__PURE__ */ new Set();
|
|
2319
|
-
resolved.tags.forEach((tag) => {
|
|
2320
|
-
if (!tag.name || typeof tag.name !== "string") throw new Error(`Each tag defined in "test.tags" must have a "name" property, received: ${JSON.stringify(tag)}`);
|
|
2321
|
-
if (definedTags.has(tag.name)) throw new Error(`Tag name "${tag.name}" is already defined in "test.tags". Tag names must be unique.`);
|
|
2322
|
-
if (tag.name.match(/\s/)) throw new Error(`Tag name "${tag.name}" is invalid. Tag names cannot contain spaces.`);
|
|
2323
|
-
if (tag.name.match(/([!()*|&])/)) throw new Error(`Tag name "${tag.name}" is invalid. Tag names cannot contain "!", "*", "&", "|", "(", or ")".`);
|
|
2324
|
-
if (tag.name.match(/^\s*(and|or|not)\s*$/i)) throw new Error(`Tag name "${tag.name}" is invalid. Tag names cannot be a logical operator like "and", "or", "not".`);
|
|
2325
|
-
if (typeof tag.retry === "object" && typeof tag.retry.condition === "function") throw new TypeError(`Tag "${tag.name}": retry.condition function cannot be used inside a config file. Use a RegExp pattern instead, or define the function in your test file.`);
|
|
2326
|
-
if (tag.priority != null && (typeof tag.priority !== "number" || tag.priority < 0)) throw new TypeError(`Tag "${tag.name}": priority must be a non-negative number.`);
|
|
2327
|
-
definedTags.add(tag.name);
|
|
2328
|
-
});
|
|
2329
|
-
resolved.name = typeof options.name === "string" ? options.name : options.name?.label || "";
|
|
2330
|
-
resolved.color = typeof options.name !== "string" ? options.name?.color : void 0;
|
|
2331
|
-
if (resolved.environment === "browser") throw new Error(`Looks like you set "test.environment" to "browser". To enable Browser Mode, use "test.browser.enabled" instead.`);
|
|
2332
|
-
resolved.benchmark = {
|
|
2333
|
-
...benchmarkConfigDefaults,
|
|
2334
|
-
...resolved.benchmark
|
|
2335
|
-
};
|
|
2336
|
-
const inspector = resolved.inspect || resolved.inspectBrk;
|
|
2337
|
-
resolved.inspector = {
|
|
2338
|
-
...resolved.inspector,
|
|
2339
|
-
...parseInspector(inspector),
|
|
2340
|
-
enabled: !!inspector,
|
|
2341
|
-
waitForDebugger: options.inspector?.waitForDebugger ?? !!resolved.inspectBrk
|
|
2342
|
-
};
|
|
2343
|
-
if (viteConfig.base !== "/") resolved.base = viteConfig.base;
|
|
2344
|
-
resolved.clearScreen = resolved.clearScreen ?? viteConfig.clearScreen ?? true;
|
|
2345
|
-
if (options.shard) {
|
|
2346
|
-
if (resolved.watch) throw new Error("You cannot use --shard option with enabled watch");
|
|
2347
|
-
const [indexString, countString] = options.shard.split("/");
|
|
2348
|
-
const index = Math.abs(Number.parseInt(indexString, 10));
|
|
2349
|
-
const count = Math.abs(Number.parseInt(countString, 10));
|
|
2350
|
-
if (Number.isNaN(count) || count <= 0) throw new Error("--shard <count> must be a positive number");
|
|
2351
|
-
if (Number.isNaN(index) || index <= 0 || index > count) throw new Error("--shard <index> must be a positive number less then <count>");
|
|
2352
|
-
resolved.shard = {
|
|
2353
|
-
index,
|
|
2354
|
-
count
|
|
2355
|
-
};
|
|
2356
|
-
}
|
|
2357
|
-
if (resolved.standalone && !resolved.watch) throw new Error(`Vitest standalone mode requires --watch`);
|
|
2358
|
-
if (resolved.mergeReports && resolved.watch) throw new Error(`Cannot merge reports with --watch enabled`);
|
|
2359
|
-
if (resolved.maxWorkers) resolved.maxWorkers = resolveInlineWorkerOption(resolved.maxWorkers);
|
|
2360
|
-
if (!(options.fileParallelism ?? true))
|
|
2361
|
-
// ignore user config, parallelism cannot be implemented without limiting workers
|
|
2362
|
-
resolved.maxWorkers = 1;
|
|
2363
|
-
if (resolved.maxConcurrency === 0) {
|
|
2364
|
-
logger.console.warn(c.yellow(`The option "maxConcurrency" cannot be set to 0. Using default value ${configDefaults.maxConcurrency} instead.`));
|
|
2365
|
-
resolved.maxConcurrency = configDefaults.maxConcurrency;
|
|
2366
|
-
}
|
|
2367
|
-
if (resolved.inspect || resolved.inspectBrk) {
|
|
2368
|
-
if (resolved.maxWorkers !== 1) {
|
|
2369
|
-
const inspectOption = `--inspect${resolved.inspectBrk ? "-brk" : ""}`;
|
|
2370
|
-
throw new Error(`You cannot use ${inspectOption} without "--no-file-parallelism"`);
|
|
2371
|
-
}
|
|
2372
|
-
}
|
|
2373
|
-
// apply browser CLI options only if the config already has the browser config and not disabled manually
|
|
2374
|
-
if (vitest._cliOptions.browser && resolved.browser && (resolved.browser.enabled !== false || vitest._cliOptions.browser.enabled)) resolved.browser = mergeConfig(resolved.browser, vitest._cliOptions.browser);
|
|
2375
|
-
resolved.browser ??= {};
|
|
2376
|
-
const browser = resolved.browser;
|
|
2377
|
-
if (browser.enabled) {
|
|
2378
|
-
const instances = browser.instances;
|
|
2379
|
-
if (!browser.instances) browser.instances = [];
|
|
2380
|
-
// use `chromium` by default when the preview provider is specified
|
|
2381
|
-
// for a smoother experience. if chromium is not available, it will
|
|
2382
|
-
// open the default browser anyway
|
|
2383
|
-
if (!browser.instances.length && browser.provider?.name === "preview") browser.instances = [{ browser: "chromium" }];
|
|
2384
|
-
if (browser.name && instances?.length) {
|
|
2385
|
-
// --browser=chromium filters configs to a single one
|
|
2386
|
-
browser.instances = browser.instances.filter((instance) => instance.browser === browser.name);
|
|
2387
|
-
// if `instances` were defined, but now they are empty,
|
|
2388
|
-
// let's throw an error because the filter is invalid
|
|
2389
|
-
if (!browser.instances.length) throw new Error([`"browser.instances" was set in the config, but the array is empty. Define at least one browser config.`, ` The "browser.name" was set to "${browser.name}" which filtered all configs (${instances.map((c) => c.browser).join(", ")}). Did you mean to use another name?`].join(""));
|
|
2390
|
-
}
|
|
2391
|
-
}
|
|
2392
|
-
if (resolved.coverage.enabled && resolved.coverage.provider === "istanbul" && resolved.experimental?.viteModuleRunner === false) throw new Error(`"Istanbul" coverage provider is not compatible with "experimental.viteModuleRunner: false". Please, enable "viteModuleRunner" or switch to "v8" coverage provider.`);
|
|
2393
|
-
if (browser.enabled && resolved.detectAsyncLeaks) logger.console.warn(c.yellow("The option \"detectAsyncLeaks\" is not supported in browser mode and will be ignored."));
|
|
2394
|
-
const containsChromium = hasBrowserChromium(vitest, resolved);
|
|
2395
|
-
const hasOnlyChromium = hasOnlyBrowserChromium(vitest, resolved);
|
|
2396
|
-
// Browser-mode "Chromium" only features:
|
|
2397
|
-
if (browser.enabled && (!containsChromium || !hasOnlyChromium)) {
|
|
2398
|
-
const browserConfig = `
|
|
2399
|
-
{
|
|
2400
|
-
browser: {
|
|
2401
|
-
provider: ${browser.provider?.name || "preview"}(),
|
|
2402
|
-
instances: [
|
|
2403
|
-
${(browser.instances || []).map((i) => `{ browser: '${i.browser}' }`).join(",\n ")}
|
|
2404
|
-
],
|
|
2405
|
-
},
|
|
2406
|
-
}
|
|
2407
|
-
`.trim();
|
|
2408
|
-
const preferredProvider = !browser.provider?.name || browser.provider.name === "preview" ? "playwright" : browser.provider.name;
|
|
2409
|
-
const correctExample = `
|
|
2410
|
-
{
|
|
2411
|
-
browser: {
|
|
2412
|
-
provider: ${preferredProvider}(),
|
|
2413
|
-
instances: [
|
|
2414
|
-
{ browser: '${preferredProvider === "playwright" ? "chromium" : "chrome"}' }
|
|
2415
|
-
],
|
|
2416
|
-
},
|
|
2417
|
-
}
|
|
2418
|
-
`.trim();
|
|
2419
|
-
// requires all projects to be chromium
|
|
2420
|
-
if (!hasOnlyChromium && resolved.coverage.enabled && resolved.coverage.provider === "v8") {
|
|
2421
|
-
const coverageExample = `
|
|
2422
|
-
{
|
|
2423
|
-
coverage: {
|
|
2424
|
-
provider: 'istanbul',
|
|
2425
|
-
},
|
|
2426
|
-
}
|
|
2427
|
-
`.trim();
|
|
2428
|
-
throw new Error(`@vitest/coverage-v8 does not work with\n${browserConfig}\n\nUse either:\n${correctExample}\n\n...or change your coverage provider to:\n${coverageExample}\n`);
|
|
2429
|
-
}
|
|
2430
|
-
// ignores non-chromium browsers when there is at least one chromium project
|
|
2431
|
-
if (!containsChromium && (resolved.inspect || resolved.inspectBrk)) {
|
|
2432
|
-
const inspectOption = `--inspect${resolved.inspectBrk ? "-brk" : ""}`;
|
|
2433
|
-
throw new Error(`${inspectOption} does not work with\n${browserConfig}\n\nUse either:\n${correctExample}\n\n...or disable ${inspectOption}\n`);
|
|
2434
|
-
}
|
|
2435
|
-
}
|
|
2436
|
-
resolved.coverage.reporter = resolveCoverageReporters(resolved.coverage.reporter);
|
|
2437
|
-
if (isAgent) {
|
|
2438
|
-
// default to `skipFull` and add `text-summary` reporter when `text` reporter is used on agents
|
|
2439
|
-
const text = resolved.coverage.reporter.find(([name]) => name === "text");
|
|
2440
|
-
const textSummary = resolved.coverage.reporter.find(([name]) => name === "text-summary");
|
|
2441
|
-
if (text) {
|
|
2442
|
-
text[1] = {
|
|
2443
|
-
skipFull: true,
|
|
2444
|
-
...text[1]
|
|
2445
|
-
};
|
|
2446
|
-
if (!textSummary) resolved.coverage.reporter.push(["text-summary", {}]);
|
|
2447
|
-
}
|
|
2448
|
-
}
|
|
2449
|
-
if (resolved.coverage.changed === void 0 && resolved.changed !== void 0) resolved.coverage.changed = resolved.changed;
|
|
2450
|
-
if (resolved.coverage.enabled && resolved.coverage.reportsDirectory) {
|
|
2451
|
-
const reportsDirectory = resolve$1(resolved.root, resolved.coverage.reportsDirectory);
|
|
2452
|
-
if (reportsDirectory === resolved.root || reportsDirectory === process.cwd()) throw new Error(`You cannot set "coverage.reportsDirectory" as ${reportsDirectory}. Vitest needs to be able to remove this directory before test run`);
|
|
2453
|
-
if (resolved.coverage.htmlDir) resolved.coverage.htmlDir = resolve$1(resolved.root, resolved.coverage.htmlDir);
|
|
2454
|
-
// infer default htmlDir based on builtin reporter's html output location
|
|
2455
|
-
if (!resolved.coverage.htmlDir) {
|
|
2456
|
-
const htmlReporter = resolved.coverage.reporter.find(([name]) => name === "html" || name === "html-spa");
|
|
2457
|
-
if (htmlReporter) {
|
|
2458
|
-
const [, options] = htmlReporter;
|
|
2459
|
-
const subdir = options && typeof options === "object" && "subdir" in options && typeof options.subdir === "string" ? options.subdir : void 0;
|
|
2460
|
-
resolved.coverage.htmlDir = resolve$1(reportsDirectory, subdir || ".");
|
|
2461
|
-
} else if (resolved.coverage.reporter.find(([name]) => name === "lcov")) resolved.coverage.htmlDir = resolve$1(reportsDirectory, "lcov-report");
|
|
2462
|
-
}
|
|
2463
|
-
}
|
|
2464
|
-
if (resolved.coverage.enabled && resolved.coverage.provider === "custom" && resolved.coverage.customProviderModule) resolved.coverage.customProviderModule = resolvePath(resolved.coverage.customProviderModule, resolved.root);
|
|
2465
|
-
resolved.expect ??= {};
|
|
2466
|
-
resolved.deps ??= {};
|
|
2467
|
-
resolved.deps.moduleDirectories ??= [];
|
|
2468
|
-
resolved.deps.optimizer ??= {};
|
|
2469
|
-
resolved.deps.optimizer.ssr ??= {};
|
|
2470
|
-
resolved.deps.optimizer.ssr.enabled ??= false;
|
|
2471
|
-
resolved.deps.optimizer.client ??= {};
|
|
2472
|
-
resolved.deps.optimizer.client.enabled ??= false;
|
|
2473
|
-
resolved.deps.web ??= {};
|
|
2474
|
-
resolved.deps.web.transformAssets ??= true;
|
|
2475
|
-
resolved.deps.web.transformCss ??= true;
|
|
2476
|
-
resolved.deps.web.transformGlobPattern ??= [];
|
|
2477
|
-
resolved.setupFiles = toArray(resolved.setupFiles || []).map((file) => resolvePath(file, resolved.root));
|
|
2478
|
-
resolved.globalSetup = toArray(resolved.globalSetup || []).map((file) => resolvePath(file, resolved.root));
|
|
2479
|
-
if (resolved.coverage.include) resolved.coverage.include = resolved.coverage.include.map((pattern) => {
|
|
2480
|
-
if (isDynamicPattern(pattern)) return pattern;
|
|
2481
|
-
// Convert patterns like ["src", "packages/server"] to ["src/**", "packages/server/**"]
|
|
2482
|
-
return pattern.endsWith("/") ? `${pattern}**` : `${pattern}/**`;
|
|
2483
|
-
});
|
|
2484
|
-
// Add hard-coded default coverage exclusions. These cannot be overridden by user config.
|
|
2485
|
-
// Override original exclude array for cases where user re-uses same object in test.exclude.
|
|
2486
|
-
resolved.coverage.exclude = [
|
|
2487
|
-
...resolved.coverage.exclude,
|
|
2488
|
-
...resolved.setupFiles.map((file) => `${resolved.coverage.allowExternal ? "**/" : ""}${relative(resolved.root, file)}`),
|
|
2489
|
-
...resolved.include.filter((pattern) => !pattern.startsWith("!")),
|
|
2490
|
-
resolved.config && slash(resolved.config),
|
|
2491
|
-
...configFiles,
|
|
2492
|
-
"**/virtual:*",
|
|
2493
|
-
"**/__x00__*",
|
|
2494
|
-
"**/node_modules/**"
|
|
2495
|
-
].filter((pattern) => typeof pattern === "string");
|
|
2496
|
-
resolved.forceRerunTriggers = [...resolved.forceRerunTriggers, ...resolved.setupFiles];
|
|
2497
|
-
if (resolved.cliExclude) resolved.exclude.push(...resolved.cliExclude);
|
|
2498
|
-
if (resolved.runner) resolved.runner = resolvePath(resolved.runner, resolved.root);
|
|
2499
|
-
resolved.attachmentsDir = resolve$1(resolved.root, resolved.attachmentsDir ?? ".vitest/attachments");
|
|
2500
|
-
if (resolved.snapshotEnvironment) resolved.snapshotEnvironment = resolvePath(resolved.snapshotEnvironment, resolved.root);
|
|
2501
|
-
resolved.testNamePattern = resolved.testNamePattern ? resolved.testNamePattern instanceof RegExp ? resolved.testNamePattern : new RegExp(resolved.testNamePattern) : void 0;
|
|
2502
|
-
if (resolved.snapshotFormat && "plugins" in resolved.snapshotFormat) {
|
|
2503
|
-
resolved.snapshotFormat.plugins = [];
|
|
2504
|
-
// TODO: support it via separate config (like DiffOptions) or via `Function.toString()`
|
|
2505
|
-
if (typeof resolved.snapshotFormat.compareKeys === "function") throw new TypeError(`"snapshotFormat.compareKeys" function is not supported.`);
|
|
2506
|
-
}
|
|
2507
|
-
const UPDATE_SNAPSHOT = resolved.update || process.env.UPDATE_SNAPSHOT;
|
|
2508
|
-
resolved.snapshotOptions = {
|
|
2509
|
-
expand: resolved.expandSnapshotDiff ?? false,
|
|
2510
|
-
snapshotFormat: resolved.snapshotFormat || {},
|
|
2511
|
-
updateSnapshot: UPDATE_SNAPSHOT === "all" || UPDATE_SNAPSHOT === "new" || UPDATE_SNAPSHOT === "none" ? UPDATE_SNAPSHOT : isCI && !UPDATE_SNAPSHOT ? "none" : UPDATE_SNAPSHOT ? "all" : "new",
|
|
2512
|
-
resolveSnapshotPath: options.resolveSnapshotPath,
|
|
2513
|
-
snapshotEnvironment: null
|
|
2514
|
-
};
|
|
2515
|
-
resolved.snapshotSerializers ??= [];
|
|
2516
|
-
resolved.snapshotSerializers = resolved.snapshotSerializers.map((file) => resolvePath(file, resolved.root));
|
|
2517
|
-
resolved.forceRerunTriggers.push(...resolved.snapshotSerializers);
|
|
2518
|
-
if (options.resolveSnapshotPath) delete resolved.resolveSnapshotPath;
|
|
2519
|
-
resolved.execArgv ??= [];
|
|
2520
|
-
resolved.pool ??= "threads";
|
|
2521
|
-
if (resolved.pool === "vmForks" || resolved.pool === "vmThreads" || resolved.pool === "typescript") resolved.isolate = false;
|
|
2522
|
-
if (process.env.VITEST_MAX_WORKERS) resolved.maxWorkers = Number.parseInt(process.env.VITEST_MAX_WORKERS);
|
|
2523
|
-
if (typeof resolved.diff === "string") {
|
|
2524
|
-
resolved.diff = resolvePath(resolved.diff, resolved.root);
|
|
2525
|
-
resolved.forceRerunTriggers.push(resolved.diff);
|
|
2526
|
-
}
|
|
2527
|
-
resolved.api = {
|
|
2528
|
-
...resolveApiServerConfig(options, defaultPort),
|
|
2529
|
-
token: crypto.randomUUID()
|
|
2530
|
-
};
|
|
2531
|
-
if (options.related) resolved.related = toArray(options.related).map((file) => resolve$1(resolved.root, file));
|
|
2532
|
-
/*
|
|
2533
|
-
* Reporters can be defined in many different ways:
|
|
2534
|
-
* { reporter: 'json' }
|
|
2535
|
-
* { reporter: { onFinish() { method() } } }
|
|
2536
|
-
* { reporter: ['json', { onFinish() { method() } }] }
|
|
2537
|
-
* { reporter: [[ 'json' ]] }
|
|
2538
|
-
* { reporter: [[ 'json' ], 'html'] }
|
|
2539
|
-
* { reporter: [[ 'json', { outputFile: 'test.json' } ], 'html'] }
|
|
2540
|
-
*/
|
|
2541
|
-
if (resolved.reporters) if (!Array.isArray(resolved.reporters))
|
|
2542
|
-
// Reporter name, e.g. { reporters: 'json' }
|
|
2543
|
-
if (typeof resolved.reporters === "string") resolved.reporters = [[resolved.reporters, {}]];
|
|
2544
|
-
else resolved.reporters = [resolved.reporters];
|
|
2545
|
-
else {
|
|
2546
|
-
const reporters = resolved.reporters;
|
|
2547
|
-
resolved.reporters = [];
|
|
2548
|
-
for (const reporter of reporters) if (Array.isArray(reporter))
|
|
2549
|
-
// Reporter with options, e.g. { reporters: [ [ 'json', { outputFile: 'test.json' } ] ] }
|
|
2550
|
-
resolved.reporters.push([reporter[0], reporter[1] || {}]);
|
|
2551
|
-
else if (typeof reporter === "string")
|
|
2552
|
-
// Reporter name in array, e.g. { reporters: ["html", "json"]}
|
|
2553
|
-
resolved.reporters.push([reporter, {}]);
|
|
2554
|
-
else
|
|
2555
|
-
// Inline reporter, e.g. { reporter: [{ onFinish() { method() } }] }
|
|
2556
|
-
resolved.reporters.push(reporter);
|
|
2557
|
-
}
|
|
2558
|
-
// @ts-expect-error "reporter" is from CLI, should be absolute to the running directory
|
|
2559
|
-
// it is passed down as "vitest --reporter ../reporter.js"
|
|
2560
|
-
const reportersFromCLI = resolved.reporter;
|
|
2561
|
-
const cliReporters = toArray(reportersFromCLI || []).map((reporter) => {
|
|
2562
|
-
// ./reporter.js || ../reporter.js, but not .reporters/reporter.js
|
|
2563
|
-
if (/^\.\.?\//.test(reporter)) return resolve$1(process.cwd(), reporter);
|
|
2564
|
-
return reporter;
|
|
2565
|
-
});
|
|
2566
|
-
if (cliReporters.length) {
|
|
2567
|
-
// When CLI reporters are specified, preserve options from config file
|
|
2568
|
-
const configReportersMap = /* @__PURE__ */ new Map();
|
|
2569
|
-
// Build a map of reporter names to their options from the config
|
|
2570
|
-
for (const reporter of resolved.reporters) if (Array.isArray(reporter)) {
|
|
2571
|
-
const [reporterName, reporterOptions] = reporter;
|
|
2572
|
-
if (typeof reporterName === "string") configReportersMap.set(reporterName, reporterOptions);
|
|
2573
|
-
}
|
|
2574
|
-
resolved.reporters = Array.from(new Set(toArray(cliReporters))).filter(Boolean).map((reporter) => [reporter, configReportersMap.get(reporter) || {}]);
|
|
2575
|
-
}
|
|
2576
|
-
resolved.mergeReportsLabel = process.env.VITEST_BLOB_LABEL;
|
|
2577
|
-
for (const reporter of resolved.reporters) if (Array.isArray(reporter) && reporter[0] === "blob") {
|
|
2578
|
-
const options = reporter[1];
|
|
2579
|
-
if (options && typeof options.label === "string") resolved.mergeReportsLabel = options.label;
|
|
2580
|
-
}
|
|
2581
|
-
if (resolved.changed) resolved.passWithNoTests ??= true;
|
|
2582
|
-
resolved.css ??= {};
|
|
2583
|
-
if (typeof resolved.css === "object") {
|
|
2584
|
-
resolved.css.modules ??= {};
|
|
2585
|
-
resolved.css.modules.classNameStrategy ??= "stable";
|
|
2586
|
-
}
|
|
2587
|
-
if (resolved.cache !== false) {
|
|
2588
|
-
if (resolved.cache && typeof resolved.cache.dir === "string") vitest.logger.deprecate(`"cache.dir" is deprecated, use Vite's "cacheDir" instead if you want to change the cache director. Note caches will be written to "cacheDir\/vitest"`);
|
|
2589
|
-
resolved.cache = { dir: viteConfig.cacheDir };
|
|
2590
|
-
}
|
|
2591
|
-
resolved.sequence ??= {};
|
|
2592
|
-
if (resolved.sequence.shuffle && typeof resolved.sequence.shuffle === "object") {
|
|
2593
|
-
const { files, tests } = resolved.sequence.shuffle;
|
|
2594
|
-
resolved.sequence.sequencer ??= files ? RandomSequencer : BaseSequencer;
|
|
2595
|
-
resolved.sequence.shuffle = tests;
|
|
2596
|
-
}
|
|
2597
|
-
if (!resolved.sequence?.sequencer)
|
|
2598
|
-
// CLI flag has higher priority
|
|
2599
|
-
resolved.sequence.sequencer = resolved.sequence.shuffle ? RandomSequencer : BaseSequencer;
|
|
2600
|
-
resolved.sequence.groupOrder ??= 0;
|
|
2601
|
-
resolved.sequence.hooks ??= "stack";
|
|
2602
|
-
// Set seed if either files or tests are shuffled
|
|
2603
|
-
if (resolved.sequence.sequencer === RandomSequencer || resolved.sequence.shuffle) resolved.sequence.seed ??= Date.now();
|
|
2604
|
-
resolved.typecheck = {
|
|
2605
|
-
...configDefaults.typecheck,
|
|
2606
|
-
...resolved.typecheck
|
|
2607
|
-
};
|
|
2608
|
-
resolved.typecheck ??= {};
|
|
2609
|
-
resolved.typecheck.enabled ??= false;
|
|
2610
|
-
if (resolved.typecheck.enabled && !warnedTypeCheck) {
|
|
2611
|
-
warnedTypeCheck = true;
|
|
2612
|
-
logger.console.warn(c.yellow("Testing types with tsc and vue-tsc is an experimental feature.\nBreaking changes might not follow SemVer, please pin Vitest's version when using it."));
|
|
2613
|
-
}
|
|
2614
|
-
resolved.browser.enabled ??= false;
|
|
2615
|
-
resolved.browser.headless ??= isCI;
|
|
2616
|
-
if (resolved.browser.isolate) logger.console.warn(c.yellow("`browser.isolate` is deprecated. Use top-level `isolate` instead."));
|
|
2617
|
-
resolved.browser.isolate ??= resolved.isolate ?? true;
|
|
2618
|
-
resolved.browser.fileParallelism ??= options.fileParallelism ?? true;
|
|
2619
|
-
// disable in headless mode by default, and if CI is detected
|
|
2620
|
-
resolved.browser.ui ??= resolved.browser.headless === true ? false : !isCI;
|
|
2621
|
-
resolved.browser.commands ??= {};
|
|
2622
|
-
resolved.browser.detailsPanelPosition ??= "right";
|
|
2623
|
-
if (resolved.browser.screenshotDirectory) resolved.browser.screenshotDirectory = resolve$1(resolved.root, resolved.browser.screenshotDirectory);
|
|
2624
|
-
if (resolved.inspector.enabled) resolved.browser.trackUnhandledErrors ??= false;
|
|
2625
|
-
resolved.browser.viewport ??= {};
|
|
2626
|
-
resolved.browser.viewport.width ??= 414;
|
|
2627
|
-
resolved.browser.viewport.height ??= 896;
|
|
2628
|
-
resolved.browser.locators ??= {};
|
|
2629
|
-
resolved.browser.locators.testIdAttribute ??= "data-testid";
|
|
2630
|
-
resolved.browser.locators.exact ??= true;
|
|
2631
|
-
resolved.browser.locators.errorFormat ??= "all";
|
|
2632
|
-
if (typeof resolved.browser.provider === "string") {
|
|
2633
|
-
const source = `@vitest/browser-${resolved.browser.provider}`;
|
|
2634
|
-
throw new TypeError(`The \`browser.provider\` configuration was changed to accept a factory instead of a string. Add an import of "${resolved.browser.provider}" from "${source}" instead. See: https://vitest.dev/config/browser/provider`);
|
|
2635
|
-
}
|
|
2636
|
-
const isPreview = resolved.browser.provider?.name === "preview";
|
|
2637
|
-
if (!isPreview && resolved.browser.enabled && provider === "stackblitz") throw new Error(`stackblitz environment does not support the ${resolved.browser.provider?.name} provider. Please, use "@vitest/browser-preview" instead.`);
|
|
2638
|
-
if (isPreview && resolved.browser.screenshotFailures === true) {
|
|
2639
|
-
console.warn(c.yellow([
|
|
2640
|
-
`Browser provider "preview" doesn't support screenshots, `,
|
|
2641
|
-
`so "browser.screenshotFailures" option is forcefully disabled. `,
|
|
2642
|
-
`Set "browser.screenshotFailures" to false or remove it from the config to suppress this warning.`
|
|
2643
|
-
].join("")));
|
|
2644
|
-
resolved.browser.screenshotFailures = false;
|
|
2645
|
-
} else resolved.browser.screenshotFailures ??= !isPreview && !resolved.browser.ui;
|
|
2646
|
-
if (resolved.browser.provider && resolved.browser.provider.options == null) resolved.browser.provider.options = {};
|
|
2647
|
-
resolved.browser.api = resolveApiServerConfig(resolved.browser, defaultBrowserPort, resolved.api, logger) || { port: defaultBrowserPort };
|
|
2648
|
-
// enable includeTaskLocation by default in UI mode
|
|
2649
|
-
if (resolved.browser.enabled) {
|
|
2650
|
-
if (resolved.browser.ui) resolved.includeTaskLocation ??= true;
|
|
2651
|
-
} else if (resolved.ui) resolved.includeTaskLocation ??= true;
|
|
2652
|
-
if (typeof resolved.browser.trace === "string" || !resolved.browser.trace) resolved.browser.trace = { mode: resolved.browser.trace || "off" };
|
|
2653
|
-
const traceView = resolved.browser.traceView;
|
|
2654
|
-
resolved.browser.traceView = typeof traceView === "object" ? {
|
|
2655
|
-
enabled: traceView.enabled ?? false,
|
|
2656
|
-
recordCanvas: traceView.recordCanvas ?? false,
|
|
2657
|
-
inlineImages: traceView.inlineImages ?? false
|
|
2658
|
-
} : {
|
|
2659
|
-
enabled: traceView ?? false,
|
|
2660
|
-
recordCanvas: false,
|
|
2661
|
-
inlineImages: false
|
|
2662
|
-
};
|
|
2663
|
-
if (resolved.browser.enabled && resolved.browser.traceView.enabled) resolved.browser.detailsPanelPosition = "bottom";
|
|
2664
|
-
if (resolved.browser.trace.tracesDir != null) resolved.browser.trace.tracesDir = resolvePath(resolved.browser.trace.tracesDir, resolved.root);
|
|
2665
|
-
if (toArray(resolved.reporters).some((reporter) => {
|
|
2666
|
-
if (Array.isArray(reporter)) return reporter[0] === "html";
|
|
2667
|
-
return false;
|
|
2668
|
-
})) resolved.includeTaskLocation ??= true;
|
|
2669
|
-
else if (resolved.browser.enabled && resolved.browser.traceView.enabled && !resolved.watch) logger.console.warn(c.yellow(withLabel("yellow", "Vitest", "--browser.traceView is enabled without the HTML reporter.")));
|
|
2670
|
-
resolved.server ??= {};
|
|
2671
|
-
resolved.server.deps ??= {};
|
|
2672
|
-
if (resolved.server.debug?.dump || process.env.VITEST_DEBUG_DUMP) {
|
|
2673
|
-
const userFolder = resolved.server.debug?.dump || process.env.VITEST_DEBUG_DUMP;
|
|
2674
|
-
resolved.dumpDir = resolve$1(resolved.root, typeof userFolder === "string" && userFolder !== "true" ? userFolder : ".vitest-dump", resolved.name || "root");
|
|
2675
|
-
}
|
|
2676
|
-
resolved.testTimeout ??= resolved.browser.enabled ? 15e3 : 5e3;
|
|
2677
|
-
resolved.hookTimeout ??= resolved.browser.enabled ? 3e4 : 1e4;
|
|
2678
|
-
resolved.experimental ??= {};
|
|
2679
|
-
if (resolved.experimental.openTelemetry?.sdkPath) {
|
|
2680
|
-
const sdkPath = resolve$1(resolved.root, resolved.experimental.openTelemetry.sdkPath);
|
|
2681
|
-
resolved.experimental.openTelemetry.sdkPath = pathToFileURL(sdkPath).toString();
|
|
2682
|
-
}
|
|
2683
|
-
if (resolved.experimental.openTelemetry?.browserSdkPath) {
|
|
2684
|
-
const browserSdkPath = resolve$1(resolved.root, resolved.experimental.openTelemetry.browserSdkPath);
|
|
2685
|
-
resolved.experimental.openTelemetry.browserSdkPath = browserSdkPath;
|
|
2686
|
-
}
|
|
2687
|
-
if (resolved.experimental.fsModuleCachePath) resolved.experimental.fsModuleCachePath = resolve$1(resolved.root, resolved.experimental.fsModuleCachePath);
|
|
2688
|
-
resolved.experimental.importDurations ??= {};
|
|
2689
|
-
resolved.experimental.importDurations.print ??= false;
|
|
2690
|
-
resolved.experimental.importDurations.failOnDanger ??= false;
|
|
2691
|
-
if (resolved.experimental.importDurations.limit == null) {
|
|
2692
|
-
const shouldCollect = resolved.experimental.importDurations.print || resolved.experimental.importDurations.failOnDanger || resolved.ui;
|
|
2693
|
-
resolved.experimental.importDurations.limit = shouldCollect ? 10 : 0;
|
|
2694
|
-
}
|
|
2695
|
-
resolved.experimental.importDurations.thresholds ??= {};
|
|
2696
|
-
resolved.experimental.importDurations.thresholds.warn ??= 100;
|
|
2697
|
-
resolved.experimental.importDurations.thresholds.danger ??= 500;
|
|
2698
|
-
if (typeof resolved.experimental.vcsProvider === "string" && resolved.experimental.vcsProvider !== "git") resolved.experimental.vcsProvider = resolvePath(resolved.experimental.vcsProvider, resolved.root);
|
|
2699
|
-
return resolved;
|
|
2700
|
-
}
|
|
2701
|
-
function isBrowserEnabled(config) {
|
|
2702
|
-
return Boolean(config.browser?.enabled);
|
|
2703
|
-
}
|
|
2704
|
-
function resolveCoverageReporters(configReporters) {
|
|
2705
|
-
// E.g. { reporter: "html" }
|
|
2706
|
-
if (!Array.isArray(configReporters)) return [[configReporters, {}]];
|
|
2707
|
-
const resolvedReporters = [];
|
|
2708
|
-
for (const reporter of configReporters) if (Array.isArray(reporter))
|
|
2709
|
-
// E.g. { reporter: [ ["html", { skipEmpty: true }], ["lcov"], ["json", { file: "map.json" }] ]}
|
|
2710
|
-
resolvedReporters.push([reporter[0], reporter[1] || {}]);
|
|
2711
|
-
else
|
|
2712
|
-
// E.g. { reporter: ["html", "json"]}
|
|
2713
|
-
resolvedReporters.push([reporter, {}]);
|
|
2714
|
-
return resolvedReporters;
|
|
2715
|
-
}
|
|
2716
|
-
function isChromiumName(provider, name) {
|
|
2717
|
-
if (provider === "playwright") return name === "chromium";
|
|
2718
|
-
return name === "chrome" || name === "edge";
|
|
2719
|
-
}
|
|
2720
|
-
function hasBrowserChromium(vitest, config) {
|
|
2721
|
-
const browser = config.browser;
|
|
2722
|
-
if (!browser || !browser.provider || browser.provider.name === "preview" || !browser.enabled) return false;
|
|
2723
|
-
if (browser.name) return isChromiumName(browser.provider.name, browser.name);
|
|
2724
|
-
if (!browser.instances) return false;
|
|
2725
|
-
return browser.instances.some((instance) => {
|
|
2726
|
-
const name = instance.name || (config.name ? `${config.name} (${instance.browser})` : instance.browser);
|
|
2727
|
-
// browser config is filtered out
|
|
2728
|
-
if (!vitest.matchesProjectFilter(name)) return false;
|
|
2729
|
-
return isChromiumName(browser.provider.name, instance.browser);
|
|
2730
|
-
});
|
|
2731
|
-
}
|
|
2732
|
-
function hasOnlyBrowserChromium(vitest, config) {
|
|
2733
|
-
const browser = config.browser;
|
|
2734
|
-
if (!browser || !browser.provider || browser.provider.name === "preview" || !browser.enabled) return false;
|
|
2735
|
-
if (browser.name) return isChromiumName(browser.provider.name, browser.name);
|
|
2736
|
-
if (!browser.instances) return false;
|
|
2737
|
-
return browser.instances.every((instance) => {
|
|
2738
|
-
const name = instance.name || (config.name ? `${config.name} (${instance.browser})` : instance.browser);
|
|
2739
|
-
// browser config is filtered out
|
|
2740
|
-
if (!vitest.matchesProjectFilter(name)) return true;
|
|
2741
|
-
return isChromiumName(browser.provider.name, instance.browser);
|
|
2742
|
-
});
|
|
2743
|
-
}
|
|
2744
|
-
|
|
2745
2097
|
const THRESHOLD_KEYS = [
|
|
2746
2098
|
"lines",
|
|
2747
2099
|
"functions",
|
|
@@ -2766,6 +2118,7 @@ class BaseCoverageProvider {
|
|
|
2766
2118
|
coverageFiles = /* @__PURE__ */ new Map();
|
|
2767
2119
|
pendingPromises = [];
|
|
2768
2120
|
coverageFilesDirectory;
|
|
2121
|
+
reportsDirectoryLock;
|
|
2769
2122
|
roots = [];
|
|
2770
2123
|
changedFiles;
|
|
2771
2124
|
_initialize(ctx) {
|
|
@@ -2791,6 +2144,7 @@ Update your dependencies and make sure the versions match.`));
|
|
|
2791
2144
|
const shard = this.ctx.config.shard;
|
|
2792
2145
|
const tempDirectory = `.tmp${shard ? `-${shard.index}-${shard.count}` : ""}`;
|
|
2793
2146
|
this.coverageFilesDirectory = resolve$1(this.options.reportsDirectory, tempDirectory);
|
|
2147
|
+
this.reportsDirectoryLock = new ReportsDirectoryLock(resolve$1(this.options.reportsDirectory));
|
|
2794
2148
|
// If --project filter is set pick only roots of resolved projects
|
|
2795
2149
|
this.roots = ctx.config.project?.length ? [...new Set(ctx.projects.map((project) => project.config.root))] : [ctx.config.root];
|
|
2796
2150
|
}
|
|
@@ -2802,12 +2156,12 @@ Update your dependencies and make sure the versions match.`));
|
|
|
2802
2156
|
const filename = slash(cleanUrl(_filename));
|
|
2803
2157
|
const cacheHit = this.globCache.get(filename);
|
|
2804
2158
|
if (cacheHit !== void 0) return cacheHit;
|
|
2159
|
+
const matchingRoot = roots.find((root) => filename.startsWith(`${slash(root)}/`) || filename === slash(root));
|
|
2805
2160
|
// File outside project root with default allowExternal
|
|
2806
|
-
if (this.options.allowExternal === false &&
|
|
2161
|
+
if (this.options.allowExternal === false && !matchingRoot) {
|
|
2807
2162
|
this.globCache.set(filename, false);
|
|
2808
2163
|
return false;
|
|
2809
2164
|
}
|
|
2810
|
-
const matchingRoot = roots.find((root) => filename.startsWith(`${slash(root)}/`) || filename === slash(root));
|
|
2811
2165
|
const relativeFilename = matchingRoot ? relative(matchingRoot, filename) : filename;
|
|
2812
2166
|
if (pm.isMatch(relativeFilename, this.options.exclude, { dot: true })) {
|
|
2813
2167
|
this.globCache.set(filename, false);
|
|
@@ -2854,6 +2208,7 @@ Update your dependencies and make sure the versions match.`));
|
|
|
2854
2208
|
return this.options;
|
|
2855
2209
|
}
|
|
2856
2210
|
async clean(clean = true) {
|
|
2211
|
+
await this.reportsDirectoryLock.acquire();
|
|
2857
2212
|
if (clean && existsSync(this.options.reportsDirectory)) await promises.rm(this.options.reportsDirectory, {
|
|
2858
2213
|
recursive: true,
|
|
2859
2214
|
force: true,
|
|
@@ -2913,10 +2268,14 @@ Update your dependencies and make sure the versions match.`));
|
|
|
2913
2268
|
}
|
|
2914
2269
|
}
|
|
2915
2270
|
async cleanAfterRun() {
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2271
|
+
try {
|
|
2272
|
+
this.coverageFiles = /* @__PURE__ */ new Map();
|
|
2273
|
+
await promises.rm(this.coverageFilesDirectory, { recursive: true });
|
|
2274
|
+
// Remove empty reports directory, e.g. when only text-reporter is used
|
|
2275
|
+
if (readdirSync(this.options.reportsDirectory).length === 0) await promises.rm(this.options.reportsDirectory, { recursive: true });
|
|
2276
|
+
} finally {
|
|
2277
|
+
await this.reportsDirectoryLock.release();
|
|
2278
|
+
}
|
|
2920
2279
|
}
|
|
2921
2280
|
async onTestRunStart() {
|
|
2922
2281
|
if (this.options.changed) try {
|
|
@@ -2963,7 +2322,8 @@ Update your dependencies and make sure the versions match.`));
|
|
|
2963
2322
|
for (const key of Object.keys(this.options.thresholds)) {
|
|
2964
2323
|
if (key === "perFile" || key === "autoUpdate" || key === "100" || THRESHOLD_KEYS.includes(key)) continue;
|
|
2965
2324
|
const glob = key;
|
|
2966
|
-
const
|
|
2325
|
+
const globEntry = this.options.thresholds[glob];
|
|
2326
|
+
const globThresholds = resolveGlobThresholds(globEntry);
|
|
2967
2327
|
const globCoverageMap = this.createCoverageMap();
|
|
2968
2328
|
const matcher = pm(glob);
|
|
2969
2329
|
const matchingFiles = files.filter((file) => matcher(relative(this.ctx.config.root, file)));
|
|
@@ -2974,7 +2334,8 @@ Update your dependencies and make sure the versions match.`));
|
|
|
2974
2334
|
resolvedThresholds.push({
|
|
2975
2335
|
name: glob,
|
|
2976
2336
|
coverageMap: globCoverageMap,
|
|
2977
|
-
thresholds: globThresholds
|
|
2337
|
+
thresholds: globThresholds,
|
|
2338
|
+
...resolvePerFile(globEntry)
|
|
2978
2339
|
});
|
|
2979
2340
|
}
|
|
2980
2341
|
// Global threshold is for all files, even if they are included by glob patterns
|
|
@@ -2990,7 +2351,8 @@ Update your dependencies and make sure the versions match.`));
|
|
|
2990
2351
|
functions: this.options.thresholds?.functions,
|
|
2991
2352
|
lines: this.options.thresholds?.lines,
|
|
2992
2353
|
statements: this.options.thresholds?.statements
|
|
2993
|
-
}
|
|
2354
|
+
},
|
|
2355
|
+
...resolvePerFile(this.options.thresholds)
|
|
2994
2356
|
});
|
|
2995
2357
|
return resolvedThresholds;
|
|
2996
2358
|
}
|
|
@@ -2998,51 +2360,56 @@ Update your dependencies and make sure the versions match.`));
|
|
|
2998
2360
|
* Check collected coverage against configured thresholds. Sets exit code to 1 when thresholds not reached.
|
|
2999
2361
|
*/
|
|
3000
2362
|
checkThresholds(allThresholds) {
|
|
3001
|
-
for (const { coverageMap, thresholds, name } of allThresholds) {
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
const summaries = this.options.thresholds?.perFile ? coverageMap.files().map((file) => ({
|
|
3005
|
-
file,
|
|
3006
|
-
summary: coverageMap.fileCoverageFor(file).toSummary()
|
|
3007
|
-
})) : [{
|
|
2363
|
+
for (const { coverageMap, thresholds, perFile, perFileThresholds, name } of allThresholds) {
|
|
2364
|
+
const groups = [];
|
|
2365
|
+
if (!perFile) groups.push({
|
|
3008
2366
|
file: null,
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
2367
|
+
thresholds,
|
|
2368
|
+
summary: coverageMap.getCoverageSummary(),
|
|
2369
|
+
name: name === GLOBAL_THRESHOLDS_KEY ? name : `"${name}"`
|
|
2370
|
+
});
|
|
2371
|
+
if (perFile) for (const file of coverageMap.files().sort()) groups.push({
|
|
2372
|
+
file,
|
|
2373
|
+
thresholds,
|
|
2374
|
+
summary: coverageMap.fileCoverageFor(file).toSummary(),
|
|
2375
|
+
name: name === GLOBAL_THRESHOLDS_KEY ? name : `"${name}"`
|
|
2376
|
+
});
|
|
2377
|
+
if (perFileThresholds) for (const file of coverageMap.files().sort()) groups.push({
|
|
2378
|
+
file,
|
|
2379
|
+
thresholds: perFileThresholds,
|
|
2380
|
+
summary: coverageMap.fileCoverageFor(file).toSummary(),
|
|
2381
|
+
name: "per-file"
|
|
2382
|
+
});
|
|
2383
|
+
for (const group of groups) {
|
|
2384
|
+
if (group.thresholds.branches === void 0 && group.thresholds.functions === void 0 && group.thresholds.lines === void 0 && group.thresholds.statements === void 0) continue;
|
|
2385
|
+
this.reportThresholdViolations(group.thresholds, group.summary, group.file, group.name);
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
reportThresholdViolations(thresholds, summary, file, label) {
|
|
2390
|
+
for (const thresholdKey of THRESHOLD_KEYS) {
|
|
2391
|
+
const threshold = thresholds[thresholdKey];
|
|
2392
|
+
if (threshold === void 0) continue;
|
|
2393
|
+
/**
|
|
2394
|
+
* Positive thresholds are treated as minimum coverage percentages (X means: X% of lines must be covered),
|
|
2395
|
+
* while negative thresholds are treated as maximum uncovered counts (-X means: X lines may be uncovered).
|
|
2396
|
+
*/
|
|
2397
|
+
if (threshold >= 0) {
|
|
2398
|
+
const coverage = summary.data[thresholdKey].pct;
|
|
2399
|
+
if (coverage < threshold) {
|
|
2400
|
+
process.exitCode = 1;
|
|
2401
|
+
let errorMessage = `ERROR: Coverage for ${thresholdKey} (${coverage}%) does not meet ${label} threshold (${threshold}%)`;
|
|
2402
|
+
if (file) errorMessage += ` for ${relative("./", file).replace(/\\/g, "/")}`;
|
|
2403
|
+
this.ctx.logger.error(errorMessage);
|
|
2404
|
+
}
|
|
2405
|
+
} else {
|
|
2406
|
+
const uncovered = summary.data[thresholdKey].total - summary.data[thresholdKey].covered;
|
|
2407
|
+
const absoluteThreshold = threshold * -1;
|
|
2408
|
+
if (uncovered > absoluteThreshold) {
|
|
2409
|
+
process.exitCode = 1;
|
|
2410
|
+
let errorMessage = `ERROR: Uncovered ${thresholdKey} (${uncovered}) exceed ${label} threshold (${absoluteThreshold})`;
|
|
2411
|
+
if (file) errorMessage += ` for ${relative("./", file).replace(/\\/g, "/")}`;
|
|
2412
|
+
this.ctx.logger.error(errorMessage);
|
|
3046
2413
|
}
|
|
3047
2414
|
}
|
|
3048
2415
|
}
|
|
@@ -3054,8 +2421,11 @@ Update your dependencies and make sure the versions match.`));
|
|
|
3054
2421
|
let updatedThresholds = false;
|
|
3055
2422
|
const config = resolveConfig(configurationFile);
|
|
3056
2423
|
assertConfigurationModule(config);
|
|
3057
|
-
for (const { coverageMap, thresholds, name } of allThresholds) {
|
|
3058
|
-
const summaries =
|
|
2424
|
+
for (const { coverageMap, thresholds, name, perFile } of allThresholds) {
|
|
2425
|
+
const summaries = perFile ? coverageMap.files().map((file) => coverageMap.fileCoverageFor(file).toSummary()) : [coverageMap.getCoverageSummary()];
|
|
2426
|
+
// A `perFile` glob may match no files; skip it instead of writing
|
|
2427
|
+
// Infinity thresholds from `Math.min(...[])`.
|
|
2428
|
+
if (summaries.length === 0) continue;
|
|
3059
2429
|
const thresholdsToUpdate = [];
|
|
3060
2430
|
for (const key of THRESHOLD_KEYS) {
|
|
3061
2431
|
const threshold = thresholds[key] ?? 100;
|
|
@@ -3065,22 +2435,30 @@ Update your dependencies and make sure the versions match.`));
|
|
|
3065
2435
|
*/
|
|
3066
2436
|
if (threshold >= 0) {
|
|
3067
2437
|
const actual = Math.min(...summaries.map((summary) => summary[key].pct));
|
|
3068
|
-
if (actual > threshold) thresholdsToUpdate.push([
|
|
2438
|
+
if (actual > threshold) thresholdsToUpdate.push([
|
|
2439
|
+
key,
|
|
2440
|
+
actual,
|
|
2441
|
+
threshold
|
|
2442
|
+
]);
|
|
3069
2443
|
} else {
|
|
3070
2444
|
const absoluteThreshold = threshold * -1;
|
|
3071
2445
|
const actual = Math.max(...summaries.map((summary) => summary[key].total - summary[key].covered));
|
|
3072
2446
|
if (actual < absoluteThreshold) {
|
|
3073
2447
|
// If everything was covered, set new threshold to 100% (since a threshold of 0 would be considered as 0%)
|
|
3074
2448
|
const updatedThreshold = actual === 0 ? 100 : actual * -1;
|
|
3075
|
-
thresholdsToUpdate.push([
|
|
2449
|
+
thresholdsToUpdate.push([
|
|
2450
|
+
key,
|
|
2451
|
+
updatedThreshold,
|
|
2452
|
+
threshold
|
|
2453
|
+
]);
|
|
3076
2454
|
}
|
|
3077
2455
|
}
|
|
3078
2456
|
}
|
|
3079
2457
|
if (thresholdsToUpdate.length === 0) continue;
|
|
3080
2458
|
updatedThresholds = true;
|
|
3081
2459
|
const thresholdFormatter = typeof this.options.thresholds?.autoUpdate === "function" ? this.options.thresholds?.autoUpdate : (value) => value;
|
|
3082
|
-
for (const [threshold, newValue] of thresholdsToUpdate) {
|
|
3083
|
-
const formattedValue = thresholdFormatter(newValue);
|
|
2460
|
+
for (const [threshold, newValue, previousValue] of thresholdsToUpdate) {
|
|
2461
|
+
const formattedValue = thresholdFormatter(newValue, previousValue);
|
|
3084
2462
|
if (name === GLOBAL_THRESHOLDS_KEY) config.test.coverage.thresholds[threshold] = formattedValue;
|
|
3085
2463
|
else {
|
|
3086
2464
|
const glob = config.test.coverage.thresholds[name];
|
|
@@ -3160,6 +2538,25 @@ Update your dependencies and make sure the versions match.`));
|
|
|
3160
2538
|
};
|
|
3161
2539
|
}
|
|
3162
2540
|
}
|
|
2541
|
+
function resolvePerFile(thresholds) {
|
|
2542
|
+
if (!thresholds || typeof thresholds !== "object" || !("perFile" in thresholds)) return {
|
|
2543
|
+
perFile: false,
|
|
2544
|
+
perFileThresholds: null
|
|
2545
|
+
};
|
|
2546
|
+
const { perFile } = thresholds;
|
|
2547
|
+
if (perFile === true) return {
|
|
2548
|
+
perFile: true,
|
|
2549
|
+
perFileThresholds: null
|
|
2550
|
+
};
|
|
2551
|
+
if (perFile && typeof perFile === "object") return {
|
|
2552
|
+
perFile: false,
|
|
2553
|
+
perFileThresholds: resolveGlobThresholds(perFile)
|
|
2554
|
+
};
|
|
2555
|
+
return {
|
|
2556
|
+
perFile: false,
|
|
2557
|
+
perFileThresholds: null
|
|
2558
|
+
};
|
|
2559
|
+
}
|
|
3163
2560
|
/**
|
|
3164
2561
|
* Narrow down `unknown` glob thresholds to resolved ones
|
|
3165
2562
|
*/
|
|
@@ -3226,6 +2623,65 @@ function resolveMergeConfig(mod) {
|
|
|
3226
2623
|
if (config) return config;
|
|
3227
2624
|
}
|
|
3228
2625
|
}
|
|
2626
|
+
class ReportsDirectoryLock {
|
|
2627
|
+
lockFile;
|
|
2628
|
+
constructor(reportsDirectory) {
|
|
2629
|
+
this.reportsDirectory = reportsDirectory;
|
|
2630
|
+
const hash = createHash("sha256").update(reportsDirectory).digest("hex").slice(0, 16);
|
|
2631
|
+
this.lockFile = resolve$1(tmpdir(), `vitest-coverage-${hash}.lock`);
|
|
2632
|
+
}
|
|
2633
|
+
async acquire() {
|
|
2634
|
+
if (await this.tryWrite()) return;
|
|
2635
|
+
const owner = await this.readOwner();
|
|
2636
|
+
// We already hold the lock for this directory (e.g. watch-mode reruns).
|
|
2637
|
+
if (owner?.pid === process.pid) return;
|
|
2638
|
+
// Another running Vitest owns this directory.
|
|
2639
|
+
if (owner && isProcessAlive(owner.pid)) throw this.inUseError(owner);
|
|
2640
|
+
// The lock was left behind by a process that no longer exists. Reclaim it.
|
|
2641
|
+
await promises.rm(this.lockFile, { force: true });
|
|
2642
|
+
if (!await this.tryWrite()) throw this.inUseError(await this.readOwner());
|
|
2643
|
+
}
|
|
2644
|
+
async release() {
|
|
2645
|
+
if ((await this.readOwner())?.pid === process.pid) await promises.rm(this.lockFile, { force: true });
|
|
2646
|
+
}
|
|
2647
|
+
async tryWrite() {
|
|
2648
|
+
const payload = JSON.stringify({
|
|
2649
|
+
pid: process.pid,
|
|
2650
|
+
reportsDirectory: this.reportsDirectory
|
|
2651
|
+
});
|
|
2652
|
+
try {
|
|
2653
|
+
// `wx` fails with EEXIST if the file already exists, so only one process wins.
|
|
2654
|
+
await promises.writeFile(this.lockFile, payload, { flag: "wx" });
|
|
2655
|
+
return true;
|
|
2656
|
+
} catch (error) {
|
|
2657
|
+
if (error.code === "EEXIST") return false;
|
|
2658
|
+
throw error;
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
async readOwner() {
|
|
2662
|
+
try {
|
|
2663
|
+
const owner = JSON.parse(await promises.readFile(this.lockFile, "utf-8"));
|
|
2664
|
+
return typeof owner?.pid === "number" ? owner : null;
|
|
2665
|
+
} catch {
|
|
2666
|
+
return null;
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
inUseError(owner) {
|
|
2670
|
+
return /* @__PURE__ */ new Error(`The coverage report directory "${this.reportsDirectory}" is already in use by another Vitest process${owner ? ` (pid ${owner.pid})` : ""}. Running coverage for multiple Vitest processes in the same directory at the same time is not supported, because they would delete each other's reports.\nGive each run its own "coverage.reportsDirectory" (e.g. --coverage.reportsDirectory=coverage-${process.pid}) or run them sequentially.`);
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
function isProcessAlive(pid) {
|
|
2674
|
+
try {
|
|
2675
|
+
// Sending signal 0 checks if the process exists without actually killing it:
|
|
2676
|
+
// https://nodejs.org/api/process.html#processkillpid-signal
|
|
2677
|
+
process.kill(pid, 0);
|
|
2678
|
+
return true;
|
|
2679
|
+
} catch (error) {
|
|
2680
|
+
// ESRCH means the process is gone. Treat anything else (e.g. EPERM) as alive
|
|
2681
|
+
// so we never reclaim a lock from a process that is still running.
|
|
2682
|
+
return error.code !== "ESRCH";
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
3229
2685
|
|
|
3230
2686
|
// this is copy pasted from vite
|
|
3231
2687
|
function normalizeResolvedIdToUrl(environment, resolvedId) {
|
|
@@ -4908,13 +4364,14 @@ class Logger {
|
|
|
4908
4364
|
const color = this.ctx.config.watch ? "blue" : "cyan";
|
|
4909
4365
|
const mode = this.ctx.config.watch ? "DEV" : "RUN";
|
|
4910
4366
|
this.log(withLabel(color, mode, `v${this.ctx.version} `) + c.gray(this.ctx.config.root));
|
|
4911
|
-
|
|
4912
|
-
if (
|
|
4367
|
+
const seed = this.ctx.getSeed();
|
|
4368
|
+
if (seed != null) this.log(PAD + c.gray(`Running tests with seed "${seed}"`));
|
|
4913
4369
|
if (this.ctx.config.ui) {
|
|
4914
4370
|
const host = this.ctx.config.api?.host || "localhost";
|
|
4915
4371
|
const port = this.ctx.vite.config.server.port;
|
|
4916
|
-
const
|
|
4917
|
-
|
|
4372
|
+
const url = new URL(this.ctx.config.uiBase, `http://${host}:${port}`);
|
|
4373
|
+
url.searchParams.set("token", this.ctx.config.api.token);
|
|
4374
|
+
this.log(PAD + c.dim(c.green(`UI started at ${url}`)));
|
|
4918
4375
|
} else if (this.ctx.config.api?.port) {
|
|
4919
4376
|
const resolvedUrls = this.ctx.vite.resolvedUrls;
|
|
4920
4377
|
// workaround for https://github.com/vitejs/vite/issues/15438, it was fixed in vite 5.1
|
|
@@ -4926,16 +4383,6 @@ class Logger {
|
|
|
4926
4383
|
if (this.ctx.config.standalone) this.log(c.yellow(`\nVitest is running in standalone mode. Edit a test file to rerun tests.\n`));
|
|
4927
4384
|
else this.log();
|
|
4928
4385
|
}
|
|
4929
|
-
printBrowserBanner(project) {
|
|
4930
|
-
if (!project.browser) return;
|
|
4931
|
-
const resolvedUrls = project.browser.vite.resolvedUrls;
|
|
4932
|
-
const origin = resolvedUrls?.local[0] ?? resolvedUrls?.network[0];
|
|
4933
|
-
if (!origin) return;
|
|
4934
|
-
const output = project.isRootProject() ? "" : formatProjectName(project);
|
|
4935
|
-
const provider = project.browser.provider?.name;
|
|
4936
|
-
const providerString = provider === "preview" ? "" : ` by ${c.reset(c.bold(provider))}`;
|
|
4937
|
-
this.log(c.dim(`${output}Browser runner started${providerString} ${c.dim("at")} ${c.blue(new URL("/__vitest_test__/", origin))}\n`));
|
|
4938
|
-
}
|
|
4939
4386
|
printUnhandledErrors(errors) {
|
|
4940
4387
|
const errorMessage = c.red(c.bold(`\nVitest caught ${errors.length} unhandled error${errors.length > 1 ? "s" : ""} during the test run.
|
|
4941
4388
|
This might cause false positive tests. Resolve unhandled errors to make sure your tests are not affected.`));
|
|
@@ -5253,7 +4700,7 @@ class VitestPackageInstaller {
|
|
|
5253
4700
|
if (/* @__PURE__ */ isPackageExists(dependency, { paths: [root, __dirname$1] })) return true;
|
|
5254
4701
|
process.stderr.write(withLabel("red", "MISSING DEPENDENCY", `Cannot find dependency '${dependency}'\n\n`));
|
|
5255
4702
|
if (!isTTY) return false;
|
|
5256
|
-
const { install } = await (await import('./index.
|
|
4703
|
+
const { install } = await (await import('./index.Djaij2xr.js').then(function (n) { return n.i; })).default({
|
|
5257
4704
|
type: "confirm",
|
|
5258
4705
|
name: "install",
|
|
5259
4706
|
message: c.reset(`Do you want to install ${c.green(dependency)}?`)
|
|
@@ -5365,7 +4812,7 @@ function createBrowserPool(vitest) {
|
|
|
5365
4812
|
vitest.onCancel(() => {
|
|
5366
4813
|
isCancelled = true;
|
|
5367
4814
|
});
|
|
5368
|
-
const initialisedPools = await Promise.all(
|
|
4815
|
+
const initialisedPools = await Promise.all(Array.from(groupedFiles.entries(), async ([project, files]) => {
|
|
5369
4816
|
await project._initBrowserProvider();
|
|
5370
4817
|
if (!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.`);
|
|
5371
4818
|
if (isCancelled) return;
|
|
@@ -5404,7 +4851,7 @@ function createBrowserPool(vitest) {
|
|
|
5404
4851
|
return {
|
|
5405
4852
|
name: "browser",
|
|
5406
4853
|
async close() {
|
|
5407
|
-
await Promise.all(
|
|
4854
|
+
await Promise.all(Array.from(providers, (provider) => provider.close()));
|
|
5408
4855
|
vitest._browserSessions.sessionIds.clear();
|
|
5409
4856
|
providers.clear();
|
|
5410
4857
|
vitest.projects.forEach((project) => {
|
|
@@ -5479,11 +4926,10 @@ class BrowserPool {
|
|
|
5479
4926
|
this.project.vitest._browserSessions.sessionIds.add(sessionId);
|
|
5480
4927
|
const project = this.project.name;
|
|
5481
4928
|
debug?.("[%s] creating session for %s", sessionId, project);
|
|
5482
|
-
|
|
4929
|
+
const page = this._traces.$(`vitest.browser.open`, {
|
|
5483
4930
|
context: this._otel.context,
|
|
5484
4931
|
attributes: { "vitest.browser.session_id": sessionId }
|
|
5485
|
-
}, () => this.openPage(sessionId, { parallel: workerCount > 1 }))
|
|
5486
|
-
page = page.then(() => {
|
|
4932
|
+
}, () => this.openPage(sessionId, { parallel: workerCount > 1 })).then(() => {
|
|
5487
4933
|
// start running tests on the page when it's ready
|
|
5488
4934
|
this.runNextTest(method, sessionId);
|
|
5489
4935
|
});
|
|
@@ -5499,6 +4945,24 @@ class BrowserPool {
|
|
|
5499
4945
|
parallel: options.parallel
|
|
5500
4946
|
});
|
|
5501
4947
|
}
|
|
4948
|
+
// stable slot id (1..maxWorkers) assigned to each session/orchestrator on its
|
|
4949
|
+
// first run, exposed to the test runner as both `concurrencyId` and `workerId`.
|
|
4950
|
+
// the id lives on the session, so it is freed when the session disconnects, and
|
|
4951
|
+
// the used set is derived from the live orchestrators, so it stays within maxWorkers
|
|
4952
|
+
getConcurrencyId(sessionId) {
|
|
4953
|
+
const sessions = this.project.vitest._browserSessions;
|
|
4954
|
+
const session = sessions.getSession(sessionId);
|
|
4955
|
+
if (session?.concurrencyId) return session.concurrencyId;
|
|
4956
|
+
const used = /* @__PURE__ */ new Set();
|
|
4957
|
+
for (const id of this.orchestrators.keys()) {
|
|
4958
|
+
const concurrencyId = sessions.getSession(id)?.concurrencyId;
|
|
4959
|
+
if (concurrencyId) used.add(concurrencyId);
|
|
4960
|
+
}
|
|
4961
|
+
let concurrencyId = 1;
|
|
4962
|
+
while (used.has(concurrencyId)) concurrencyId++;
|
|
4963
|
+
if (session) session.concurrencyId = concurrencyId;
|
|
4964
|
+
return concurrencyId;
|
|
4965
|
+
}
|
|
5502
4966
|
getOrchestrator(sessionId) {
|
|
5503
4967
|
const orchestrator = this.orchestrators.get(sessionId);
|
|
5504
4968
|
if (!orchestrator) throw new Error(`Orchestrator not found for session ${sessionId}. This is a bug in Vitest. Please, open a new issue with reproduction.`);
|
|
@@ -5535,11 +4999,14 @@ class BrowserPool {
|
|
|
5535
4999
|
context: this._otel.context,
|
|
5536
5000
|
attributes: { "code.file.path": file.filepath }
|
|
5537
5001
|
}, async () => {
|
|
5002
|
+
const concurrencyId = this.getConcurrencyId(sessionId);
|
|
5538
5003
|
return orchestrator.createTesters({
|
|
5539
5004
|
method,
|
|
5540
5005
|
files: [file],
|
|
5541
5006
|
providedContext: this._providedContext || "[{}]",
|
|
5542
|
-
otelCarrier: this._traces.getContextCarrier()
|
|
5007
|
+
otelCarrier: this._traces.getContextCarrier(),
|
|
5008
|
+
concurrencyId,
|
|
5009
|
+
workerId: concurrencyId
|
|
5543
5010
|
});
|
|
5544
5011
|
}).then(() => {
|
|
5545
5012
|
debug?.("[%s] test %s finished running", sessionId, file);
|
|
@@ -5727,6 +5194,7 @@ class PoolRunner {
|
|
|
5727
5194
|
poolId = void 0;
|
|
5728
5195
|
project;
|
|
5729
5196
|
environment;
|
|
5197
|
+
_lastTestFiles;
|
|
5730
5198
|
_state = RunnerState.IDLE;
|
|
5731
5199
|
_operationLock = null;
|
|
5732
5200
|
_terminatePromise = createDefer();
|
|
@@ -5749,6 +5217,7 @@ class PoolRunner {
|
|
|
5749
5217
|
this.project = options.project;
|
|
5750
5218
|
this.environment = options.environment;
|
|
5751
5219
|
const vitest = this.project.vitest;
|
|
5220
|
+
this._lastTestFiles = [];
|
|
5752
5221
|
this._traces = vitest._traces;
|
|
5753
5222
|
if (this._traces.isEnabled()) {
|
|
5754
5223
|
const { span: workerSpan, context } = this._traces.startContextSpan("vitest.worker");
|
|
@@ -5802,7 +5271,8 @@ class PoolRunner {
|
|
|
5802
5271
|
return span;
|
|
5803
5272
|
}
|
|
5804
5273
|
request(method, context) {
|
|
5805
|
-
this.
|
|
5274
|
+
this._lastTestFiles = context.files.map((f) => f.filepath);
|
|
5275
|
+
this._otel?.files.push(...this._lastTestFiles);
|
|
5806
5276
|
return this.postMessage({
|
|
5807
5277
|
__vitest_worker_request__: true,
|
|
5808
5278
|
type: method,
|
|
@@ -5922,6 +5392,7 @@ class PoolRunner {
|
|
|
5922
5392
|
this._state = RunnerState.STOPPED;
|
|
5923
5393
|
throw error;
|
|
5924
5394
|
} finally {
|
|
5395
|
+
this._lastTestFiles = [];
|
|
5925
5396
|
this._operationLock.resolve();
|
|
5926
5397
|
this._operationLock = null;
|
|
5927
5398
|
this._otel?.span.end();
|
|
@@ -5947,20 +5418,36 @@ class PoolRunner {
|
|
|
5947
5418
|
this._eventEmitter.emit("error", error);
|
|
5948
5419
|
}
|
|
5949
5420
|
};
|
|
5950
|
-
emitUnexpectedExit = () => {
|
|
5951
|
-
const
|
|
5421
|
+
emitUnexpectedExit = (code, signal) => {
|
|
5422
|
+
const hasCode = typeof code === "number";
|
|
5423
|
+
const errorDetails = hasCode || signal ? `with ${hasCode ? `exit code ${code} ` : ""}${signal ? `signal ${signal} ` : ""}` : "";
|
|
5424
|
+
const testFileDetails = this._lastTestFiles.length ? ` while running test file${this._lastTestFiles.length === 1 ? "" : "s"} ${this._lastTestFiles.join(", ")}` : "";
|
|
5425
|
+
const error = /* @__PURE__ */ new Error(`Worker exited unexpectedly ${errorDetails}during ${this._state} state${testFileDetails}`);
|
|
5426
|
+
this._state = RunnerState.STOPPED;
|
|
5952
5427
|
this._eventEmitter.emit("error", error);
|
|
5953
5428
|
};
|
|
5954
5429
|
waitForStart() {
|
|
5955
5430
|
return new Promise((resolve, reject) => {
|
|
5956
|
-
const
|
|
5431
|
+
const cleanup = () => {
|
|
5432
|
+
this.off("message", onStart);
|
|
5433
|
+
this.off("error", onError);
|
|
5434
|
+
};
|
|
5435
|
+
function onStart(message) {
|
|
5957
5436
|
if (message.type === "started") {
|
|
5958
|
-
|
|
5437
|
+
cleanup();
|
|
5959
5438
|
if (message.error) reject(message.error);
|
|
5960
5439
|
else resolve();
|
|
5961
5440
|
}
|
|
5962
|
-
}
|
|
5441
|
+
}
|
|
5442
|
+
// The worker can die before it ever reports back (e.g. an invalid
|
|
5443
|
+
// `execArgv` makes Node exit immediately). Reject as soon as that
|
|
5444
|
+
// happens instead of waiting for the start timeout to elapse.
|
|
5445
|
+
function onError(error) {
|
|
5446
|
+
cleanup();
|
|
5447
|
+
reject(error);
|
|
5448
|
+
}
|
|
5963
5449
|
this.on("message", onStart);
|
|
5450
|
+
this.on("error", onError);
|
|
5964
5451
|
});
|
|
5965
5452
|
}
|
|
5966
5453
|
withTimeout(promise, timeout) {
|
|
@@ -6356,7 +5843,7 @@ class Pool {
|
|
|
6356
5843
|
try {
|
|
6357
5844
|
let isMemoryLimitReached = false;
|
|
6358
5845
|
const runner = this.getPoolRunner(task, method);
|
|
6359
|
-
const poolId = runner.poolId ?? this.
|
|
5846
|
+
const poolId = runner.poolId ?? this.getConcurrencyId();
|
|
6360
5847
|
runner.poolId = poolId;
|
|
6361
5848
|
const activeTask = {
|
|
6362
5849
|
task,
|
|
@@ -6376,14 +5863,18 @@ class Pool {
|
|
|
6376
5863
|
if (task.memoryLimit && message.usedMemory) isMemoryLimitReached = message.usedMemory >= task.memoryLimit;
|
|
6377
5864
|
if (message.error) this.options.state.catchError(message.error, "Test Run Error");
|
|
6378
5865
|
runner.off("message", onFinished);
|
|
5866
|
+
runner.off("error", onTaskError);
|
|
6379
5867
|
resolver.resolve();
|
|
6380
5868
|
}
|
|
6381
5869
|
};
|
|
5870
|
+
function onTaskError(error) {
|
|
5871
|
+
runner.off("message", onFinished);
|
|
5872
|
+
runner.off("error", onTaskError);
|
|
5873
|
+
resolver.reject(new Error(`[vitest-pool]: Worker ${task.worker} emitted error.`, { cause: error }));
|
|
5874
|
+
}
|
|
6382
5875
|
runner.on("message", onFinished);
|
|
5876
|
+
runner.on("error", onTaskError);
|
|
6383
5877
|
if (!runner.isStarted) {
|
|
6384
|
-
runner.on("error", (error) => {
|
|
6385
|
-
resolver.reject(new Error(`[vitest-pool]: Worker ${task.worker} emitted error.`, { cause: error }));
|
|
6386
|
-
});
|
|
6387
5878
|
const id = setTimeout(() => resolver.reject(/* @__PURE__ */ new Error(`[vitest-pool]: Timeout starting ${task.worker} runner.`)), WORKER_START_TIMEOUT);
|
|
6388
5879
|
await runner.start({ workerId: task.context.workerId }).catch((error) => resolver.reject(new Error(`[vitest-pool]: Failed to start ${task.worker} worker for test files ${formatFiles(task)}.`, { cause: error }))).finally(() => clearTimeout(id));
|
|
6389
5880
|
}
|
|
@@ -6396,7 +5887,7 @@ class Pool {
|
|
|
6396
5887
|
await resolver.promise.catch((error) => span?.recordException(error)).finally(() => span?.end());
|
|
6397
5888
|
const index = this.activeTasks.indexOf(activeTask);
|
|
6398
5889
|
if (index !== -1) this.activeTasks.splice(index, 1);
|
|
6399
|
-
if (!task.isolate && !isMemoryLimitReached && this.queue[0]?.task.isolate === false && isEqualRunner(runner, this.queue[0].task)) {
|
|
5890
|
+
if (!task.isolate && !runner.isTerminated && !isMemoryLimitReached && this.queue[0]?.task.isolate === false && isEqualRunner(runner, this.queue[0].task)) {
|
|
6400
5891
|
this.sharedRunners.push(runner);
|
|
6401
5892
|
return this.schedule();
|
|
6402
5893
|
}
|
|
@@ -6466,15 +5957,16 @@ catch (error) {
|
|
|
6466
5957
|
if (customPool != null && customPool.name === task.worker) return new PoolRunner(options, customPool.createPoolWorker(options));
|
|
6467
5958
|
throw new Error(`Runner ${task.worker} is not supported. Test files: ${formatFiles(task)}.`);
|
|
6468
5959
|
}
|
|
6469
|
-
|
|
6470
|
-
let
|
|
5960
|
+
getConcurrencyId() {
|
|
5961
|
+
let concurrencyId;
|
|
6471
5962
|
this.workerIds.forEach((state, id) => {
|
|
6472
|
-
if (state &&
|
|
6473
|
-
|
|
5963
|
+
if (state && concurrencyId == null) {
|
|
5964
|
+
concurrencyId = id;
|
|
6474
5965
|
this.workerIds.set(id, false);
|
|
6475
5966
|
}
|
|
6476
5967
|
});
|
|
6477
|
-
|
|
5968
|
+
if (concurrencyId == null) throw new Error("Cannot set concurrency id because there are no valid free ids.");
|
|
5969
|
+
return concurrencyId;
|
|
6478
5970
|
}
|
|
6479
5971
|
freeWorkerId(id) {
|
|
6480
5972
|
this.workerIds.set(id, true);
|
|
@@ -6519,7 +6011,7 @@ function deepEqual$1(obj1, obj2) {
|
|
|
6519
6011
|
const keys1 = Object.keys(obj1);
|
|
6520
6012
|
const keys2 = Object.keys(obj2);
|
|
6521
6013
|
if (keys1.length !== keys2.length) return false;
|
|
6522
|
-
for (const key of keys1) if (!Object.
|
|
6014
|
+
for (const key of keys1) if (!Object.hasOwn(obj2, key) || !deepEqual$1(obj1[key], obj2[key])) return false;
|
|
6523
6015
|
return true;
|
|
6524
6016
|
}
|
|
6525
6017
|
|
|
@@ -6545,7 +6037,7 @@ function createPool(ctx) {
|
|
|
6545
6037
|
specs = await sequencer.shard(Array.from(specs));
|
|
6546
6038
|
}
|
|
6547
6039
|
const taskGroups = [];
|
|
6548
|
-
let workerId =
|
|
6040
|
+
let workerId = 1;
|
|
6549
6041
|
const sorted = await sequencer.sort(specs);
|
|
6550
6042
|
const { environments, tags } = await getSpecificationsOptions(specs);
|
|
6551
6043
|
const groups = groupSpecs(sorted, environments);
|
|
@@ -7045,6 +6537,9 @@ function requirePermessageDeflate () {
|
|
|
7045
6537
|
* acknowledge disabling of client context takeover
|
|
7046
6538
|
* @param {Number} [options.concurrencyLimit=10] The number of concurrent
|
|
7047
6539
|
* calls to zlib
|
|
6540
|
+
* @param {Boolean} [options.isServer=false] Create the instance in either
|
|
6541
|
+
* server or client mode
|
|
6542
|
+
* @param {Number} [options.maxPayload=0] The maximum allowed message length
|
|
7048
6543
|
* @param {(Boolean|Number)} [options.serverMaxWindowBits] Request/confirm the
|
|
7049
6544
|
* use of a custom server window size
|
|
7050
6545
|
* @param {Boolean} [options.serverNoContextTakeover=false] Request/accept
|
|
@@ -7055,16 +6550,13 @@ function requirePermessageDeflate () {
|
|
|
7055
6550
|
* deflate
|
|
7056
6551
|
* @param {Object} [options.zlibInflateOptions] Options to pass to zlib on
|
|
7057
6552
|
* inflate
|
|
7058
|
-
* @param {Boolean} [isServer=false] Create the instance in either server or
|
|
7059
|
-
* client mode
|
|
7060
|
-
* @param {Number} [maxPayload=0] The maximum allowed message length
|
|
7061
6553
|
*/
|
|
7062
|
-
constructor(options
|
|
7063
|
-
this._maxPayload = maxPayload | 0;
|
|
6554
|
+
constructor(options) {
|
|
7064
6555
|
this._options = options || {};
|
|
7065
6556
|
this._threshold =
|
|
7066
6557
|
this._options.threshold !== undefined ? this._options.threshold : 1024;
|
|
7067
|
-
this.
|
|
6558
|
+
this._maxPayload = this._options.maxPayload | 0;
|
|
6559
|
+
this._isServer = !!this._options.isServer;
|
|
7068
6560
|
this._deflate = null;
|
|
7069
6561
|
this._inflate = null;
|
|
7070
6562
|
|
|
@@ -7745,6 +7237,10 @@ function requireReceiver () {
|
|
|
7745
7237
|
* extensions
|
|
7746
7238
|
* @param {Boolean} [options.isServer=false] Specifies whether to operate in
|
|
7747
7239
|
* client or server mode
|
|
7240
|
+
* @param {Number} [options.maxBufferedChunks=0] The maximum number of
|
|
7241
|
+
* buffered data chunks
|
|
7242
|
+
* @param {Number} [options.maxFragments=0] The maximum number of message
|
|
7243
|
+
* fragments
|
|
7748
7244
|
* @param {Number} [options.maxPayload=0] The maximum allowed message length
|
|
7749
7245
|
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
|
|
7750
7246
|
* not to skip UTF-8 validation for text and close messages
|
|
@@ -7759,6 +7255,8 @@ function requireReceiver () {
|
|
|
7759
7255
|
this._binaryType = options.binaryType || BINARY_TYPES[0];
|
|
7760
7256
|
this._extensions = options.extensions || {};
|
|
7761
7257
|
this._isServer = !!options.isServer;
|
|
7258
|
+
this._maxBufferedChunks = options.maxBufferedChunks | 0;
|
|
7259
|
+
this._maxFragments = options.maxFragments | 0;
|
|
7762
7260
|
this._maxPayload = options.maxPayload | 0;
|
|
7763
7261
|
this._skipUTF8Validation = !!options.skipUTF8Validation;
|
|
7764
7262
|
this[kWebSocket] = undefined;
|
|
@@ -7794,6 +7292,22 @@ function requireReceiver () {
|
|
|
7794
7292
|
_write(chunk, encoding, cb) {
|
|
7795
7293
|
if (this._opcode === 0x08 && this._state == GET_INFO) return cb();
|
|
7796
7294
|
|
|
7295
|
+
if (
|
|
7296
|
+
this._maxBufferedChunks > 0 &&
|
|
7297
|
+
this._buffers.length >= this._maxBufferedChunks
|
|
7298
|
+
) {
|
|
7299
|
+
cb(
|
|
7300
|
+
this.createError(
|
|
7301
|
+
RangeError,
|
|
7302
|
+
'Too many buffered chunks',
|
|
7303
|
+
false,
|
|
7304
|
+
1008,
|
|
7305
|
+
'WS_ERR_TOO_MANY_BUFFERED_PARTS'
|
|
7306
|
+
)
|
|
7307
|
+
);
|
|
7308
|
+
return;
|
|
7309
|
+
}
|
|
7310
|
+
|
|
7797
7311
|
this._bufferedBytes += chunk.length;
|
|
7798
7312
|
this._buffers.push(chunk);
|
|
7799
7313
|
this.startLoop(cb);
|
|
@@ -8190,6 +7704,22 @@ function requireReceiver () {
|
|
|
8190
7704
|
}
|
|
8191
7705
|
|
|
8192
7706
|
if (data.length) {
|
|
7707
|
+
if (
|
|
7708
|
+
this._maxFragments > 0 &&
|
|
7709
|
+
this._fragments.length >= this._maxFragments
|
|
7710
|
+
) {
|
|
7711
|
+
const error = this.createError(
|
|
7712
|
+
RangeError,
|
|
7713
|
+
'Too many message fragments',
|
|
7714
|
+
false,
|
|
7715
|
+
1008,
|
|
7716
|
+
'WS_ERR_TOO_MANY_BUFFERED_PARTS'
|
|
7717
|
+
);
|
|
7718
|
+
|
|
7719
|
+
cb(error);
|
|
7720
|
+
return;
|
|
7721
|
+
}
|
|
7722
|
+
|
|
8193
7723
|
//
|
|
8194
7724
|
// This message is not compressed so its length is the sum of the payload
|
|
8195
7725
|
// length of all fragments.
|
|
@@ -8229,6 +7759,22 @@ function requireReceiver () {
|
|
|
8229
7759
|
return;
|
|
8230
7760
|
}
|
|
8231
7761
|
|
|
7762
|
+
if (
|
|
7763
|
+
this._maxFragments > 0 &&
|
|
7764
|
+
this._fragments.length >= this._maxFragments
|
|
7765
|
+
) {
|
|
7766
|
+
const error = this.createError(
|
|
7767
|
+
RangeError,
|
|
7768
|
+
'Too many message fragments',
|
|
7769
|
+
false,
|
|
7770
|
+
1008,
|
|
7771
|
+
'WS_ERR_TOO_MANY_BUFFERED_PARTS'
|
|
7772
|
+
);
|
|
7773
|
+
|
|
7774
|
+
cb(error);
|
|
7775
|
+
return;
|
|
7776
|
+
}
|
|
7777
|
+
|
|
8232
7778
|
this._fragments.push(buf);
|
|
8233
7779
|
}
|
|
8234
7780
|
|
|
@@ -8423,6 +7969,9 @@ function requireSender () {
|
|
|
8423
7969
|
|
|
8424
7970
|
const { Duplex } = require$$0$2;
|
|
8425
7971
|
const { randomFillSync } = require$$1;
|
|
7972
|
+
const {
|
|
7973
|
+
types: { isUint8Array }
|
|
7974
|
+
} = require$$2;
|
|
8426
7975
|
|
|
8427
7976
|
const PerMessageDeflate = requirePermessageDeflate();
|
|
8428
7977
|
const { EMPTY_BUFFER, kWebSocket, NOOP } = requireConstants();
|
|
@@ -8619,8 +8168,10 @@ function requireSender () {
|
|
|
8619
8168
|
|
|
8620
8169
|
if (typeof data === 'string') {
|
|
8621
8170
|
buf.write(data, 2);
|
|
8622
|
-
} else {
|
|
8171
|
+
} else if (isUint8Array(data)) {
|
|
8623
8172
|
buf.set(data, 2);
|
|
8173
|
+
} else {
|
|
8174
|
+
throw new TypeError('Second argument must be a string or a Uint8Array');
|
|
8624
8175
|
}
|
|
8625
8176
|
}
|
|
8626
8177
|
|
|
@@ -9544,7 +9095,7 @@ function requireWebsocket () {
|
|
|
9544
9095
|
|
|
9545
9096
|
const EventEmitter = require$$0$3;
|
|
9546
9097
|
const https = require$$1$1;
|
|
9547
|
-
const http = require$$2;
|
|
9098
|
+
const http = require$$2$1;
|
|
9548
9099
|
const net = require$$3;
|
|
9549
9100
|
const tls = require$$4;
|
|
9550
9101
|
const { randomBytes, createHash } = require$$1;
|
|
@@ -9741,6 +9292,10 @@ function requireWebsocket () {
|
|
|
9741
9292
|
* multiple times in the same tick
|
|
9742
9293
|
* @param {Function} [options.generateMask] The function used to generate the
|
|
9743
9294
|
* masking key
|
|
9295
|
+
* @param {Number} [options.maxBufferedChunks=0] The maximum number of
|
|
9296
|
+
* buffered data chunks
|
|
9297
|
+
* @param {Number} [options.maxFragments=0] The maximum number of message
|
|
9298
|
+
* fragments
|
|
9744
9299
|
* @param {Number} [options.maxPayload=0] The maximum allowed message size
|
|
9745
9300
|
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
|
|
9746
9301
|
* not to skip UTF-8 validation for text and close messages
|
|
@@ -9752,6 +9307,8 @@ function requireWebsocket () {
|
|
|
9752
9307
|
binaryType: this.binaryType,
|
|
9753
9308
|
extensions: this._extensions,
|
|
9754
9309
|
isServer: this._isServer,
|
|
9310
|
+
maxBufferedChunks: options.maxBufferedChunks,
|
|
9311
|
+
maxFragments: options.maxFragments,
|
|
9755
9312
|
maxPayload: options.maxPayload,
|
|
9756
9313
|
skipUTF8Validation: options.skipUTF8Validation
|
|
9757
9314
|
});
|
|
@@ -10180,6 +9737,10 @@ function requireWebsocket () {
|
|
|
10180
9737
|
* masking key
|
|
10181
9738
|
* @param {Number} [options.handshakeTimeout] Timeout in milliseconds for the
|
|
10182
9739
|
* handshake request
|
|
9740
|
+
* @param {Number} [options.maxBufferedChunks=1048576] The maximum number of
|
|
9741
|
+
* buffered data chunks
|
|
9742
|
+
* @param {Number} [options.maxFragments=131072] The maximum number of message
|
|
9743
|
+
* fragments
|
|
10183
9744
|
* @param {Number} [options.maxPayload=104857600] The maximum allowed message
|
|
10184
9745
|
* size
|
|
10185
9746
|
* @param {Number} [options.maxRedirects=10] The maximum number of redirects
|
|
@@ -10200,6 +9761,8 @@ function requireWebsocket () {
|
|
|
10200
9761
|
autoPong: true,
|
|
10201
9762
|
closeTimeout: CLOSE_TIMEOUT,
|
|
10202
9763
|
protocolVersion: protocolVersions[1],
|
|
9764
|
+
maxBufferedChunks: 1024 * 1024,
|
|
9765
|
+
maxFragments: 128 * 1024,
|
|
10203
9766
|
maxPayload: 100 * 1024 * 1024,
|
|
10204
9767
|
skipUTF8Validation: false,
|
|
10205
9768
|
perMessageDeflate: true,
|
|
@@ -10233,7 +9796,7 @@ function requireWebsocket () {
|
|
|
10233
9796
|
} else {
|
|
10234
9797
|
try {
|
|
10235
9798
|
parsedUrl = new URL(address);
|
|
10236
|
-
} catch
|
|
9799
|
+
} catch {
|
|
10237
9800
|
throw new SyntaxError(`Invalid URL: ${address}`);
|
|
10238
9801
|
}
|
|
10239
9802
|
}
|
|
@@ -10295,11 +9858,11 @@ function requireWebsocket () {
|
|
|
10295
9858
|
opts.timeout = opts.handshakeTimeout;
|
|
10296
9859
|
|
|
10297
9860
|
if (opts.perMessageDeflate) {
|
|
10298
|
-
perMessageDeflate = new PerMessageDeflate(
|
|
10299
|
-
opts.perMessageDeflate
|
|
10300
|
-
false,
|
|
10301
|
-
opts.maxPayload
|
|
10302
|
-
);
|
|
9861
|
+
perMessageDeflate = new PerMessageDeflate({
|
|
9862
|
+
...opts.perMessageDeflate,
|
|
9863
|
+
isServer: false,
|
|
9864
|
+
maxPayload: opts.maxPayload
|
|
9865
|
+
});
|
|
10303
9866
|
opts.headers['Sec-WebSocket-Extensions'] = format({
|
|
10304
9867
|
[PerMessageDeflate.extensionName]: perMessageDeflate.offer()
|
|
10305
9868
|
});
|
|
@@ -10557,6 +10120,8 @@ function requireWebsocket () {
|
|
|
10557
10120
|
websocket.setSocket(socket, head, {
|
|
10558
10121
|
allowSynchronousEvents: opts.allowSynchronousEvents,
|
|
10559
10122
|
generateMask: opts.generateMask,
|
|
10123
|
+
maxBufferedChunks: opts.maxBufferedChunks,
|
|
10124
|
+
maxFragments: opts.maxFragments,
|
|
10560
10125
|
maxPayload: opts.maxPayload,
|
|
10561
10126
|
skipUTF8Validation: opts.skipUTF8Validation
|
|
10562
10127
|
});
|
|
@@ -11106,12 +10671,14 @@ function requireStream () {
|
|
|
11106
10671
|
|
|
11107
10672
|
requireStream();
|
|
11108
10673
|
|
|
10674
|
+
requireExtension();
|
|
10675
|
+
|
|
10676
|
+
requirePermessageDeflate();
|
|
10677
|
+
|
|
11109
10678
|
requireReceiver();
|
|
11110
10679
|
|
|
11111
10680
|
requireSender();
|
|
11112
10681
|
|
|
11113
|
-
requireWebsocket();
|
|
11114
|
-
|
|
11115
10682
|
var subprotocol;
|
|
11116
10683
|
var hasRequiredSubprotocol;
|
|
11117
10684
|
|
|
@@ -11182,6 +10749,10 @@ function requireSubprotocol () {
|
|
|
11182
10749
|
return subprotocol;
|
|
11183
10750
|
}
|
|
11184
10751
|
|
|
10752
|
+
requireSubprotocol();
|
|
10753
|
+
|
|
10754
|
+
requireWebsocket();
|
|
10755
|
+
|
|
11185
10756
|
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Duplex$", "caughtErrors": "none" }] */
|
|
11186
10757
|
|
|
11187
10758
|
var websocketServer;
|
|
@@ -11192,7 +10763,7 @@ function requireWebsocketServer () {
|
|
|
11192
10763
|
hasRequiredWebsocketServer = 1;
|
|
11193
10764
|
|
|
11194
10765
|
const EventEmitter = require$$0$3;
|
|
11195
|
-
const http = require$$2;
|
|
10766
|
+
const http = require$$2$1;
|
|
11196
10767
|
const { Duplex } = require$$0$2;
|
|
11197
10768
|
const { createHash } = require$$1;
|
|
11198
10769
|
|
|
@@ -11232,6 +10803,10 @@ function requireWebsocketServer () {
|
|
|
11232
10803
|
* called
|
|
11233
10804
|
* @param {Function} [options.handleProtocols] A hook to handle protocols
|
|
11234
10805
|
* @param {String} [options.host] The hostname where to bind the server
|
|
10806
|
+
* @param {Number} [options.maxBufferedChunks=1048576] The maximum number of
|
|
10807
|
+
* buffered data chunks
|
|
10808
|
+
* @param {Number} [options.maxFragments=131072] The maximum number of message
|
|
10809
|
+
* fragments
|
|
11235
10810
|
* @param {Number} [options.maxPayload=104857600] The maximum allowed message
|
|
11236
10811
|
* size
|
|
11237
10812
|
* @param {Boolean} [options.noServer=false] Enable no server mode
|
|
@@ -11254,6 +10829,8 @@ function requireWebsocketServer () {
|
|
|
11254
10829
|
options = {
|
|
11255
10830
|
allowSynchronousEvents: true,
|
|
11256
10831
|
autoPong: true,
|
|
10832
|
+
maxBufferedChunks: 1024 * 1024,
|
|
10833
|
+
maxFragments: 128 * 1024,
|
|
11257
10834
|
maxPayload: 100 * 1024 * 1024,
|
|
11258
10835
|
skipUTF8Validation: false,
|
|
11259
10836
|
perMessageDeflate: false,
|
|
@@ -11482,11 +11059,11 @@ function requireWebsocketServer () {
|
|
|
11482
11059
|
this.options.perMessageDeflate &&
|
|
11483
11060
|
secWebSocketExtensions !== undefined
|
|
11484
11061
|
) {
|
|
11485
|
-
const perMessageDeflate = new PerMessageDeflate(
|
|
11486
|
-
this.options.perMessageDeflate,
|
|
11487
|
-
true,
|
|
11488
|
-
this.options.maxPayload
|
|
11489
|
-
);
|
|
11062
|
+
const perMessageDeflate = new PerMessageDeflate({
|
|
11063
|
+
...this.options.perMessageDeflate,
|
|
11064
|
+
isServer: true,
|
|
11065
|
+
maxPayload: this.options.maxPayload
|
|
11066
|
+
});
|
|
11490
11067
|
|
|
11491
11068
|
try {
|
|
11492
11069
|
const offers = extension.parse(secWebSocketExtensions);
|
|
@@ -11613,6 +11190,8 @@ function requireWebsocketServer () {
|
|
|
11613
11190
|
|
|
11614
11191
|
ws.setSocket(socket, head, {
|
|
11615
11192
|
allowSynchronousEvents: this.options.allowSynchronousEvents,
|
|
11193
|
+
maxBufferedChunks: this.options.maxBufferedChunks,
|
|
11194
|
+
maxFragments: this.options.maxFragments,
|
|
11616
11195
|
maxPayload: this.options.maxPayload,
|
|
11617
11196
|
skipUTF8Validation: this.options.skipUTF8Validation
|
|
11618
11197
|
});
|
|
@@ -12187,8 +11766,9 @@ function serializeConfig(project) {
|
|
|
12187
11766
|
allowWrite: api?.allowWrite
|
|
12188
11767
|
};
|
|
12189
11768
|
})(project.isBrowserEnabled() ? config.browser.api : config.api),
|
|
12190
|
-
diff: config.diff,
|
|
11769
|
+
diff: serializeDiffOptions(config.diff),
|
|
12191
11770
|
retry: config.retry,
|
|
11771
|
+
repeats: config.repeats,
|
|
12192
11772
|
disableConsoleIntercept: config.disableConsoleIntercept,
|
|
12193
11773
|
root: config.root,
|
|
12194
11774
|
name: config.name,
|
|
@@ -12223,11 +11803,11 @@ function serializeConfig(project) {
|
|
|
12223
11803
|
expand: config.snapshotOptions.expand ?? globalConfig.snapshotOptions.expand
|
|
12224
11804
|
},
|
|
12225
11805
|
sequence: {
|
|
12226
|
-
shuffle:
|
|
12227
|
-
concurrent:
|
|
11806
|
+
shuffle: config.sequence.shuffle,
|
|
11807
|
+
concurrent: config.sequence.concurrent,
|
|
12228
11808
|
seed: globalConfig.sequence.seed,
|
|
12229
|
-
hooks:
|
|
12230
|
-
setupFiles:
|
|
11809
|
+
hooks: config.sequence.hooks,
|
|
11810
|
+
setupFiles: config.sequence.setupFiles
|
|
12231
11811
|
},
|
|
12232
11812
|
inspect: globalConfig.inspect,
|
|
12233
11813
|
inspectBrk: globalConfig.inspectBrk,
|
|
@@ -12285,6 +11865,36 @@ function serializeConfig(project) {
|
|
|
12285
11865
|
disableColors: isAgent && !isForceColor()
|
|
12286
11866
|
};
|
|
12287
11867
|
}
|
|
11868
|
+
const serializableDiffKeys = [
|
|
11869
|
+
"aAnnotation",
|
|
11870
|
+
"aIndicator",
|
|
11871
|
+
"bAnnotation",
|
|
11872
|
+
"bIndicator",
|
|
11873
|
+
"commonIndicator",
|
|
11874
|
+
"contextLines",
|
|
11875
|
+
"emptyFirstOrLastLinePlaceholder",
|
|
11876
|
+
"expand",
|
|
11877
|
+
"includeChangeCounts",
|
|
11878
|
+
"omitAnnotationLines",
|
|
11879
|
+
"printBasicPrototype",
|
|
11880
|
+
"maxDepth",
|
|
11881
|
+
"truncateThreshold",
|
|
11882
|
+
"truncateAnnotation"
|
|
11883
|
+
];
|
|
11884
|
+
// `diff` can be an inline object containing color/compareKeys functions
|
|
11885
|
+
// (`DiffOptions`). Those functions are not structured-cloneable (threads pool)
|
|
11886
|
+
// and are silently dropped over `child_process` IPC (forks pool), so passing
|
|
11887
|
+
// the raw object to workers throws `DataCloneError`. Forward only the
|
|
11888
|
+
// serializable fields declared by `SerializedDiffOptions`, and only the ones
|
|
11889
|
+
// actually set — explicit `undefined` values would override the diff defaults
|
|
11890
|
+
// when the worker merges the options. The function-based options still
|
|
11891
|
+
// require the file-path form, which workers import locally.
|
|
11892
|
+
function serializeDiffOptions(diff) {
|
|
11893
|
+
if (diff == null || typeof diff === "string") return diff;
|
|
11894
|
+
const result = {};
|
|
11895
|
+
for (const key of serializableDiffKeys) if (diff[key] !== void 0) result[key] = diff[key];
|
|
11896
|
+
return result;
|
|
11897
|
+
}
|
|
12288
11898
|
|
|
12289
11899
|
async function loadGlobalSetupFiles(runner, globalSetup) {
|
|
12290
11900
|
const globalSetupFiles = toArray(globalSetup);
|
|
@@ -13361,7 +12971,10 @@ function WorkspaceVitestPlugin(project, options) {
|
|
|
13361
12971
|
ws: false,
|
|
13362
12972
|
preTransformRequests: false,
|
|
13363
12973
|
middlewareMode: true,
|
|
13364
|
-
fs: {
|
|
12974
|
+
fs: {
|
|
12975
|
+
allow: resolveFsAllow(project.vitest.config.root, project.vitest.vite.config.configFile),
|
|
12976
|
+
deny: [API_TOKEN_FILE]
|
|
12977
|
+
}
|
|
13365
12978
|
},
|
|
13366
12979
|
environments: { ssr: { resolve: resolveOptions } },
|
|
13367
12980
|
test: {}
|
|
@@ -13475,7 +13088,7 @@ const defaultInline = [
|
|
|
13475
13088
|
];
|
|
13476
13089
|
const depsExternal = [/\/node_modules\/.*\.cjs\.js$/, /\/node_modules\/.*\.mjs$/];
|
|
13477
13090
|
function guessCJSversion(id) {
|
|
13478
|
-
if (
|
|
13091
|
+
if (ESM_EXT_RE.test(id)) {
|
|
13479
13092
|
for (const i of [
|
|
13480
13093
|
id.replace(ESM_EXT_RE, ".mjs"),
|
|
13481
13094
|
id.replace(ESM_EXT_RE, ".umd.js"),
|
|
@@ -13483,7 +13096,7 @@ function guessCJSversion(id) {
|
|
|
13483
13096
|
id.replace(ESM_EXT_RE, ".js")
|
|
13484
13097
|
]) if (existsSync(i)) return i;
|
|
13485
13098
|
}
|
|
13486
|
-
if (
|
|
13099
|
+
if (ESM_FOLDER_RE.test(id)) {
|
|
13487
13100
|
for (const i of [
|
|
13488
13101
|
id.replace(ESM_FOLDER_RE, "/umd/$1"),
|
|
13489
13102
|
id.replace(ESM_FOLDER_RE, "/cjs/$1"),
|
|
@@ -14116,6 +13729,7 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
|
|
|
14116
13729
|
"expandSnapshotDiff",
|
|
14117
13730
|
"disableConsoleIntercept",
|
|
14118
13731
|
"retry",
|
|
13732
|
+
"repeats",
|
|
14119
13733
|
"testNamePattern",
|
|
14120
13734
|
"passWithNoTests",
|
|
14121
13735
|
"bail",
|
|
@@ -14474,7 +14088,8 @@ class BlobReporter {
|
|
|
14474
14088
|
errors,
|
|
14475
14089
|
coverage,
|
|
14476
14090
|
executionTime,
|
|
14477
|
-
environmentModules
|
|
14091
|
+
environmentModules,
|
|
14092
|
+
this.ctx.state.transformTime
|
|
14478
14093
|
]);
|
|
14479
14094
|
let outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "blob");
|
|
14480
14095
|
if (outputFile) {
|
|
@@ -14503,7 +14118,7 @@ async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
|
|
|
14503
14118
|
const promises = (await readdir(resolvedDir)).map(async (filename) => {
|
|
14504
14119
|
const fullPath = resolve$1(resolvedDir, filename);
|
|
14505
14120
|
if (!(await stat(fullPath)).isFile()) throw new TypeError(`vitest.mergeReports() expects all paths in "${blobsDirectory}" to be files generated by the blob reporter, but "${filename}" is not a file`);
|
|
14506
|
-
const [version, files, errors, coverage, executionTime, environmentModules] = parse(await readFile(fullPath, "utf-8"));
|
|
14121
|
+
const [version, files, errors, coverage, executionTime, environmentModules, transformTime] = parse(await readFile(fullPath, "utf-8"));
|
|
14507
14122
|
if (!version) throw new TypeError(`vitest.mergeReports() expects all paths in "${blobsDirectory}" to be files generated by the blob reporter, but "${filename}" is not a valid blob file`);
|
|
14508
14123
|
return {
|
|
14509
14124
|
version,
|
|
@@ -14512,7 +14127,8 @@ async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
|
|
|
14512
14127
|
coverage,
|
|
14513
14128
|
file: filename,
|
|
14514
14129
|
executionTime,
|
|
14515
|
-
environmentModules
|
|
14130
|
+
environmentModules,
|
|
14131
|
+
transformTime
|
|
14516
14132
|
};
|
|
14517
14133
|
});
|
|
14518
14134
|
const blobs = await Promise.all(promises);
|
|
@@ -14547,7 +14163,8 @@ async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
|
|
|
14547
14163
|
}),
|
|
14548
14164
|
errors: blobs.flatMap((blob) => blob.errors),
|
|
14549
14165
|
coverages: blobs.map((blob) => blob.coverage),
|
|
14550
|
-
executionTimes: blobs.map((blob) => blob.executionTime)
|
|
14166
|
+
executionTimes: blobs.map((blob) => blob.executionTime),
|
|
14167
|
+
transformTimes: blobs.map((blob) => blob.transformTime)
|
|
14551
14168
|
};
|
|
14552
14169
|
}
|
|
14553
14170
|
function serializeEnvironmentModuleGraph(environment) {
|
|
@@ -14873,7 +14490,7 @@ class BaseReporter {
|
|
|
14873
14490
|
getFullName(test, separator) {
|
|
14874
14491
|
if (test === test.file) return test.name;
|
|
14875
14492
|
let name = test.file.name;
|
|
14876
|
-
if (test.location) name += c.dim(`:${test.location.line}
|
|
14493
|
+
if (test.location) name += c.dim(`:${test.location.line}`);
|
|
14877
14494
|
name += separator;
|
|
14878
14495
|
name += getTestName(test, separator);
|
|
14879
14496
|
return name;
|
|
@@ -15304,7 +14921,7 @@ function deepEqual(a, b) {
|
|
|
15304
14921
|
const keysA = Object.keys(a);
|
|
15305
14922
|
const keysB = Object.keys(b);
|
|
15306
14923
|
if (keysA.length !== keysB.length) return false;
|
|
15307
|
-
for (const key of keysA) if (!Object.
|
|
14924
|
+
for (const key of keysA) if (!Object.hasOwn(b, key) || !deepEqual(a[key], b[key])) return false;
|
|
15308
14925
|
return true;
|
|
15309
14926
|
}
|
|
15310
14927
|
function sum(items, cb) {
|
|
@@ -16233,22 +15850,20 @@ class JsonReporter {
|
|
|
16233
15850
|
testResults,
|
|
16234
15851
|
coverageMap: this.coverageMap
|
|
16235
15852
|
};
|
|
16236
|
-
|
|
16237
|
-
}
|
|
16238
|
-
/**
|
|
16239
|
-
* Writes the report to an output file if specified in the config,
|
|
16240
|
-
* or logs it to the console otherwise.
|
|
16241
|
-
* @param report
|
|
16242
|
-
*/
|
|
16243
|
-
async writeReport(report) {
|
|
15853
|
+
const resultString = JSON.stringify(result);
|
|
16244
15854
|
const outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "json");
|
|
16245
15855
|
if (outputFile) {
|
|
16246
15856
|
const reportFile = resolve$1(this.ctx.config.root, outputFile);
|
|
16247
15857
|
const outputDirectory = dirname(reportFile);
|
|
16248
15858
|
if (!existsSync(outputDirectory)) await promises.mkdir(outputDirectory, { recursive: true });
|
|
16249
|
-
await promises.writeFile(reportFile,
|
|
15859
|
+
await promises.writeFile(reportFile, resultString, "utf-8");
|
|
16250
15860
|
this.ctx.logger.log(`JSON report written to ${reportFile}`);
|
|
16251
|
-
} else this.ctx.logger.log(
|
|
15861
|
+
} else if (this.options.stdout) this.ctx.logger.log(resultString);
|
|
15862
|
+
else {
|
|
15863
|
+
const report = this.ctx.createReport("json");
|
|
15864
|
+
await report.writeFile("output.json", resultString);
|
|
15865
|
+
this.ctx.logger.log(`JSON report written to ${resolve$1(report.root, "output.json")}`);
|
|
15866
|
+
}
|
|
16252
15867
|
}
|
|
16253
15868
|
}
|
|
16254
15869
|
|
|
@@ -16332,6 +15947,8 @@ class JUnitReporter {
|
|
|
16332
15947
|
this.reportFile = resolve$1(this.ctx.config.root, outputFile);
|
|
16333
15948
|
const outputDirectory = dirname(this.reportFile);
|
|
16334
15949
|
if (!existsSync(outputDirectory)) await promises.mkdir(outputDirectory, { recursive: true });
|
|
15950
|
+
} else if (!this.options.stdout) this.reportFile = resolve$1(this.ctx.createReport("junit").root, "output.xml");
|
|
15951
|
+
if (this.reportFile) {
|
|
16335
15952
|
this.fileFd = await promises.open(this.reportFile, "w+");
|
|
16336
15953
|
this.baseLog = async (text) => {
|
|
16337
15954
|
if (!this.fileFd) this.fileFd = await promises.open(this.reportFile, "w+");
|
|
@@ -16711,7 +16328,7 @@ class VerboseReporter extends DefaultReporter {
|
|
|
16711
16328
|
if (this.ctx.config.hideSkippedTests && testResult.state === "skipped" && test.options.mode !== "todo") return;
|
|
16712
16329
|
let title = ` ${this.getEntityPrefix(test)} `;
|
|
16713
16330
|
title += test.module.task.name;
|
|
16714
|
-
if (test.location) title += c.dim(`:${test.location.line}
|
|
16331
|
+
if (test.location) title += c.dim(`:${test.location.line}`);
|
|
16715
16332
|
title += separator;
|
|
16716
16333
|
title += getTestName(test.task, separator);
|
|
16717
16334
|
title += this.getTestCaseSuffix(test);
|
|
@@ -16775,11 +16392,11 @@ function parseFilter(filter) {
|
|
|
16775
16392
|
const colonIndex = filter.lastIndexOf(":");
|
|
16776
16393
|
if (colonIndex === -1) return { filename: filter };
|
|
16777
16394
|
const [parsedFilename, lineNumber] = [filter.substring(0, colonIndex), filter.substring(colonIndex + 1)];
|
|
16778
|
-
if (
|
|
16395
|
+
if (/^\d+$/.test(lineNumber)) return {
|
|
16779
16396
|
filename: parsedFilename,
|
|
16780
16397
|
lineNumber: Number.parseInt(lineNumber)
|
|
16781
16398
|
};
|
|
16782
|
-
else if (
|
|
16399
|
+
else if (/^\d+-\d+$/.test(lineNumber)) throw new RangeLocationFilterProvidedError(filter);
|
|
16783
16400
|
else return { filename: filter };
|
|
16784
16401
|
}
|
|
16785
16402
|
function groupFilters(filters) {
|
|
@@ -17207,7 +16824,7 @@ class TestSuite extends SuiteImplementation {
|
|
|
17207
16824
|
*/
|
|
17208
16825
|
toTestSpecification() {
|
|
17209
16826
|
const isTypecheck = this.task.meta.typecheck === true;
|
|
17210
|
-
const testIds =
|
|
16827
|
+
const testIds = Array.from(this.children.allTests(), (test) => test.id);
|
|
17211
16828
|
return this.project.createSpecification(this.module.moduleId, { testIds }, isTypecheck ? "typecheck" : void 0);
|
|
17212
16829
|
}
|
|
17213
16830
|
/**
|
|
@@ -17274,7 +16891,9 @@ class TestModule extends SuiteImplementation {
|
|
|
17274
16891
|
setupDuration,
|
|
17275
16892
|
duration: this.task.result?.duration || 0,
|
|
17276
16893
|
heap: this.task.result?.heap,
|
|
17277
|
-
importDurations: this.task.importDurations ?? {}
|
|
16894
|
+
importDurations: this.task.importDurations ?? {},
|
|
16895
|
+
concurrencyId: this.task.concurrencyId,
|
|
16896
|
+
workerId: this.task.workerId
|
|
17278
16897
|
};
|
|
17279
16898
|
}
|
|
17280
16899
|
}
|
|
@@ -18743,13 +18362,15 @@ class Vitest {
|
|
|
18743
18362
|
async mergeReports(directory) {
|
|
18744
18363
|
return this._traces.$("vitest.merge_reports", async () => {
|
|
18745
18364
|
if (this.reporters.some((r) => r instanceof BlobReporter)) throw new Error("Cannot merge reports when `--reporter=blob` is used. Remove blob reporter from the config first.");
|
|
18746
|
-
const { files, errors, coverages, executionTimes } = await readBlobs(this.version, directory || this.config.mergeReports, this.projects);
|
|
18365
|
+
const { files, errors, coverages, executionTimes, transformTimes } = await readBlobs(this.version, directory || this.config.mergeReports, this.projects);
|
|
18747
18366
|
this.state.blobs = {
|
|
18748
18367
|
files,
|
|
18749
18368
|
errors,
|
|
18750
18369
|
coverages,
|
|
18751
|
-
executionTimes
|
|
18370
|
+
executionTimes,
|
|
18371
|
+
transformTimes
|
|
18752
18372
|
};
|
|
18373
|
+
this.state.transformTime = transformTimes.reduce((a, b) => a + b, 0);
|
|
18753
18374
|
await this.report("onInit", this);
|
|
18754
18375
|
const specifications = [];
|
|
18755
18376
|
for (const file of files) {
|
|
@@ -18775,7 +18396,7 @@ class Vitest {
|
|
|
18775
18396
|
* Returns the seed, if tests are running in a random order.
|
|
18776
18397
|
*/
|
|
18777
18398
|
getSeed() {
|
|
18778
|
-
return this.config.sequence.seed
|
|
18399
|
+
return this.config.sequence.sequencer === RandomSequencer || !!this.config.sequence.shuffle || this.projects.some((p) => !!p.config.sequence.shuffle) ? this.config.sequence.seed : null;
|
|
18779
18400
|
}
|
|
18780
18401
|
/** @internal */
|
|
18781
18402
|
async _reportFileTask(file) {
|
|
@@ -19138,7 +18759,7 @@ class Vitest {
|
|
|
19138
18759
|
*/
|
|
19139
18760
|
async cancelCurrentRun(reason) {
|
|
19140
18761
|
this.isCancelling = true;
|
|
19141
|
-
this.cancelPromise = Promise.all(
|
|
18762
|
+
this.cancelPromise = Promise.all(Array.from(this._onCancelListeners, (listener) => listener(reason)));
|
|
19142
18763
|
await this.cancelPromise.finally(() => this.cancelPromise = void 0);
|
|
19143
18764
|
await this.runningPromise;
|
|
19144
18765
|
}
|
|
@@ -19506,8 +19127,6 @@ async function VitestPlugin(options = {}, vitest = new Vitest(deepClone(options)
|
|
|
19506
19127
|
const defines = deleteDefineConfig(viteConfig);
|
|
19507
19128
|
options.defines = defines;
|
|
19508
19129
|
options.viteDefine = originalDefine;
|
|
19509
|
-
let open = false;
|
|
19510
|
-
if (testConfig.ui && testConfig.open) open = testConfig.uiBase ?? "/__vitest__/";
|
|
19511
19130
|
const resolveOptions = getDefaultResolveOptions();
|
|
19512
19131
|
let config = {
|
|
19513
19132
|
base: "/",
|
|
@@ -19519,11 +19138,14 @@ async function VitestPlugin(options = {}, vitest = new Vitest(deepClone(options)
|
|
|
19519
19138
|
},
|
|
19520
19139
|
server: {
|
|
19521
19140
|
...testConfig.api,
|
|
19522
|
-
open,
|
|
19141
|
+
open: false,
|
|
19523
19142
|
hmr: false,
|
|
19524
19143
|
ws: testConfig.api?.middlewareMode ? false : void 0,
|
|
19525
19144
|
preTransformRequests: false,
|
|
19526
|
-
fs: {
|
|
19145
|
+
fs: {
|
|
19146
|
+
allow: resolveFsAllow(options.root || process.cwd(), testConfig.config),
|
|
19147
|
+
deny: [API_TOKEN_FILE]
|
|
19148
|
+
}
|
|
19527
19149
|
},
|
|
19528
19150
|
build: {
|
|
19529
19151
|
outDir: "dummy-non-existing-folder",
|
|
@@ -19638,7 +19260,7 @@ async function createVitest(modeOrOptions, optionsOrViteOverrides = {}, viteOver
|
|
|
19638
19260
|
}
|
|
19639
19261
|
const ctx = new Vitest(deepClone(options), vitestOptions);
|
|
19640
19262
|
const root = slash(resolve$2(options.root || process.cwd()));
|
|
19641
|
-
const configPath = options.config === false ? false : options.config ? resolveModule(options.config, { paths: [root] }) ?? resolve$2(root, options.config) :
|
|
19263
|
+
const configPath = options.config === false ? false : options.config ? resolveModule(options.config, { paths: [root] }) ?? resolve$2(root, options.config) : findConfigFile(root);
|
|
19642
19264
|
options.config = configPath;
|
|
19643
19265
|
const { browser: _removeBrowser, ...restOptions } = options;
|
|
19644
19266
|
const config = {
|
|
@@ -19649,7 +19271,33 @@ async function createVitest(modeOrOptions, optionsOrViteOverrides = {}, viteOver
|
|
|
19649
19271
|
};
|
|
19650
19272
|
try {
|
|
19651
19273
|
const server = await createViteServer(mergeConfig(config, mergeConfig(viteOverrides, { root: options.root })));
|
|
19652
|
-
if (ctx.config.api?.port)
|
|
19274
|
+
if (ctx.config.api?.port) {
|
|
19275
|
+
await server.listen();
|
|
19276
|
+
if (ctx.config.ui && ctx.config.open) {
|
|
19277
|
+
// Note: `tokenCreated` is only an approximation of "the browser is not
|
|
19278
|
+
// authenticated yet". If the user clears cookies while the token file
|
|
19279
|
+
// persists, the clean URL will block until they re-open the `?token=`
|
|
19280
|
+
// URL printed in the terminal.
|
|
19281
|
+
if (ctx.config.api.tokenCreated) {
|
|
19282
|
+
// First run that generated the token: no browser holds the auth
|
|
19283
|
+
// cookie yet, so open the authenticated URL to set it. A new tab
|
|
19284
|
+
// here is fine since no clean-URL tab exists to reuse.
|
|
19285
|
+
const url = new URL(ctx.config.uiBase, "http://localhost");
|
|
19286
|
+
url.searchParams.set("token", ctx.config.api.token);
|
|
19287
|
+
server.config.server.open = `${url.pathname}${url.search}`;
|
|
19288
|
+
} else
|
|
19289
|
+
// Subsequent runs: open the clean UI base URL (without `?token=`)
|
|
19290
|
+
// rather than the authenticated URL printed by the logger. On macOS,
|
|
19291
|
+
// `openBrowser` reuses an existing tab whose URL matches via substring
|
|
19292
|
+
// and reloads it (Vite's `bin/openChrome.js`). Since the 302 redirect
|
|
19293
|
+
// strips the token, an already-authenticated tab lives at the clean
|
|
19294
|
+
// URL, so opening the clean URL matches and reloads it; opening the
|
|
19295
|
+
// token URL would never match and would spawn a new tab on every
|
|
19296
|
+
// restart.
|
|
19297
|
+
server.config.server.open = ctx.config.uiBase;
|
|
19298
|
+
server.openBrowser();
|
|
19299
|
+
}
|
|
19300
|
+
}
|
|
19653
19301
|
return ctx;
|
|
19654
19302
|
}
|
|
19655
19303
|
// Vitest can fail at any point inside "setServer" or inside a custom plugin
|
|
@@ -19796,7 +19444,6 @@ const keys = [
|
|
|
19796
19444
|
["p", "filter by a filename"],
|
|
19797
19445
|
["t", "filter by a test name regex pattern"],
|
|
19798
19446
|
["w", "filter by a project name"],
|
|
19799
|
-
["b", "start the browser server if not started yet"],
|
|
19800
19447
|
["q", "quit"]
|
|
19801
19448
|
];
|
|
19802
19449
|
const cancelKeys = [
|
|
@@ -19812,7 +19459,7 @@ ${keys.map((i) => c.dim(" press ") + c.reset([i[0]].flat().map(c.bold).join(",
|
|
|
19812
19459
|
`);
|
|
19813
19460
|
}
|
|
19814
19461
|
function* traverseFilteredTestNames(parentName, filter, t) {
|
|
19815
|
-
if (
|
|
19462
|
+
if (t.type === "test") {
|
|
19816
19463
|
if (t.name.match(filter)) {
|
|
19817
19464
|
const displayName = `${parentName} > ${t.name}`;
|
|
19818
19465
|
yield {
|
|
@@ -19881,14 +19528,6 @@ function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
|
|
|
19881
19528
|
if (name === "t") return inputNamePattern();
|
|
19882
19529
|
// change fileNamePattern
|
|
19883
19530
|
if (name === "p") return inputFilePattern();
|
|
19884
|
-
if (name === "b") {
|
|
19885
|
-
await ctx._initBrowserServers();
|
|
19886
|
-
ctx.projects.forEach((project) => {
|
|
19887
|
-
ctx.logger.log();
|
|
19888
|
-
ctx.logger.printBrowserBanner(project);
|
|
19889
|
-
});
|
|
19890
|
-
return null;
|
|
19891
|
-
}
|
|
19892
19531
|
}
|
|
19893
19532
|
async function keypressHandler(str, key) {
|
|
19894
19533
|
await _keypressHandler(str, key);
|
|
@@ -20149,4 +19788,4 @@ var cliApi = /*#__PURE__*/Object.freeze({
|
|
|
20149
19788
|
startVitest: startVitest
|
|
20150
19789
|
});
|
|
20151
19790
|
|
|
20152
|
-
export {
|
|
19791
|
+
export { BaseCoverageProvider as B, DefaultReporter as D, ForksPoolWorker as F, GitNotFoundError as G, HangingProcessReporter as H, JUnitReporter as J, MinimalReporter as M, ReportersMap as R, TapFlatReporter as T, Vitest as V, VitestPlugin as a, DotReporter as b, GithubActionsReporter as c, JsonReporter as d, TapReporter as e, FilesNotFoundError as f, ThreadsPoolWorker as g, TypecheckPoolWorker as h, VerboseReporter as i, VitestPackageInstaller as j, VmForksPoolWorker as k, VmThreadsPoolWorker as l, createDebugger as m, createMethodsRPC as n, createViteLogger as o, createVitest as p, escapeTestName as q, experimental_getRunnerTask as r, getFilePoolName as s, isFileServingAllowed as t, isValidApiRequest as u, registerConsoleShortcuts as v, resolveFsAllow as w, startVitest as x, cliApi as y };
|