vitest 5.0.0-beta.2 → 5.0.0-beta.4
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/dist/browser.d.ts +1 -1
- package/dist/browser.js +1 -1
- package/dist/chunks/{base.Opc_YHkk.js → base.BEGVMQrS.js} +6 -6
- package/dist/chunks/{browser.d.BUhkKcDl.d.ts → browser.d.BGxB4Xum.d.ts} +5 -26
- package/dist/chunks/{cac.8N4bOkkB.js → cac.CyXAEMkE.js} +26 -33
- package/dist/chunks/{cli-api.B0RFke2g.js → cli-api.DJMXq34b.js} +640 -615
- package/dist/chunks/{config.d.D91DHYaD.d.ts → config.d.DXq1aBpy.d.ts} +93 -89
- package/dist/chunks/{creator.BqL2U_x4.js → creator.D66cVXYh.js} +2 -2
- package/dist/chunks/{defaults.szbHWQun.js → defaults.CUUnbOrq.js} +6 -4
- package/dist/chunks/{env.D4Lgay0q.js → env.BKKtU2WC.js} +2 -1
- package/dist/chunks/global.d.BtKPuz2X.d.ts +194 -0
- package/dist/chunks/{globals.EHmmu0nC.js → globals.BuY-yD0m.js} +2 -1
- package/dist/chunks/{index.D_7-4CaB.js → index.CE58PZNH.js} +1660 -701
- package/dist/chunks/{index.CViWo__T.js → index.CcluKS59.js} +4 -4
- package/dist/chunks/{index.CbgUM9E5.js → index.nQFVd50u.js} +2 -1
- package/dist/chunks/{init-forks.DMge3WTt.js → init-forks.Ce3vGWgL.js} +1 -1
- package/dist/chunks/{init-threads.eIoyCTon.js → init-threads.8e1OLv5v.js} +1 -1
- package/dist/chunks/{init.BVd7SaCA.js → init.6qx-LaHs.js} +31 -3
- package/dist/chunks/{nativeModuleMocker.DKpFw0pk.js → nativeModuleMocker.DDZfQXLs.js} +1 -1
- package/dist/chunks/{plugin.d.cIKZEZ16.d.ts → plugin.d.B7MTG_Fe.d.ts} +103 -88
- package/dist/chunks/{rpc.d.7JZuxZ8u.d.ts → rpc.d.OQ_EZi1Z.d.ts} +18 -2
- package/dist/chunks/{setup-common.Hpq30zVk.js → setup-common.DdEF_hkE.js} +1 -1
- package/dist/chunks/{vm.2okbRRME.js → vm.Bu7mmcZq.js} +2 -2
- package/dist/chunks/{worker.d.Bu1kXGw4.d.ts → worker.d.yR22cs6X.d.ts} +3 -2
- package/dist/cli.js +2 -2
- package/dist/config.cjs +1 -1
- package/dist/config.d.ts +7 -10
- package/dist/config.js +2 -2
- package/dist/index.d.ts +24 -41
- package/dist/index.js +2 -1
- package/dist/module-evaluator.d.ts +4 -1
- package/dist/module-evaluator.js +15 -20
- package/dist/node.d.ts +20 -13
- package/dist/node.js +6 -6
- package/dist/runtime.js +2 -2
- package/dist/worker.d.ts +3 -3
- package/dist/worker.js +7 -6
- package/dist/workers/forks.js +8 -7
- package/dist/workers/runVmTests.js +4 -3
- package/dist/workers/threads.js +8 -7
- package/dist/workers/vmForks.js +4 -4
- package/dist/workers/vmThreads.js +4 -4
- package/package.json +20 -21
- package/dist/chunks/global.d.DhbKSQoV.d.ts +0 -99
|
@@ -1,6 +1,5 @@
|
|
|
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 * as pathe from 'pathe';
|
|
4
3
|
import { join, dirname, basename, isAbsolute, resolve as resolve$1, relative, normalize, extname } from 'pathe';
|
|
5
4
|
import { r as resolveCoverageProviderModule, C as CoverageProviderMap } from './coverage.CTzCuANN.js';
|
|
6
5
|
import path, { resolve as resolve$2 } from 'node:path';
|
|
@@ -8,13 +7,13 @@ import { unique, createDefer, slash, shuffle, toArray, cleanUrl, withTrailingSla
|
|
|
8
7
|
import { a as any, p as prompt } from './index.og1WyBLx.js';
|
|
9
8
|
import { r as resolveModule, i as isPackageExists, N as NativeModuleRunner } from './nativeModuleRunner.BOeMnHl4.js';
|
|
10
9
|
import * as vite from 'vite';
|
|
11
|
-
import { parseAst, searchForWorkspaceRoot, mergeConfig, fetchModule,
|
|
10
|
+
import { parseAst, searchForWorkspaceRoot, mergeConfig, fetchModule, version, createServer, isFileLoadingAllowed, normalizePath as normalizePath$1, isRunnableDevEnvironment } from 'vite';
|
|
12
11
|
import { c as configFiles, d as defaultBrowserPort, b as defaultInspectPort, a as defaultPort, A as API_PATH } from './constants.-juJ8b_4.js';
|
|
13
12
|
import * as nodeos from 'node:os';
|
|
14
13
|
import nodeos__default, { tmpdir, hostname } from 'node:os';
|
|
15
|
-
import { getTests, createFileTask as createFileTask$1, createTaskName, validateTags, calculateSuiteHash,
|
|
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';
|
|
16
15
|
import { serializeValue } from '@vitest/utils/serialize';
|
|
17
|
-
import { v as version$1 } from './cac.
|
|
16
|
+
import { v as version$1 } from './cac.CyXAEMkE.js';
|
|
18
17
|
import { distDir, rootDir } from '../path.js';
|
|
19
18
|
import { Traces } from '../traces.js';
|
|
20
19
|
import { createDebug } from 'obug';
|
|
@@ -23,8 +22,8 @@ import { rm, readFile, writeFile, rename, stat, unlink, mkdir, readdir, copyFile
|
|
|
23
22
|
import c from 'tinyrainbow';
|
|
24
23
|
import url, { pathToFileURL, fileURLToPath } from 'node:url';
|
|
25
24
|
import { isDynamicPattern, glob } from 'tinyglobby';
|
|
26
|
-
import { c as configDefaults, e as benchmarkConfigDefaults, a as coverageConfigDefaults } from './defaults.
|
|
27
|
-
import {
|
|
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';
|
|
28
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';
|
|
29
28
|
import { isAgent, isCI, provider } from 'std-env';
|
|
30
29
|
import module$1, { createRequire, builtinModules, isBuiltin as isBuiltin$1 } from 'node:module';
|
|
@@ -1222,6 +1221,15 @@ function detectCodeBlock(content) {
|
|
|
1222
1221
|
|
|
1223
1222
|
const debug$1 = createDebugger("vitest:ast-collect-info");
|
|
1224
1223
|
const verbose = createDebugger("vitest:ast-collect-verbose");
|
|
1224
|
+
const INTERMEDIATE_CALL_PROPERTIES = new Set([
|
|
1225
|
+
"each",
|
|
1226
|
+
"for",
|
|
1227
|
+
"skipIf",
|
|
1228
|
+
"runIf",
|
|
1229
|
+
"extend",
|
|
1230
|
+
"scoped",
|
|
1231
|
+
"override"
|
|
1232
|
+
]);
|
|
1225
1233
|
function isTestFunctionName(name) {
|
|
1226
1234
|
return name === "it" || name === "test" || name.startsWith("test") || name.endsWith("Test");
|
|
1227
1235
|
}
|
|
@@ -1274,15 +1282,7 @@ function astParseFile(filepath, code) {
|
|
|
1274
1282
|
const properties = getProperties(callee);
|
|
1275
1283
|
const property = callee?.property?.name;
|
|
1276
1284
|
// intermediate calls like .each(), .for() will be picked up in the next iteration
|
|
1277
|
-
if (property &&
|
|
1278
|
-
"each",
|
|
1279
|
-
"for",
|
|
1280
|
-
"skipIf",
|
|
1281
|
-
"runIf",
|
|
1282
|
-
"extend",
|
|
1283
|
-
"scoped",
|
|
1284
|
-
"override"
|
|
1285
|
-
].includes(property)) return;
|
|
1285
|
+
if (property && INTERMEDIATE_CALL_PROPERTIES.has(property)) return;
|
|
1286
1286
|
// skip properties on return values of calls - e.g., test('name', fn).skip()
|
|
1287
1287
|
if (callee.type === "MemberExpression" && callee.object?.type === "CallExpression") return;
|
|
1288
1288
|
// derive mode from the full chain (handles any order like .skip.concurrent or .concurrent.skip)
|
|
@@ -1349,11 +1349,12 @@ function astParseFile(filepath, code) {
|
|
|
1349
1349
|
definitions
|
|
1350
1350
|
};
|
|
1351
1351
|
}
|
|
1352
|
-
function createFailedFileTask(project, filepath, error) {
|
|
1352
|
+
function createFailedFileTask(project, filepath, error, options) {
|
|
1353
1353
|
const config = project.serializedConfig;
|
|
1354
|
+
const pool = options?.pool ?? config.pool;
|
|
1354
1355
|
const file = {
|
|
1355
|
-
...createFileTask$1(filepath, config.root, config.name,
|
|
1356
|
-
typecheck:
|
|
1356
|
+
...createFileTask$1(filepath, config.root, config.name, pool, void 0, {
|
|
1357
|
+
typecheck: pool === "typescript",
|
|
1357
1358
|
__vitest_label__: config.mergeReportsLabel
|
|
1358
1359
|
}),
|
|
1359
1360
|
mode: "run",
|
|
@@ -1381,12 +1382,13 @@ function serializeError(ctx, error) {
|
|
|
1381
1382
|
message: error.message
|
|
1382
1383
|
}];
|
|
1383
1384
|
}
|
|
1384
|
-
function createFileTask(project, testFilepath, code, requestMap, filepath, fileTags) {
|
|
1385
|
+
function createFileTask(project, testFilepath, code, requestMap, filepath, fileTags, options) {
|
|
1385
1386
|
const { definitions, ast } = astParseFile(testFilepath, code);
|
|
1386
1387
|
const config = project.serializedConfig;
|
|
1388
|
+
const pool = options?.pool ?? config.pool;
|
|
1387
1389
|
const file = {
|
|
1388
|
-
...createFileTask$1(filepath, config.root, config.name,
|
|
1389
|
-
typecheck:
|
|
1390
|
+
...createFileTask$1(filepath, config.root, config.name, pool, void 0, {
|
|
1391
|
+
typecheck: pool === "typescript",
|
|
1390
1392
|
__vitest_label__: config.mergeReportsLabel
|
|
1391
1393
|
}),
|
|
1392
1394
|
mode: "run",
|
|
@@ -1416,10 +1418,10 @@ function createFileTask(project, testFilepath, code, requestMap, filepath, fileT
|
|
|
1416
1418
|
column: processedLocation.column
|
|
1417
1419
|
});
|
|
1418
1420
|
if (originalLocation.column != null) {
|
|
1419
|
-
verbose?.(`Found location for`, definition.type, definition.name, `${processedLocation.line}:${processedLocation.column}`, "->", `${originalLocation.line}:${originalLocation.column}`);
|
|
1421
|
+
verbose?.(`Found location for`, definition.type, definition.name, `${processedLocation.line}:${processedLocation.column + 1}`, "->", `${originalLocation.line}:${originalLocation.column + 1}`);
|
|
1420
1422
|
location = {
|
|
1421
1423
|
line: originalLocation.line,
|
|
1422
|
-
column: originalLocation.column
|
|
1424
|
+
column: originalLocation.column + 1
|
|
1423
1425
|
};
|
|
1424
1426
|
} else debug$1?.("Cannot find original location for", definition.type, definition.name, `${processedLocation.column}:${processedLocation.line}`);
|
|
1425
1427
|
} else debug$1?.("Cannot find original location for", definition.type, definition.name, `${definition.start}`);
|
|
@@ -1471,6 +1473,7 @@ function createFileTask(project, testFilepath, code, requestMap, filepath, fileT
|
|
|
1471
1473
|
timeout: 0,
|
|
1472
1474
|
annotations: [],
|
|
1473
1475
|
artifacts: [],
|
|
1476
|
+
benchmarks: [],
|
|
1474
1477
|
tags: taskTags
|
|
1475
1478
|
};
|
|
1476
1479
|
definition.task = task;
|
|
@@ -1485,16 +1488,35 @@ function createFileTask(project, testFilepath, code, requestMap, filepath, fileT
|
|
|
1485
1488
|
message: `No test suite found in file ${filepath}`
|
|
1486
1489
|
}]
|
|
1487
1490
|
};
|
|
1488
|
-
return
|
|
1491
|
+
return {
|
|
1492
|
+
file,
|
|
1493
|
+
definitions
|
|
1494
|
+
};
|
|
1489
1495
|
}
|
|
1490
1496
|
async function astCollectTests(project, filepath) {
|
|
1497
|
+
return (await astCollectFileInformation(project, filepath)).file;
|
|
1498
|
+
}
|
|
1499
|
+
async function astCollectFileInformation(project, filepath, options) {
|
|
1491
1500
|
const request = await transformSSR(project, filepath);
|
|
1492
1501
|
const testFilepath = relative(project.config.root, filepath);
|
|
1493
1502
|
if (!request) {
|
|
1494
1503
|
debug$1?.("Cannot parse", testFilepath, "(vite didn't return anything)");
|
|
1495
|
-
return
|
|
1504
|
+
return {
|
|
1505
|
+
file: createFailedFileTask(project, filepath, /* @__PURE__ */ new Error(`Failed to parse ${testFilepath}. Vite didn't return anything.`), options),
|
|
1506
|
+
filepath,
|
|
1507
|
+
parsed: "",
|
|
1508
|
+
map: null,
|
|
1509
|
+
definitions: []
|
|
1510
|
+
};
|
|
1496
1511
|
}
|
|
1497
|
-
|
|
1512
|
+
const { file, definitions } = createFileTask(project, testFilepath, request.code, request.map, filepath, request.fileTags, options);
|
|
1513
|
+
return {
|
|
1514
|
+
file,
|
|
1515
|
+
filepath,
|
|
1516
|
+
parsed: request.code,
|
|
1517
|
+
map: request.map,
|
|
1518
|
+
definitions
|
|
1519
|
+
};
|
|
1498
1520
|
}
|
|
1499
1521
|
async function transformSSR(project, filepath) {
|
|
1500
1522
|
const { env: pragmaEnv, tags: fileTags } = detectCodeBlock(await promises.readFile(filepath, "utf-8").catch(() => ""));
|
|
@@ -1544,7 +1566,7 @@ class BrowserSessions {
|
|
|
1544
1566
|
destroySession(sessionId) {
|
|
1545
1567
|
this.sessions.delete(sessionId);
|
|
1546
1568
|
}
|
|
1547
|
-
createSession(sessionId, project, pool) {
|
|
1569
|
+
createSession(sessionId, project, pool, options) {
|
|
1548
1570
|
// this promise only waits for the WS connection with the orchestrator to be established
|
|
1549
1571
|
const defer = createDefer();
|
|
1550
1572
|
const timeout = setTimeout(() => {
|
|
@@ -1552,6 +1574,7 @@ class BrowserSessions {
|
|
|
1552
1574
|
}, project.vitest.config.browser.connectTimeout ?? 6e4).unref();
|
|
1553
1575
|
this.sessions.set(sessionId, {
|
|
1554
1576
|
project,
|
|
1577
|
+
otelCarrier: options?.otelCarrier,
|
|
1555
1578
|
connected: () => {
|
|
1556
1579
|
defer.resolve();
|
|
1557
1580
|
clearTimeout(timeout);
|
|
@@ -2260,8 +2283,10 @@ function resolveInlineWorkerOption(value) {
|
|
|
2260
2283
|
if (typeof value === "string" && value.trim().endsWith("%")) return getWorkersCountByPercentage(value);
|
|
2261
2284
|
else return Number(value);
|
|
2262
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;
|
|
2263
2289
|
function resolveConfig$1(vitest, options, viteConfig) {
|
|
2264
|
-
const mode = vitest.mode;
|
|
2265
2290
|
const logger = vitest.logger;
|
|
2266
2291
|
if (options.dom) {
|
|
2267
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}"`));
|
|
@@ -2270,9 +2295,9 @@ function resolveConfig$1(vitest, options, viteConfig) {
|
|
|
2270
2295
|
const resolved = {
|
|
2271
2296
|
...configDefaults,
|
|
2272
2297
|
...options,
|
|
2273
|
-
root: viteConfig.root
|
|
2274
|
-
mode
|
|
2298
|
+
root: viteConfig.root
|
|
2275
2299
|
};
|
|
2300
|
+
resolved.mode ??= viteConfig.mode ?? "test";
|
|
2276
2301
|
if (resolved.retry && typeof resolved.retry === "object" && typeof resolved.retry.condition === "function") {
|
|
2277
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."));
|
|
2278
2303
|
resolved.retry = {
|
|
@@ -2304,6 +2329,10 @@ function resolveConfig$1(vitest, options, viteConfig) {
|
|
|
2304
2329
|
resolved.name = typeof options.name === "string" ? options.name : options.name?.label || "";
|
|
2305
2330
|
resolved.color = typeof options.name !== "string" ? options.name?.color : void 0;
|
|
2306
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
|
+
};
|
|
2307
2336
|
const inspector = resolved.inspect || resolved.inspectBrk;
|
|
2308
2337
|
resolved.inspector = {
|
|
2309
2338
|
...resolved.inspector,
|
|
@@ -2328,7 +2357,7 @@ function resolveConfig$1(vitest, options, viteConfig) {
|
|
|
2328
2357
|
if (resolved.standalone && !resolved.watch) throw new Error(`Vitest standalone mode requires --watch`);
|
|
2329
2358
|
if (resolved.mergeReports && resolved.watch) throw new Error(`Cannot merge reports with --watch enabled`);
|
|
2330
2359
|
if (resolved.maxWorkers) resolved.maxWorkers = resolveInlineWorkerOption(resolved.maxWorkers);
|
|
2331
|
-
if (!(options.fileParallelism ??
|
|
2360
|
+
if (!(options.fileParallelism ?? true))
|
|
2332
2361
|
// ignore user config, parallelism cannot be implemented without limiting workers
|
|
2333
2362
|
resolved.maxWorkers = 1;
|
|
2334
2363
|
if (resolved.maxConcurrency === 0) {
|
|
@@ -2457,7 +2486,7 @@ function resolveConfig$1(vitest, options, viteConfig) {
|
|
|
2457
2486
|
resolved.coverage.exclude = [
|
|
2458
2487
|
...resolved.coverage.exclude,
|
|
2459
2488
|
...resolved.setupFiles.map((file) => `${resolved.coverage.allowExternal ? "**/" : ""}${relative(resolved.root, file)}`),
|
|
2460
|
-
...resolved.include,
|
|
2489
|
+
...resolved.include.filter((pattern) => !pattern.startsWith("!")),
|
|
2461
2490
|
resolved.config && slash(resolved.config),
|
|
2462
2491
|
...configFiles,
|
|
2463
2492
|
"**/virtual:*",
|
|
@@ -2491,25 +2520,6 @@ function resolveConfig$1(vitest, options, viteConfig) {
|
|
|
2491
2520
|
resolved.pool ??= "threads";
|
|
2492
2521
|
if (resolved.pool === "vmForks" || resolved.pool === "vmThreads" || resolved.pool === "typescript") resolved.isolate = false;
|
|
2493
2522
|
if (process.env.VITEST_MAX_WORKERS) resolved.maxWorkers = Number.parseInt(process.env.VITEST_MAX_WORKERS);
|
|
2494
|
-
if (mode === "benchmark") {
|
|
2495
|
-
resolved.benchmark = {
|
|
2496
|
-
...benchmarkConfigDefaults,
|
|
2497
|
-
...resolved.benchmark
|
|
2498
|
-
};
|
|
2499
|
-
// override test config
|
|
2500
|
-
resolved.coverage.enabled = false;
|
|
2501
|
-
resolved.typecheck.enabled = false;
|
|
2502
|
-
resolved.include = resolved.benchmark.include;
|
|
2503
|
-
resolved.exclude = resolved.benchmark.exclude;
|
|
2504
|
-
resolved.includeSource = resolved.benchmark.includeSource;
|
|
2505
|
-
const reporters = Array.from(new Set([...toArray(resolved.benchmark.reporters), ...toArray(options.reporter)])).filter(Boolean);
|
|
2506
|
-
if (reporters.length) resolved.benchmark.reporters = reporters;
|
|
2507
|
-
else resolved.benchmark.reporters = ["default"];
|
|
2508
|
-
if (options.outputFile) resolved.benchmark.outputFile = options.outputFile;
|
|
2509
|
-
// --compare from cli
|
|
2510
|
-
if (options.compare) resolved.benchmark.compare = options.compare;
|
|
2511
|
-
if (options.outputJson) resolved.benchmark.outputJson = options.outputJson;
|
|
2512
|
-
}
|
|
2513
2523
|
if (typeof resolved.diff === "string") {
|
|
2514
2524
|
resolved.diff = resolvePath(resolved.diff, resolved.root);
|
|
2515
2525
|
resolved.forceRerunTriggers.push(resolved.diff);
|
|
@@ -2545,25 +2555,23 @@ function resolveConfig$1(vitest, options, viteConfig) {
|
|
|
2545
2555
|
// Inline reporter, e.g. { reporter: [{ onFinish() { method() } }] }
|
|
2546
2556
|
resolved.reporters.push(reporter);
|
|
2547
2557
|
}
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
if (typeof reporterName === "string") configReportersMap.set(reporterName, reporterOptions);
|
|
2564
|
-
}
|
|
2565
|
-
resolved.reporters = Array.from(new Set(toArray(cliReporters))).filter(Boolean).map((reporter) => [reporter, configReportersMap.get(reporter) || {}]);
|
|
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);
|
|
2566
2573
|
}
|
|
2574
|
+
resolved.reporters = Array.from(new Set(toArray(cliReporters))).filter(Boolean).map((reporter) => [reporter, configReportersMap.get(reporter) || {}]);
|
|
2567
2575
|
}
|
|
2568
2576
|
resolved.mergeReportsLabel = process.env.VITEST_BLOB_LABEL;
|
|
2569
2577
|
for (const reporter of resolved.reporters) if (Array.isArray(reporter) && reporter[0] === "blob") {
|
|
@@ -2599,12 +2607,15 @@ function resolveConfig$1(vitest, options, viteConfig) {
|
|
|
2599
2607
|
};
|
|
2600
2608
|
resolved.typecheck ??= {};
|
|
2601
2609
|
resolved.typecheck.enabled ??= false;
|
|
2602
|
-
if (resolved.typecheck.enabled
|
|
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
|
+
}
|
|
2603
2614
|
resolved.browser.enabled ??= false;
|
|
2604
2615
|
resolved.browser.headless ??= isCI;
|
|
2605
2616
|
if (resolved.browser.isolate) logger.console.warn(c.yellow("`browser.isolate` is deprecated. Use top-level `isolate` instead."));
|
|
2606
2617
|
resolved.browser.isolate ??= resolved.isolate ?? true;
|
|
2607
|
-
resolved.browser.fileParallelism ??= options.fileParallelism ??
|
|
2618
|
+
resolved.browser.fileParallelism ??= options.fileParallelism ?? true;
|
|
2608
2619
|
// disable in headless mode by default, and if CI is detected
|
|
2609
2620
|
resolved.browser.ui ??= resolved.browser.headless === true ? false : !isCI;
|
|
2610
2621
|
resolved.browser.commands ??= {};
|
|
@@ -2616,7 +2627,8 @@ function resolveConfig$1(vitest, options, viteConfig) {
|
|
|
2616
2627
|
resolved.browser.viewport.height ??= 896;
|
|
2617
2628
|
resolved.browser.locators ??= {};
|
|
2618
2629
|
resolved.browser.locators.testIdAttribute ??= "data-testid";
|
|
2619
|
-
resolved.browser.locators.exact ??=
|
|
2630
|
+
resolved.browser.locators.exact ??= true;
|
|
2631
|
+
resolved.browser.locators.errorFormat ??= "all";
|
|
2620
2632
|
if (typeof resolved.browser.provider === "string") {
|
|
2621
2633
|
const source = `@vitest/browser-${resolved.browser.provider}`;
|
|
2622
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`);
|
|
@@ -3567,8 +3579,8 @@ class ServerModuleRunner extends ModuleRunner {
|
|
|
3567
3579
|
|
|
3568
3580
|
class FilesNotFoundError extends Error {
|
|
3569
3581
|
code = "VITEST_FILES_NOT_FOUND";
|
|
3570
|
-
constructor(
|
|
3571
|
-
super(`No
|
|
3582
|
+
constructor() {
|
|
3583
|
+
super(`No test files found`);
|
|
3572
3584
|
}
|
|
3573
3585
|
}
|
|
3574
3586
|
class GitNotFoundError extends Error {
|
|
@@ -4146,144 +4158,6 @@ function getDefs(c) {
|
|
|
4146
4158
|
};
|
|
4147
4159
|
}
|
|
4148
4160
|
|
|
4149
|
-
async function collectTests(ctx, filepath) {
|
|
4150
|
-
const request = await ctx.vite.environments.ssr.transformRequest(filepath);
|
|
4151
|
-
if (!request) return null;
|
|
4152
|
-
const ast = await parseAstAsync(request.code);
|
|
4153
|
-
const projectName = ctx.name;
|
|
4154
|
-
const file = {
|
|
4155
|
-
...createFileTask$1(filepath, ctx.config.root, projectName, void 0, void 0, { typecheck: true }),
|
|
4156
|
-
start: ast.start,
|
|
4157
|
-
end: ast.end,
|
|
4158
|
-
mode: "run"
|
|
4159
|
-
};
|
|
4160
|
-
file.file = file;
|
|
4161
|
-
const definitions = [];
|
|
4162
|
-
const getName = (callee) => {
|
|
4163
|
-
if (!callee) return null;
|
|
4164
|
-
if (callee.type === "Identifier") return callee.name;
|
|
4165
|
-
if (callee.type === "CallExpression") return getName(callee.callee);
|
|
4166
|
-
if (callee.type === "TaggedTemplateExpression") return getName(callee.tag);
|
|
4167
|
-
if (callee.type === "MemberExpression") {
|
|
4168
|
-
if (callee.object?.type === "Identifier" && [
|
|
4169
|
-
"it",
|
|
4170
|
-
"test",
|
|
4171
|
-
"describe",
|
|
4172
|
-
"suite"
|
|
4173
|
-
].includes(callee.object.name)) return callee.object?.name;
|
|
4174
|
-
// direct call as `__vite_ssr_exports_0__.test()`
|
|
4175
|
-
if (callee.object?.name?.startsWith("__vite_ssr_")) return getName(callee.property);
|
|
4176
|
-
// call as `__vite_ssr__.test.skip()`
|
|
4177
|
-
return getName(callee.object?.property);
|
|
4178
|
-
}
|
|
4179
|
-
// unwrap (0, ...)
|
|
4180
|
-
if (callee.type === "SequenceExpression" && callee.expressions.length === 2) {
|
|
4181
|
-
const [e0, e1] = callee.expressions;
|
|
4182
|
-
if (e0.type === "Literal" && e0.value === 0) return getName(e1);
|
|
4183
|
-
}
|
|
4184
|
-
return null;
|
|
4185
|
-
};
|
|
4186
|
-
ancestor(ast, { CallExpression(node) {
|
|
4187
|
-
const { callee } = node;
|
|
4188
|
-
const name = getName(callee);
|
|
4189
|
-
if (!name) return;
|
|
4190
|
-
if (![
|
|
4191
|
-
"it",
|
|
4192
|
-
"test",
|
|
4193
|
-
"describe",
|
|
4194
|
-
"suite"
|
|
4195
|
-
].includes(name)) return;
|
|
4196
|
-
const property = callee?.property?.name;
|
|
4197
|
-
let mode = !property || property === name ? "run" : property;
|
|
4198
|
-
// they will be picked up in the next iteration
|
|
4199
|
-
if ([
|
|
4200
|
-
"each",
|
|
4201
|
-
"for",
|
|
4202
|
-
"skipIf",
|
|
4203
|
-
"runIf"
|
|
4204
|
-
].includes(mode)) return;
|
|
4205
|
-
let start;
|
|
4206
|
-
const end = node.end;
|
|
4207
|
-
// .each
|
|
4208
|
-
if (callee.type === "CallExpression") start = callee.end;
|
|
4209
|
-
else if (callee.type === "TaggedTemplateExpression") start = callee.end + 1;
|
|
4210
|
-
else start = node.start;
|
|
4211
|
-
const { arguments: [messageNode] } = node;
|
|
4212
|
-
const message = messageNode?.type === "Literal" || messageNode?.type === "TemplateLiteral" ? request.code.slice(messageNode.start + 1, messageNode.end - 1) : request.code.slice(messageNode.start, messageNode.end);
|
|
4213
|
-
// cannot statically analyze, so we always skip it
|
|
4214
|
-
if (mode === "skipIf" || mode === "runIf") mode = "skip";
|
|
4215
|
-
definitions.push({
|
|
4216
|
-
start,
|
|
4217
|
-
end,
|
|
4218
|
-
name: message,
|
|
4219
|
-
type: name === "it" || name === "test" ? "test" : "suite",
|
|
4220
|
-
mode,
|
|
4221
|
-
task: null
|
|
4222
|
-
});
|
|
4223
|
-
} });
|
|
4224
|
-
let lastSuite = file;
|
|
4225
|
-
const updateLatestSuite = (index) => {
|
|
4226
|
-
while (lastSuite.suite && lastSuite.end < index) lastSuite = lastSuite.suite;
|
|
4227
|
-
return lastSuite;
|
|
4228
|
-
};
|
|
4229
|
-
definitions.sort((a, b) => a.start - b.start).forEach((definition) => {
|
|
4230
|
-
const latestSuite = updateLatestSuite(definition.start);
|
|
4231
|
-
let mode = definition.mode;
|
|
4232
|
-
if (latestSuite.mode !== "run")
|
|
4233
|
-
// inherit suite mode, if it's set
|
|
4234
|
-
mode = latestSuite.mode;
|
|
4235
|
-
if (definition.type === "suite") {
|
|
4236
|
-
const task = {
|
|
4237
|
-
type: definition.type,
|
|
4238
|
-
id: "",
|
|
4239
|
-
suite: latestSuite,
|
|
4240
|
-
file,
|
|
4241
|
-
tasks: [],
|
|
4242
|
-
mode,
|
|
4243
|
-
name: definition.name,
|
|
4244
|
-
fullName: createTaskName([lastSuite.fullName, definition.name]),
|
|
4245
|
-
fullTestName: createTaskName([lastSuite.fullTestName, definition.name]),
|
|
4246
|
-
end: definition.end,
|
|
4247
|
-
start: definition.start,
|
|
4248
|
-
meta: { typecheck: true }
|
|
4249
|
-
};
|
|
4250
|
-
definition.task = task;
|
|
4251
|
-
latestSuite.tasks.push(task);
|
|
4252
|
-
lastSuite = task;
|
|
4253
|
-
return;
|
|
4254
|
-
}
|
|
4255
|
-
const task = {
|
|
4256
|
-
type: definition.type,
|
|
4257
|
-
id: "",
|
|
4258
|
-
suite: latestSuite,
|
|
4259
|
-
file,
|
|
4260
|
-
mode,
|
|
4261
|
-
timeout: 0,
|
|
4262
|
-
context: {},
|
|
4263
|
-
name: definition.name,
|
|
4264
|
-
fullName: createTaskName([lastSuite.fullName, definition.name]),
|
|
4265
|
-
fullTestName: createTaskName([lastSuite.fullTestName, definition.name]),
|
|
4266
|
-
end: definition.end,
|
|
4267
|
-
start: definition.start,
|
|
4268
|
-
annotations: [],
|
|
4269
|
-
artifacts: [],
|
|
4270
|
-
meta: { typecheck: true }
|
|
4271
|
-
};
|
|
4272
|
-
definition.task = task;
|
|
4273
|
-
latestSuite.tasks.push(task);
|
|
4274
|
-
});
|
|
4275
|
-
calculateSuiteHash(file);
|
|
4276
|
-
const hasOnly = someTasksAreOnly(file);
|
|
4277
|
-
interpretTaskModes(file, ctx.config.testNamePattern, void 0, void 0, void 0, hasOnly, false, ctx.config.allowOnly);
|
|
4278
|
-
return {
|
|
4279
|
-
file,
|
|
4280
|
-
parsed: request.code,
|
|
4281
|
-
filepath,
|
|
4282
|
-
map: request.map,
|
|
4283
|
-
definitions
|
|
4284
|
-
};
|
|
4285
|
-
}
|
|
4286
|
-
|
|
4287
4161
|
const newLineRegExp = /\r?\n/;
|
|
4288
4162
|
const errCodeRegExp = /error TS(?<errCode>\d+)/;
|
|
4289
4163
|
async function makeTscErrorInfo(errInfo) {
|
|
@@ -4364,7 +4238,7 @@ class Typechecker {
|
|
|
4364
4238
|
this._onWatcherRerun = fn;
|
|
4365
4239
|
}
|
|
4366
4240
|
async collectFileTests(filepath) {
|
|
4367
|
-
return
|
|
4241
|
+
return astCollectFileInformation(this.project, filepath, { pool: "typescript" });
|
|
4368
4242
|
}
|
|
4369
4243
|
getFiles() {
|
|
4370
4244
|
return this.files;
|
|
@@ -4504,18 +4378,16 @@ Possible solutions:
|
|
|
4504
4378
|
}
|
|
4505
4379
|
async spawn() {
|
|
4506
4380
|
const { root, watch, typecheck } = this.project.config;
|
|
4507
|
-
const args = [
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
"false",
|
|
4511
|
-
"--incremental",
|
|
4512
|
-
"--tsBuildInfoFile",
|
|
4513
|
-
join(process.versions.pnp ? join(nodeos__default.tmpdir(), this.project.hash) : distDir, "tsconfig.tmp.tsbuildinfo")
|
|
4514
|
-
];
|
|
4381
|
+
const args = ["--pretty", "false"];
|
|
4382
|
+
if (typecheck.build) args.unshift("--build");
|
|
4383
|
+
else args.push("--noEmit", "--incremental", "--tsBuildInfoFile", join(process.versions.pnp ? join(nodeos__default.tmpdir(), this.project.hash) : distDir, "tsconfig.tmp.tsbuildinfo"));
|
|
4515
4384
|
// use builtin watcher because it's faster
|
|
4516
4385
|
if (watch) args.push("--watch");
|
|
4517
4386
|
if (typecheck.allowJs) args.push("--allowJs", "--checkJs");
|
|
4518
|
-
if (typecheck.tsconfig)
|
|
4387
|
+
if (typecheck.tsconfig) {
|
|
4388
|
+
if (!typecheck.build) args.push("-p");
|
|
4389
|
+
args.push(resolve$1(root, typecheck.tsconfig));
|
|
4390
|
+
}
|
|
4519
4391
|
this._output = "";
|
|
4520
4392
|
this._startTime = performance$1.now();
|
|
4521
4393
|
const child = x(typecheck.checker, args, {
|
|
@@ -4755,9 +4627,9 @@ function printErrorInner(error, project, options) {
|
|
|
4755
4627
|
const testName = e.VITEST_TEST_NAME;
|
|
4756
4628
|
// testName has testPath inside
|
|
4757
4629
|
if (testPath) logger.error(c.red(`This error originated in "${c.bold(relative(project.config.root, testPath))}" test file. It doesn't mean the error was thrown inside the file itself, but while it was running.`));
|
|
4758
|
-
if (testName) logger.error(c.red(`The
|
|
4759
|
-
-
|
|
4760
|
-
-
|
|
4630
|
+
if (testName) logger.error(c.red(`The last test to run before this error was "${c.bold(testName)}". This means either:
|
|
4631
|
+
- the error was thrown while Vitest was running this test, or
|
|
4632
|
+
- the error was thrown after the test completed, and this was the most recent test at that point.`));
|
|
4761
4633
|
if (typeof e.cause === "object" && e.cause && "name" in e.cause) {
|
|
4762
4634
|
e.cause.name = `Caused by: ${e.cause.name}`;
|
|
4763
4635
|
printErrorInner(e.cause, project, {
|
|
@@ -5011,10 +4883,10 @@ class Logger {
|
|
|
5011
4883
|
}
|
|
5012
4884
|
printNoTestFound(filters) {
|
|
5013
4885
|
const config = this.ctx.config;
|
|
5014
|
-
if (config.watch && (config.changed || config.related?.length)) this.log(`No affected
|
|
5015
|
-
else if (config.watch) this.log(c.red(`No
|
|
5016
|
-
else if (config.passWithNoTests) this.log(`No
|
|
5017
|
-
else this.error(c.red(`No
|
|
4886
|
+
if (config.watch && (config.changed || config.related?.length)) this.log(`No affected test files found\n`);
|
|
4887
|
+
else if (config.watch) this.log(c.red(`No test files found. You can change the file name pattern by pressing "p"\n`));
|
|
4888
|
+
else if (config.passWithNoTests) this.log(`No test files found, exiting with code 0\n`);
|
|
4889
|
+
else this.error(c.red(`No test files found, exiting with code 1\n`));
|
|
5018
4890
|
const comma = c.dim(", ");
|
|
5019
4891
|
if (filters?.length) this.console.error(c.dim("filter: ") + c.yellow(filters.join(comma)));
|
|
5020
4892
|
const projectsFilter = toArray(config.project);
|
|
@@ -5775,6 +5647,15 @@ function createMethodsRPC(project, methodsOptions = {}) {
|
|
|
5775
5647
|
onAfterSuiteRun(meta) {
|
|
5776
5648
|
vitest.coverageProvider?.onAfterSuiteRun(meta);
|
|
5777
5649
|
},
|
|
5650
|
+
async onTestBenchmark(testId, benchmark) {
|
|
5651
|
+
return vitest._testRun.recordBenchmark(testId, benchmark);
|
|
5652
|
+
},
|
|
5653
|
+
async readBenchmarkResult(relativePath) {
|
|
5654
|
+
return project.benchmark.readResult(relativePath);
|
|
5655
|
+
},
|
|
5656
|
+
async writeBenchmarkResult(relativePath, data) {
|
|
5657
|
+
return project.benchmark.writeResult(relativePath, data);
|
|
5658
|
+
},
|
|
5778
5659
|
async onTaskArtifactRecord(testId, artifact) {
|
|
5779
5660
|
return vitest._testRun.recordArtifact(testId, artifact);
|
|
5780
5661
|
},
|
|
@@ -6067,7 +5948,7 @@ class PoolRunner {
|
|
|
6067
5948
|
}
|
|
6068
5949
|
};
|
|
6069
5950
|
emitUnexpectedExit = () => {
|
|
6070
|
-
const error = /* @__PURE__ */ new Error(
|
|
5951
|
+
const error = /* @__PURE__ */ new Error(`Worker exited unexpectedly during ${this._state} state`);
|
|
6071
5952
|
this._eventEmitter.emit("error", error);
|
|
6072
5953
|
};
|
|
6073
5954
|
waitForStart() {
|
|
@@ -6638,7 +6519,7 @@ function deepEqual$1(obj1, obj2) {
|
|
|
6638
6519
|
const keys1 = Object.keys(obj1);
|
|
6639
6520
|
const keys2 = Object.keys(obj2);
|
|
6640
6521
|
if (keys1.length !== keys2.length) return false;
|
|
6641
|
-
for (const key of keys1) if (!
|
|
6522
|
+
for (const key of keys1) if (!Object.prototype.hasOwnProperty.call(obj2, key) || !deepEqual$1(obj1[key], obj2[key])) return false;
|
|
6642
6523
|
return true;
|
|
6643
6524
|
}
|
|
6644
6525
|
|
|
@@ -11902,11 +11783,12 @@ function getTestFileEnvironment(project, testFile, browser = false) {
|
|
|
11902
11783
|
}
|
|
11903
11784
|
}
|
|
11904
11785
|
|
|
11905
|
-
async function getModuleGraph(ctx, projectName, testFilePath
|
|
11786
|
+
async function getModuleGraph(ctx, projectName, testFilePath) {
|
|
11906
11787
|
const graph = {};
|
|
11907
11788
|
const externalized = /* @__PURE__ */ new Set();
|
|
11908
11789
|
const inlined = /* @__PURE__ */ new Set();
|
|
11909
11790
|
const project = ctx.getProjectByName(projectName);
|
|
11791
|
+
const browser = project.config.browser.enabled;
|
|
11910
11792
|
const environment = project.config.experimental.viteModuleRunner === false ? project.vite.environments.__vitest__ : getTestFileEnvironment(project, testFilePath, browser);
|
|
11911
11793
|
if (!environment) throw new Error(`Cannot find environment for ${testFilePath}`);
|
|
11912
11794
|
const seen = /* @__PURE__ */ new Map();
|
|
@@ -12048,10 +11930,11 @@ function setup(ctx, _server) {
|
|
|
12048
11930
|
} catch {}
|
|
12049
11931
|
return result;
|
|
12050
11932
|
},
|
|
12051
|
-
async getTransformResult(projectName, moduleId, testFileTaskId
|
|
11933
|
+
async getTransformResult(projectName, moduleId, testFileTaskId) {
|
|
12052
11934
|
const project = ctx.getProjectByName(projectName);
|
|
12053
11935
|
const testModule = ctx.state.getReportedEntityById(testFileTaskId);
|
|
12054
11936
|
if (!testModule || !isFileServingAllowed(project.vite.config, moduleId)) return;
|
|
11937
|
+
const browser = !!project.config.browser.enabled;
|
|
12055
11938
|
const environment = getTestFileEnvironment(project, testModule.moduleId, browser);
|
|
12056
11939
|
const moduleNode = environment?.moduleGraph.getModuleById(moduleId);
|
|
12057
11940
|
if (!environment || !moduleNode?.transformResult) return;
|
|
@@ -12069,8 +11952,8 @@ function setup(ctx, _server) {
|
|
|
12069
11952
|
} catch {}
|
|
12070
11953
|
return result;
|
|
12071
11954
|
},
|
|
12072
|
-
async getModuleGraph(project, id
|
|
12073
|
-
return getModuleGraph(ctx, project, id
|
|
11955
|
+
async getModuleGraph(project, id) {
|
|
11956
|
+
return getModuleGraph(ctx, project, id);
|
|
12074
11957
|
},
|
|
12075
11958
|
async updateSnapshot(file) {
|
|
12076
11959
|
// silently ignore exec/write attempts if not allowed
|
|
@@ -12242,13 +12125,39 @@ function handleDefineValue(value) {
|
|
|
12242
12125
|
return JSON.stringify(value);
|
|
12243
12126
|
}
|
|
12244
12127
|
|
|
12128
|
+
class BenchmarkManager {
|
|
12129
|
+
constructor(project) {
|
|
12130
|
+
this.project = project;
|
|
12131
|
+
}
|
|
12132
|
+
// Resolve a user-supplied path against the project root. Reject paths that
|
|
12133
|
+
// escape the project root: `bench.from()` accepts arbitrary input, and we
|
|
12134
|
+
// never want a benchmark file to be able to read or clobber files outside
|
|
12135
|
+
// the workspace.
|
|
12136
|
+
resolve(relativePath) {
|
|
12137
|
+
const root = this.project.config.root;
|
|
12138
|
+
const absolute = isAbsolute(relativePath) ? resolve$1(relativePath) : resolve$1(root, relativePath);
|
|
12139
|
+
const rootWithSep = root.endsWith("/") ? root : `${root}/`;
|
|
12140
|
+
if (absolute !== root && !absolute.startsWith(rootWithSep)) throw new Error(`Benchmark artifact path "${relativePath}" resolves outside the project root (${root}). Paths passed to \`writeResult\` and \`bench.from()\` must point inside the project.`);
|
|
12141
|
+
return absolute;
|
|
12142
|
+
}
|
|
12143
|
+
async readResult(relativePath) {
|
|
12144
|
+
const path = this.resolve(relativePath);
|
|
12145
|
+
if (!existsSync(path)) return null;
|
|
12146
|
+
return JSON.parse(await readFile(path, "utf-8"));
|
|
12147
|
+
}
|
|
12148
|
+
async writeResult(relativePath, data) {
|
|
12149
|
+
const absolute = this.resolve(relativePath);
|
|
12150
|
+
await mkdir(dirname(absolute), { recursive: true });
|
|
12151
|
+
await writeFile(absolute, `${JSON.stringify(data, null, 2)}\n`, "utf-8");
|
|
12152
|
+
}
|
|
12153
|
+
}
|
|
12154
|
+
|
|
12245
12155
|
function serializeConfig(project) {
|
|
12246
12156
|
const { config, globalConfig } = project;
|
|
12247
12157
|
const viteConfig = project._vite?.config;
|
|
12248
12158
|
const optimizer = config.deps?.optimizer || {};
|
|
12249
12159
|
return {
|
|
12250
12160
|
environmentOptions: config.environmentOptions,
|
|
12251
|
-
mode: config.mode,
|
|
12252
12161
|
isolate: config.isolate,
|
|
12253
12162
|
maxWorkers: config.maxWorkers,
|
|
12254
12163
|
base: config.base,
|
|
@@ -12343,7 +12252,8 @@ function serializeConfig(project) {
|
|
|
12343
12252
|
screenshotFailures: browser.screenshotFailures,
|
|
12344
12253
|
locators: {
|
|
12345
12254
|
testIdAttribute: browser.locators.testIdAttribute,
|
|
12346
|
-
exact: browser.locators.exact
|
|
12255
|
+
exact: browser.locators.exact,
|
|
12256
|
+
errorFormat: browser.locators.errorFormat
|
|
12347
12257
|
},
|
|
12348
12258
|
providerOptions: provider?.name === "playwright" ? { actionTimeout: provider?.options?.actionTimeout } : {},
|
|
12349
12259
|
trackUnhandledErrors: browser.trackUnhandledErrors ?? true,
|
|
@@ -12353,7 +12263,12 @@ function serializeConfig(project) {
|
|
|
12353
12263
|
})(config.browser),
|
|
12354
12264
|
standalone: config.standalone,
|
|
12355
12265
|
printConsoleTrace: config.printConsoleTrace ?? globalConfig.printConsoleTrace,
|
|
12356
|
-
benchmark:
|
|
12266
|
+
benchmark: {
|
|
12267
|
+
enabled: config.benchmark.enabled,
|
|
12268
|
+
retainSamples: config.benchmark.retainSamples,
|
|
12269
|
+
suppressExportGetterWarnings: config.benchmark.suppressExportGetterWarnings,
|
|
12270
|
+
projectName: config.benchmark.projectName
|
|
12271
|
+
},
|
|
12357
12272
|
serializedDefines: config.browser.enabled ? "" : project._serializedDefines || "",
|
|
12358
12273
|
experimental: {
|
|
12359
12274
|
fsModuleCache: config.experimental.fsModuleCache ?? false,
|
|
@@ -12367,7 +12282,7 @@ function serializeConfig(project) {
|
|
|
12367
12282
|
strictTags: config.strictTags ?? true,
|
|
12368
12283
|
mergeReportsLabel: config.mergeReportsLabel,
|
|
12369
12284
|
slowTestThreshold: config.slowTestThreshold ?? globalConfig.slowTestThreshold ?? configDefaults.slowTestThreshold,
|
|
12370
|
-
isAgent
|
|
12285
|
+
disableColors: isAgent && !isForceColor()
|
|
12371
12286
|
};
|
|
12372
12287
|
}
|
|
12373
12288
|
|
|
@@ -13364,7 +13279,8 @@ function WorkspaceVitestPlugin(project, options) {
|
|
|
13364
13279
|
name: "vitest:project:name",
|
|
13365
13280
|
enforce: "post",
|
|
13366
13281
|
config(viteConfig) {
|
|
13367
|
-
|
|
13282
|
+
viteConfig.test ??= {};
|
|
13283
|
+
const testConfig = viteConfig.test;
|
|
13368
13284
|
let { label: name, color } = typeof testConfig.name === "string" ? { label: testConfig.name } : {
|
|
13369
13285
|
label: "",
|
|
13370
13286
|
...testConfig.name
|
|
@@ -13376,6 +13292,10 @@ function WorkspaceVitestPlugin(project, options) {
|
|
|
13376
13292
|
if (existsSync(pkgJsonPath)) name = JSON.parse(readFileSync(pkgJsonPath, "utf-8")).name;
|
|
13377
13293
|
if (typeof name !== "string" || !name) name = basename(dir);
|
|
13378
13294
|
} else name = options.workspacePath.toString();
|
|
13295
|
+
if (project.vitest._cliOptions.benchmarkOnly) {
|
|
13296
|
+
viteConfig.test.benchmark ??= {};
|
|
13297
|
+
viteConfig.test.benchmark.enabled = true;
|
|
13298
|
+
}
|
|
13379
13299
|
const isBrowserEnabled = viteConfig.test?.browser?.enabled ?? (viteConfig.test?.browser && project.vitest._cliOptions.browser?.enabled);
|
|
13380
13300
|
// keep project names to potentially filter it out
|
|
13381
13301
|
const workspaceNames = [name];
|
|
@@ -13388,6 +13308,7 @@ function WorkspaceVitestPlugin(project, options) {
|
|
|
13388
13308
|
instance.name ??= name ? `${name} (${instance.browser})` : instance.browser;
|
|
13389
13309
|
if (isBrowserEnabled) workspaceNames.push(instance.name);
|
|
13390
13310
|
});
|
|
13311
|
+
if (viteConfig.test?.benchmark?.enabled) workspaceNames.push(name ? `${name} (bench)` : "bench");
|
|
13391
13312
|
// if there is `--project=...` filter, check if any of the potential projects match
|
|
13392
13313
|
// if projects don't match, we ignore the test project altogether
|
|
13393
13314
|
// if some of them match, they will later be filtered again by `resolveWorkspace`
|
|
@@ -13660,9 +13581,9 @@ class TestSpecification {
|
|
|
13660
13581
|
* This class represents a test suite for a test module within a single project.
|
|
13661
13582
|
* @internal
|
|
13662
13583
|
*/
|
|
13663
|
-
constructor(project, moduleId, pool, testLinesOrOptions,
|
|
13584
|
+
constructor(project, moduleId, pool, testLinesOrOptions, taskIdOverride) {
|
|
13664
13585
|
const projectName = project.config.name;
|
|
13665
|
-
this.taskId = generateFileHash(relative(project.config.root, moduleId), projectName,
|
|
13586
|
+
this.taskId = taskIdOverride ?? generateFileHash(relative(project.config.root, moduleId), projectName, {
|
|
13666
13587
|
typecheck: pool === "typescript",
|
|
13667
13588
|
__vitest_label__: project.config.mergeReportsLabel
|
|
13668
13589
|
});
|
|
@@ -13720,6 +13641,7 @@ class TestProject {
|
|
|
13720
13641
|
* Temporary directory for the project. This is unique for each project. Vitest stores transformed content here.
|
|
13721
13642
|
*/
|
|
13722
13643
|
tmpDir;
|
|
13644
|
+
benchmark = new BenchmarkManager(this);
|
|
13723
13645
|
/** @internal */ typechecker;
|
|
13724
13646
|
/** @internal */ _config;
|
|
13725
13647
|
/** @internal */ _vite;
|
|
@@ -13779,8 +13701,8 @@ class TestProject {
|
|
|
13779
13701
|
* Creates a new test specification. Specifications describe how to run tests.
|
|
13780
13702
|
* @param moduleId The file path
|
|
13781
13703
|
*/
|
|
13782
|
-
createSpecification(moduleId, locationsOrOptions, pool,
|
|
13783
|
-
return new TestSpecification(this, moduleId, pool || getFilePoolName(this), locationsOrOptions,
|
|
13704
|
+
createSpecification(moduleId, locationsOrOptions, pool, taskIdOverride) {
|
|
13705
|
+
return new TestSpecification(this, moduleId, pool || getFilePoolName(this), locationsOrOptions, taskIdOverride);
|
|
13784
13706
|
}
|
|
13785
13707
|
toJSON() {
|
|
13786
13708
|
return {
|
|
@@ -14029,7 +13951,8 @@ class TestProject {
|
|
|
14029
13951
|
async _configureServer(options, server) {
|
|
14030
13952
|
this._config = resolveConfig$1(this.vitest, {
|
|
14031
13953
|
...options,
|
|
14032
|
-
coverage: this.vitest.config.coverage
|
|
13954
|
+
coverage: this.vitest.config.coverage,
|
|
13955
|
+
attachmentsDir: this.vitest.config.attachmentsDir
|
|
14033
13956
|
}, server.config);
|
|
14034
13957
|
this._config.api.token = this.vitest.config.api.token;
|
|
14035
13958
|
this._config.mergeReportsLabel = this.vitest.config.mergeReportsLabel;
|
|
@@ -14069,9 +13992,8 @@ class TestProject {
|
|
|
14069
13992
|
const url = new URL("/__vitest_test__/", origin);
|
|
14070
13993
|
url.searchParams.set("sessionId", sessionId);
|
|
14071
13994
|
const otelCarrier = this.vitest._traces.getContextCarrier();
|
|
14072
|
-
if (otelCarrier) url.searchParams.set("otelCarrier", JSON.stringify(otelCarrier));
|
|
14073
13995
|
this.vitest._browserSessions.sessionIds.add(sessionId);
|
|
14074
|
-
const sessionPromise = this.vitest._browserSessions.createSession(sessionId, this, pool);
|
|
13996
|
+
const sessionPromise = this.vitest._browserSessions.createSession(sessionId, this, pool, { otelCarrier });
|
|
14075
13997
|
const pagePromise = this.browser.provider.openPage(sessionId, url.toString(), { parallel: pool.parallel ?? false });
|
|
14076
13998
|
await Promise.all([sessionPromise, pagePromise]);
|
|
14077
13999
|
}
|
|
@@ -14125,7 +14047,7 @@ class TestProject {
|
|
|
14125
14047
|
return project;
|
|
14126
14048
|
}
|
|
14127
14049
|
/** @internal */
|
|
14128
|
-
static
|
|
14050
|
+
static _cloneTestProject(parent, config) {
|
|
14129
14051
|
const clone = new TestProject(parent.vitest, void 0, parent.tmpDir);
|
|
14130
14052
|
clone.runner = parent.runner;
|
|
14131
14053
|
clone._vite = parent._vite;
|
|
@@ -14287,7 +14209,58 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
|
|
|
14287
14209
|
}
|
|
14288
14210
|
names.add(name);
|
|
14289
14211
|
}
|
|
14290
|
-
return
|
|
14212
|
+
return resolveDefaultProjects(vitest, names, resolvedProjects);
|
|
14213
|
+
}
|
|
14214
|
+
async function resolveDefaultProjects(vitest, names, resolvedProjects) {
|
|
14215
|
+
const newProjects = await resolveBrowserProjects(vitest, names, resolvedProjects);
|
|
14216
|
+
let lastGroupOrder = Math.max(0, ...newProjects.map((p) => p.config.sequence.groupOrder));
|
|
14217
|
+
newProjects.forEach((project) => {
|
|
14218
|
+
const benchmark = project.config.benchmark;
|
|
14219
|
+
if (!benchmark.enabled) return;
|
|
14220
|
+
if (vitest.isExcludedByProjectFilter(project.config.name)) {
|
|
14221
|
+
benchmark.enabled = false;
|
|
14222
|
+
return;
|
|
14223
|
+
}
|
|
14224
|
+
const name = project.config.name ? `${project.config.name} (bench)` : "bench";
|
|
14225
|
+
if (!vitest.matchesProjectFilter(name)) {
|
|
14226
|
+
benchmark.enabled = false;
|
|
14227
|
+
return;
|
|
14228
|
+
}
|
|
14229
|
+
if (names.has(name)) throw new Error(`Cannot create a benchmark project because the name "${name}" is already in use.`);
|
|
14230
|
+
names.add(name);
|
|
14231
|
+
const benchmarkProject = TestProject._cloneTestProject(project, {
|
|
14232
|
+
...project.config,
|
|
14233
|
+
name,
|
|
14234
|
+
include: benchmark.include,
|
|
14235
|
+
exclude: benchmark.exclude,
|
|
14236
|
+
includeSource: benchmark.includeSource,
|
|
14237
|
+
coverage: {
|
|
14238
|
+
...project.config.coverage,
|
|
14239
|
+
enabled: false
|
|
14240
|
+
},
|
|
14241
|
+
maxWorkers: 1,
|
|
14242
|
+
maxConcurrency: 1,
|
|
14243
|
+
testTimeout: project.config.testTimeout < 6e4 ? 6e4 : project.config.testTimeout,
|
|
14244
|
+
hookTimeout: project.config.hookTimeout < 12e4 ? 12e4 : project.config.hookTimeout,
|
|
14245
|
+
benchmark: {
|
|
14246
|
+
...benchmark,
|
|
14247
|
+
projectName: project.config.name ?? ""
|
|
14248
|
+
},
|
|
14249
|
+
sequence: {
|
|
14250
|
+
...project.config.sequence,
|
|
14251
|
+
concurrent: false,
|
|
14252
|
+
groupOrder: ++lastGroupOrder
|
|
14253
|
+
},
|
|
14254
|
+
typecheck: {
|
|
14255
|
+
...project.config.typecheck,
|
|
14256
|
+
enabled: false
|
|
14257
|
+
}
|
|
14258
|
+
});
|
|
14259
|
+
// disable benchmark in the original project
|
|
14260
|
+
benchmark.enabled = false;
|
|
14261
|
+
newProjects.push(benchmarkProject);
|
|
14262
|
+
});
|
|
14263
|
+
return newProjects;
|
|
14291
14264
|
}
|
|
14292
14265
|
async function resolveBrowserProjects(vitest, names, resolvedProjects) {
|
|
14293
14266
|
const removeProjects = /* @__PURE__ */ new Set();
|
|
@@ -14328,7 +14301,7 @@ async function resolveBrowserProjects(vitest, names, resolvedProjects) {
|
|
|
14328
14301
|
names.add(name);
|
|
14329
14302
|
const clonedConfig = cloneConfig(project, config);
|
|
14330
14303
|
clonedConfig.name = name;
|
|
14331
|
-
const clone = TestProject.
|
|
14304
|
+
const clone = TestProject._cloneTestProject(project, clonedConfig);
|
|
14332
14305
|
resolvedProjects.push(clone);
|
|
14333
14306
|
});
|
|
14334
14307
|
removeProjects.add(project);
|
|
@@ -14345,7 +14318,8 @@ function cloneConfig(project, { browser, ...config }) {
|
|
|
14345
14318
|
...project.config.browser,
|
|
14346
14319
|
locators: locators ? {
|
|
14347
14320
|
testIdAttribute: locators.testIdAttribute ?? currentConfig.locators.testIdAttribute,
|
|
14348
|
-
exact: locators.exact ?? currentConfig.locators.exact
|
|
14321
|
+
exact: locators.exact ?? currentConfig.locators.exact,
|
|
14322
|
+
errorFormat: locators.errorFormat ?? currentConfig.locators.errorFormat
|
|
14349
14323
|
} : project.config.browser.locators,
|
|
14350
14324
|
viewport: viewport ?? currentConfig.viewport,
|
|
14351
14325
|
testerHtmlPath: testerHtmlPath ?? currentConfig.testerHtmlPath,
|
|
@@ -14452,8 +14426,10 @@ function getDefaultTestProject(vitest) {
|
|
|
14452
14426
|
}
|
|
14453
14427
|
function getPotentialProjectNames(project) {
|
|
14454
14428
|
const names = [project.name];
|
|
14429
|
+
// TODO: include benchmarks in browsers
|
|
14455
14430
|
if (project.config.browser.instances) names.push(...project.config.browser.instances.map((i) => i.name));
|
|
14456
14431
|
else if (project.config.browser.name) names.push(project.config.browser.name);
|
|
14432
|
+
if (project.config.benchmark.enabled) names.push(project.name ? `${project.name} (bench)` : "bench");
|
|
14457
14433
|
return names;
|
|
14458
14434
|
}
|
|
14459
14435
|
|
|
@@ -14512,8 +14488,8 @@ class BlobReporter {
|
|
|
14512
14488
|
outputFile = [
|
|
14513
14489
|
"blob",
|
|
14514
14490
|
this.ctx.config.mergeReportsLabel,
|
|
14515
|
-
shard ?
|
|
14516
|
-
].join("");
|
|
14491
|
+
shard ? `${shard.index}-${shard.count}` : ""
|
|
14492
|
+
].filter(Boolean).join("-");
|
|
14517
14493
|
outputFile = `${sanitizeFilePath(outputFile)}.json`;
|
|
14518
14494
|
await report.writeFile(outputFile, content, "utf-8");
|
|
14519
14495
|
outputFile = resolve$1(report.root, outputFile);
|
|
@@ -14587,9 +14563,11 @@ function serializeEnvironmentModuleGraph(environment) {
|
|
|
14587
14563
|
};
|
|
14588
14564
|
const modules = [];
|
|
14589
14565
|
for (const [id, mod] of environment.moduleGraph.idToModuleMap.entries()) {
|
|
14590
|
-
|
|
14566
|
+
// Vite can generate module with `file = ""` for module id "#..."
|
|
14567
|
+
// when the actual module doesn't exist (e.g. resolve failure or mocked module)
|
|
14568
|
+
if (mod.file == null) continue;
|
|
14591
14569
|
const importedIds = [];
|
|
14592
|
-
for (const importedNode of mod.importedModules) if (importedNode.id) importedIds.push(getIdIndex(importedNode.id));
|
|
14570
|
+
for (const importedNode of mod.importedModules) if (importedNode.id !== null) importedIds.push(getIdIndex(importedNode.id));
|
|
14593
14571
|
modules.push([
|
|
14594
14572
|
getIdIndex(id),
|
|
14595
14573
|
getIdIndex(mod.file),
|
|
@@ -14608,6 +14586,10 @@ function deserializeEnvironmentModuleGraph(environment, serialized) {
|
|
|
14608
14586
|
const moduleId = serialized.idTable[id];
|
|
14609
14587
|
const filePath = serialized.idTable[file];
|
|
14610
14588
|
const urlPath = serialized.idTable[url];
|
|
14589
|
+
// `createFileOnlyEntry('')` normalizes the file to ".". This keeps
|
|
14590
|
+
// the graph usable, but doesn't perfectly round-trip Vite's `file = ""`
|
|
14591
|
+
// nodes for ids like "#...".
|
|
14592
|
+
// We may just do moduleNode.file = filePath in the future.
|
|
14611
14593
|
const moduleNode = environment.moduleGraph.createFileOnlyEntry(filePath);
|
|
14612
14594
|
moduleNode.url = urlPath;
|
|
14613
14595
|
moduleNode.id = moduleId;
|
|
@@ -14679,6 +14661,66 @@ function createReport(ctx, scope) {
|
|
|
14679
14661
|
};
|
|
14680
14662
|
}
|
|
14681
14663
|
|
|
14664
|
+
const BENCH_TABLE_HEAD = [
|
|
14665
|
+
"hz",
|
|
14666
|
+
"min",
|
|
14667
|
+
"max",
|
|
14668
|
+
"mean",
|
|
14669
|
+
"p75",
|
|
14670
|
+
"p99",
|
|
14671
|
+
"p995",
|
|
14672
|
+
"p999",
|
|
14673
|
+
"rme",
|
|
14674
|
+
"samples"
|
|
14675
|
+
];
|
|
14676
|
+
function formatBenchNumber(number) {
|
|
14677
|
+
const res = String(number.toFixed(number < 100 ? 4 : 2)).split(".");
|
|
14678
|
+
return res[0].replace(/(?=(?:\d{3})+$)\B/g, ",") + (res[1] ? `.${res[1]}` : "");
|
|
14679
|
+
}
|
|
14680
|
+
// Plain-text rendering of the benchmark table (no ANSI colors, no indent).
|
|
14681
|
+
// Used by the junit reporter to embed benchmark data in <system-out>.
|
|
14682
|
+
function renderBenchmarkTableText(benchmarks, columnName = "name") {
|
|
14683
|
+
const lines = [];
|
|
14684
|
+
for (const benchmark of benchmarks) {
|
|
14685
|
+
const { tasks } = benchmark;
|
|
14686
|
+
if (tasks.length === 0) continue;
|
|
14687
|
+
if (lines.length > 0) lines.push("");
|
|
14688
|
+
const rows = tasks.map(renderBenchmarkRow);
|
|
14689
|
+
const head = [columnName, ...BENCH_TABLE_HEAD];
|
|
14690
|
+
const widths = computeBenchColumnWidths(head, rows);
|
|
14691
|
+
lines.push(padBenchRow(head, widths).join(" "));
|
|
14692
|
+
for (const task of tasks) {
|
|
14693
|
+
let row = padBenchRow(renderBenchmarkRow(task), widths).join(" ");
|
|
14694
|
+
if (task.rank === 1 && tasks.length > 1) row += " fastest";
|
|
14695
|
+
if (task.rank === tasks.length && tasks.length > 2) row += " slowest";
|
|
14696
|
+
lines.push(row);
|
|
14697
|
+
}
|
|
14698
|
+
}
|
|
14699
|
+
return lines.join("\n");
|
|
14700
|
+
}
|
|
14701
|
+
function renderBenchmarkRow(task) {
|
|
14702
|
+
return [
|
|
14703
|
+
task.name,
|
|
14704
|
+
formatBenchNumber(task.throughput.mean || 0),
|
|
14705
|
+
formatBenchNumber(task.latency.min || 0),
|
|
14706
|
+
formatBenchNumber(task.latency.max || 0),
|
|
14707
|
+
formatBenchNumber(task.latency.mean || 0),
|
|
14708
|
+
formatBenchNumber(task.latency.p75 || 0),
|
|
14709
|
+
formatBenchNumber(task.latency.p99 || 0),
|
|
14710
|
+
formatBenchNumber(task.latency.p995 || 0),
|
|
14711
|
+
formatBenchNumber(task.latency.p999 || 0),
|
|
14712
|
+
`\u00B1${(task.latency.rme || 0).toFixed(2)}%`,
|
|
14713
|
+
String(task.latency.samplesCount || 0)
|
|
14714
|
+
];
|
|
14715
|
+
}
|
|
14716
|
+
function computeBenchColumnWidths(header, rows) {
|
|
14717
|
+
const allRows = [header, ...rows];
|
|
14718
|
+
return Array.from(header, (_, i) => Math.max(...allRows.map((row) => stripVTControlCharacters(row[i]).length)));
|
|
14719
|
+
}
|
|
14720
|
+
function padBenchRow(row, widths) {
|
|
14721
|
+
return row.map((v, i) => i === 0 ? v.padEnd(widths[i]) : v.padStart(widths[i]));
|
|
14722
|
+
}
|
|
14723
|
+
|
|
14682
14724
|
const BADGE_PADDING = " ";
|
|
14683
14725
|
class BaseReporter {
|
|
14684
14726
|
start = 0;
|
|
@@ -14692,6 +14734,7 @@ class BaseReporter {
|
|
|
14692
14734
|
silent;
|
|
14693
14735
|
_filesInWatchMode = /* @__PURE__ */ new Map();
|
|
14694
14736
|
_timeStart = formatTimeString(/* @__PURE__ */ new Date());
|
|
14737
|
+
_perProjectBenchmarks = /* @__PURE__ */ new Map();
|
|
14695
14738
|
constructor(options = {}) {
|
|
14696
14739
|
this.isTTY = options.isTTY ?? isTTY;
|
|
14697
14740
|
this.silent = options.silent;
|
|
@@ -14713,17 +14756,34 @@ class BaseReporter {
|
|
|
14713
14756
|
onTestRunStart(_specifications) {
|
|
14714
14757
|
this.start = performance$1.now();
|
|
14715
14758
|
this._timeStart = formatTimeString(/* @__PURE__ */ new Date());
|
|
14759
|
+
this._perProjectBenchmarks.clear();
|
|
14716
14760
|
}
|
|
14717
14761
|
onTestRunEnd(testModules, unhandledErrors, _reason) {
|
|
14718
14762
|
const files = testModules.map((testModule) => testModule.task);
|
|
14719
14763
|
const errors = [...unhandledErrors];
|
|
14720
14764
|
this.end = performance$1.now();
|
|
14721
14765
|
if (!files.length && !errors.length) this.ctx.logger.printNoTestFound(this.ctx.filenamePattern);
|
|
14722
|
-
else
|
|
14766
|
+
else {
|
|
14767
|
+
this.printPerProjectBenchmarks();
|
|
14768
|
+
this.reportSummary(files, errors);
|
|
14769
|
+
}
|
|
14723
14770
|
}
|
|
14724
14771
|
onTestCaseResult(testCase) {
|
|
14725
14772
|
if (testCase.result().state === "failed") this.logFailedTask(testCase.task);
|
|
14726
14773
|
}
|
|
14774
|
+
onTestCaseBenchmark(testCase, benchmark) {
|
|
14775
|
+
const projectName = testCase.project.name || "";
|
|
14776
|
+
for (const task of benchmark.tasks) {
|
|
14777
|
+
if (!task.perProject) continue;
|
|
14778
|
+
const benchKey = `${testCase.module.relativeModuleId} > ${testCase.fullName} > ${task.name}`;
|
|
14779
|
+
let projectMap = this._perProjectBenchmarks.get(benchKey);
|
|
14780
|
+
if (!projectMap) {
|
|
14781
|
+
projectMap = /* @__PURE__ */ new Map();
|
|
14782
|
+
this._perProjectBenchmarks.set(benchKey, projectMap);
|
|
14783
|
+
}
|
|
14784
|
+
projectMap.set(projectName, task);
|
|
14785
|
+
}
|
|
14786
|
+
}
|
|
14727
14787
|
onTestSuiteResult(testSuite) {
|
|
14728
14788
|
if (testSuite.state() === "failed") this.logFailedTask(testSuite.task);
|
|
14729
14789
|
}
|
|
@@ -14782,9 +14842,13 @@ class BaseReporter {
|
|
|
14782
14842
|
const { duration = 0 } = test.diagnostic() || {};
|
|
14783
14843
|
const padding = this.getTestIndentation(test.task);
|
|
14784
14844
|
const suffix = this.getTestCaseSuffix(test);
|
|
14845
|
+
// perProject tasks still appear in the inline table — they're additionally
|
|
14846
|
+
// aggregated in the cross-project section at the end of the run
|
|
14847
|
+
const inlineBenchmarks = test.benchmarks().filter((b) => b.tasks.length > 0);
|
|
14785
14848
|
if (testResult.state === "failed") this.log(c.red(` ${padding}${taskFail} ${this.getTestName(test.task, separator)}`) + suffix);
|
|
14786
|
-
else if (duration > this.ctx.config.slowTestThreshold) this.log(` ${padding}${c.yellow(c.dim(F_CHECK))} ${this.getTestName(test.task, separator)}
|
|
14787
|
-
else if (this.ctx.config.hideSkippedTests && testResult.state === "skipped" && test.options.mode !== "todo") ; else if (this.renderSucceed || moduleState === "failed") this.log(` ${padding}${this.getStateSymbol(test)} ${this.getTestName(test.task, separator)}${suffix}`);
|
|
14849
|
+
else if (duration > this.ctx.config.slowTestThreshold) this.log(` ${padding}${c.yellow(c.dim(F_CHECK))} ${this.getTestName(test.task, separator)}${suffix}`);
|
|
14850
|
+
else if (this.ctx.config.hideSkippedTests && testResult.state === "skipped" && test.options.mode !== "todo") ; else if (this.renderSucceed || moduleState === "failed" || inlineBenchmarks.length) this.log(` ${padding}${this.getStateSymbol(test)} ${this.getTestName(test.task, separator)}${suffix}`);
|
|
14851
|
+
if (inlineBenchmarks.length > 0) this.printBenchmarkTable(inlineBenchmarks, padding);
|
|
14788
14852
|
}
|
|
14789
14853
|
getModuleLog(testModule, counts) {
|
|
14790
14854
|
let state = c.dim(`${counts.tests} test${counts.tests > 1 ? "s" : ""}`);
|
|
@@ -14938,8 +15002,7 @@ class BaseReporter {
|
|
|
14938
15002
|
reportSummary(files, errors) {
|
|
14939
15003
|
this.printErrorsSummary(files, errors);
|
|
14940
15004
|
const leakCount = this.printLeaksSummary();
|
|
14941
|
-
|
|
14942
|
-
else this.reportTestSummary(files, errors, leakCount);
|
|
15005
|
+
this.reportTestSummary(files, errors, leakCount);
|
|
14943
15006
|
}
|
|
14944
15007
|
reportTestSummary(files, errors, leakCount) {
|
|
14945
15008
|
this.log();
|
|
@@ -15004,19 +15067,23 @@ class BaseReporter {
|
|
|
15004
15067
|
}
|
|
15005
15068
|
}
|
|
15006
15069
|
if (allImports.length === 0) return;
|
|
15007
|
-
|
|
15008
|
-
|
|
15009
|
-
|
|
15010
|
-
|
|
15070
|
+
let dangerImportsCount = 0;
|
|
15071
|
+
let hasWarnImports = false;
|
|
15072
|
+
let totalSelfTime = 0;
|
|
15073
|
+
let totalTotalTime = 0;
|
|
15074
|
+
for (const imp of allImports) {
|
|
15075
|
+
if (imp.totalTime >= thresholds.danger) dangerImportsCount++;
|
|
15076
|
+
if (imp.totalTime >= thresholds.warn) hasWarnImports = true;
|
|
15077
|
+
totalSelfTime += imp.selfTime;
|
|
15078
|
+
totalTotalTime += imp.totalTime;
|
|
15079
|
+
}
|
|
15011
15080
|
// Determine if we should print
|
|
15012
|
-
const shouldFail = failOnDanger &&
|
|
15081
|
+
const shouldFail = failOnDanger && dangerImportsCount > 0;
|
|
15013
15082
|
if (!(print === true || print === "on-warn" && hasWarnImports || shouldFail)) return;
|
|
15014
15083
|
const sortedImports = allImports.sort((a, b) => b.totalTime - a.totalTime);
|
|
15015
15084
|
const maxTotalTime = sortedImports[0].totalTime;
|
|
15016
15085
|
const limit = this.ctx.config.experimental.importDurations.limit;
|
|
15017
15086
|
const topImports = sortedImports.slice(0, limit);
|
|
15018
|
-
const totalSelfTime = allImports.reduce((sum, imp) => sum + imp.selfTime, 0);
|
|
15019
|
-
const totalTotalTime = allImports.reduce((sum, imp) => sum + imp.totalTime, 0);
|
|
15020
15087
|
const slowestImport = sortedImports[0];
|
|
15021
15088
|
this.log();
|
|
15022
15089
|
this.log(c.bold("Import Duration Breakdown") + c.dim(` (Top ${limit})`));
|
|
@@ -15056,7 +15123,7 @@ class BaseReporter {
|
|
|
15056
15123
|
// Fail if danger threshold exceeded
|
|
15057
15124
|
if (shouldFail) {
|
|
15058
15125
|
this.log();
|
|
15059
|
-
this.ctx.logger.error(`ERROR: ${
|
|
15126
|
+
this.ctx.logger.error(`ERROR: ${dangerImportsCount} import(s) exceeded the danger threshold of ${thresholds.danger}ms`);
|
|
15060
15127
|
process.exitCode = 1;
|
|
15061
15128
|
}
|
|
15062
15129
|
}
|
|
@@ -15123,21 +15190,63 @@ class BaseReporter {
|
|
|
15123
15190
|
}
|
|
15124
15191
|
return leakWithStacks.size;
|
|
15125
15192
|
}
|
|
15126
|
-
|
|
15127
|
-
|
|
15128
|
-
|
|
15129
|
-
for (const
|
|
15130
|
-
|
|
15131
|
-
|
|
15132
|
-
|
|
15133
|
-
|
|
15134
|
-
|
|
15135
|
-
|
|
15136
|
-
|
|
15137
|
-
|
|
15138
|
-
|
|
15139
|
-
|
|
15193
|
+
printPerProjectBenchmarks() {
|
|
15194
|
+
if (this._perProjectBenchmarks.size === 0) return;
|
|
15195
|
+
let hasComparable = false;
|
|
15196
|
+
for (const projectMap of this._perProjectBenchmarks.values()) if (projectMap.size > 1) {
|
|
15197
|
+
hasComparable = true;
|
|
15198
|
+
break;
|
|
15199
|
+
}
|
|
15200
|
+
if (!hasComparable) return;
|
|
15201
|
+
this.log("");
|
|
15202
|
+
this.log(divider(c.bold(c.bgBlue(` Cross-Project Benchmark Comparison `)), null, null, c.blue));
|
|
15203
|
+
for (const [benchName, projectMap] of this._perProjectBenchmarks) {
|
|
15204
|
+
const tasks = [...projectMap.entries()].sort((a, b) => a[1].latency.mean - b[1].latency.mean).map(([projectName, task], index) => ({
|
|
15205
|
+
...task,
|
|
15206
|
+
name: projectName,
|
|
15207
|
+
rank: index + 1
|
|
15208
|
+
}));
|
|
15209
|
+
if (tasks.length <= 1) continue;
|
|
15140
15210
|
this.log("");
|
|
15211
|
+
this.log(` ${c.dim(benchName)}`);
|
|
15212
|
+
this.printBenchmarkTable([{
|
|
15213
|
+
name: benchName,
|
|
15214
|
+
tasks
|
|
15215
|
+
}], "", "project");
|
|
15216
|
+
}
|
|
15217
|
+
this.log("");
|
|
15218
|
+
}
|
|
15219
|
+
printBenchmarkTable(benchmarks, basePadding, columnName = "name") {
|
|
15220
|
+
let printedCount = 0;
|
|
15221
|
+
for (const benchmark of benchmarks) {
|
|
15222
|
+
const { tasks } = benchmark;
|
|
15223
|
+
if (tasks.length === 0) continue;
|
|
15224
|
+
if (printedCount > 0) this.log("");
|
|
15225
|
+
const rows = tasks.map((t) => renderBenchmarkRow(t));
|
|
15226
|
+
const tableHead = [columnName, ...BENCH_TABLE_HEAD];
|
|
15227
|
+
const widths = computeBenchColumnWidths(tableHead, rows);
|
|
15228
|
+
const indent = ` ${basePadding} `;
|
|
15229
|
+
this.log(`${indent}${padBenchRow(tableHead, widths).map(c.bold).join(" ")}`);
|
|
15230
|
+
printedCount++;
|
|
15231
|
+
for (const task of tasks) {
|
|
15232
|
+
const padded = padBenchRow(renderBenchmarkRow(task), widths);
|
|
15233
|
+
let row = [
|
|
15234
|
+
padded[0],
|
|
15235
|
+
c.blue(padded[1]),
|
|
15236
|
+
c.cyan(padded[2]),
|
|
15237
|
+
c.cyan(padded[3]),
|
|
15238
|
+
c.cyan(padded[4]),
|
|
15239
|
+
c.cyan(padded[5]),
|
|
15240
|
+
c.cyan(padded[6]),
|
|
15241
|
+
c.cyan(padded[7]),
|
|
15242
|
+
c.cyan(padded[8]),
|
|
15243
|
+
c.dim(padded[9]),
|
|
15244
|
+
c.dim(padded[10])
|
|
15245
|
+
].join(" ");
|
|
15246
|
+
if (task.rank === 1 && tasks.length > 1) row += c.bold(c.green(" fastest"));
|
|
15247
|
+
if (task.rank === tasks.length && tasks.length > 2) row += c.bold(c.gray(" slowest"));
|
|
15248
|
+
this.log(`${indent}${row}`);
|
|
15249
|
+
}
|
|
15141
15250
|
}
|
|
15142
15251
|
}
|
|
15143
15252
|
printTaskErrors(tasks, errorDivider) {
|
|
@@ -15195,7 +15304,7 @@ function deepEqual(a, b) {
|
|
|
15195
15304
|
const keysA = Object.keys(a);
|
|
15196
15305
|
const keysB = Object.keys(b);
|
|
15197
15306
|
if (keysA.length !== keysB.length) return false;
|
|
15198
|
-
for (const key of keysA) if (!
|
|
15307
|
+
for (const key of keysA) if (!Object.prototype.hasOwnProperty.call(b, key) || !deepEqual(a[key], b[key])) return false;
|
|
15199
15308
|
return true;
|
|
15200
15309
|
}
|
|
15201
15310
|
function sum(items, cb) {
|
|
@@ -15208,6 +15317,9 @@ function getIndentation(suite, level = 1) {
|
|
|
15208
15317
|
return level;
|
|
15209
15318
|
}
|
|
15210
15319
|
|
|
15320
|
+
/** Minimum time between two renders, no matter how many scheduled renderes were called */
|
|
15321
|
+
const DEFAULT_RENDER_THRESHOLD_MS = 100;
|
|
15322
|
+
/** Interval between automatic renders. If no test state changes happened, this will increase just duration field */
|
|
15211
15323
|
const DEFAULT_RENDER_INTERVAL_MS = 1e3;
|
|
15212
15324
|
const ESC$1 = "\x1B[";
|
|
15213
15325
|
const CLEAR_LINE = `${ESC$1}K`;
|
|
@@ -15230,14 +15342,19 @@ class WindowRenderer {
|
|
|
15230
15342
|
cleanups = [];
|
|
15231
15343
|
constructor(options) {
|
|
15232
15344
|
this.options = {
|
|
15233
|
-
|
|
15234
|
-
|
|
15345
|
+
...options,
|
|
15346
|
+
threshold: options.threshold ?? DEFAULT_RENDER_THRESHOLD_MS,
|
|
15347
|
+
interval: options.interval ?? DEFAULT_RENDER_INTERVAL_MS
|
|
15235
15348
|
};
|
|
15349
|
+
// Capture the original write methods early, before intercepting these
|
|
15236
15350
|
this.streams = {
|
|
15237
15351
|
output: options.logger.outputStream.write.bind(options.logger.outputStream),
|
|
15238
15352
|
error: options.logger.errorStream.write.bind(options.logger.errorStream)
|
|
15239
15353
|
};
|
|
15240
15354
|
this.cleanups.push(this.interceptStream(process.stdout, "output"), this.interceptStream(process.stderr, "error"));
|
|
15355
|
+
// Intercept calls to custom VitestOptions.stdout and stderr streams
|
|
15356
|
+
if (options.logger.outputStream !== process.stdout) this.cleanups.push(this.interceptStream(options.logger.outputStream, "output"));
|
|
15357
|
+
if (options.logger.errorStream !== process.stderr) this.cleanups.push(this.interceptStream(options.logger.errorStream, "error"));
|
|
15241
15358
|
// Write buffered content on unexpected exits, e.g. direct `process.exit()` calls
|
|
15242
15359
|
this.options.logger.onTerminalCleanup(() => {
|
|
15243
15360
|
this.flushBuffer();
|
|
@@ -15269,9 +15386,10 @@ class WindowRenderer {
|
|
|
15269
15386
|
if (!this.renderScheduled) {
|
|
15270
15387
|
this.renderScheduled = true;
|
|
15271
15388
|
this.flushBuffer();
|
|
15272
|
-
setTimeout(() => {
|
|
15389
|
+
if (this.options.threshold) setTimeout(() => {
|
|
15273
15390
|
this.renderScheduled = false;
|
|
15274
|
-
},
|
|
15391
|
+
}, this.options.threshold).unref();
|
|
15392
|
+
else this.renderScheduled = false;
|
|
15275
15393
|
}
|
|
15276
15394
|
}
|
|
15277
15395
|
flushBuffer() {
|
|
@@ -15293,15 +15411,16 @@ class WindowRenderer {
|
|
|
15293
15411
|
if (current) this.render(current?.message, current?.type);
|
|
15294
15412
|
}
|
|
15295
15413
|
render(message, type = "output") {
|
|
15414
|
+
this.write(SYNC_START);
|
|
15296
15415
|
if (this.finished) {
|
|
15297
15416
|
this.clearWindow();
|
|
15298
|
-
|
|
15417
|
+
this.write(message || "", type);
|
|
15418
|
+
return this.write(SYNC_END);
|
|
15299
15419
|
}
|
|
15300
15420
|
const windowContent = this.options.getWindow();
|
|
15301
15421
|
const rowCount = getRenderedRowCount(windowContent, this.options.logger.getColumns());
|
|
15302
15422
|
let padding = this.windowHeight - rowCount;
|
|
15303
15423
|
if (padding > 0 && message) padding -= getRenderedRowCount([message], this.options.logger.getColumns());
|
|
15304
|
-
this.write(SYNC_START);
|
|
15305
15424
|
this.clearWindow();
|
|
15306
15425
|
if (message) this.write(message, type);
|
|
15307
15426
|
if (padding > 0) this.write("\n".repeat(padding));
|
|
@@ -15373,7 +15492,9 @@ class SummaryReporter {
|
|
|
15373
15492
|
};
|
|
15374
15493
|
this.renderer = new WindowRenderer({
|
|
15375
15494
|
logger: ctx.logger,
|
|
15376
|
-
getWindow: () => this.createSummary()
|
|
15495
|
+
getWindow: () => this.createSummary(),
|
|
15496
|
+
interval: this.options.interval,
|
|
15497
|
+
threshold: this.options.threshold
|
|
15377
15498
|
});
|
|
15378
15499
|
this.ctx.onClose(() => {
|
|
15379
15500
|
clearInterval(this.durationInterval);
|
|
@@ -15416,27 +15537,30 @@ class SummaryReporter {
|
|
|
15416
15537
|
this.maxParallelTests = Math.max(this.maxParallelTests, this.runningModules.size);
|
|
15417
15538
|
this.renderer.schedule();
|
|
15418
15539
|
}
|
|
15419
|
-
|
|
15420
|
-
const
|
|
15421
|
-
|
|
15422
|
-
const hook = {
|
|
15423
|
-
name: options.name,
|
|
15540
|
+
startStep(stats, name) {
|
|
15541
|
+
const step = {
|
|
15542
|
+
name,
|
|
15424
15543
|
visible: false,
|
|
15425
15544
|
startTime: performance.now(),
|
|
15426
15545
|
onFinish: () => {}
|
|
15427
15546
|
};
|
|
15428
|
-
stats.
|
|
15429
|
-
stats.
|
|
15547
|
+
stats.step?.onFinish?.();
|
|
15548
|
+
stats.step = step;
|
|
15549
|
+
if (!Number.isFinite(this.ctx.config.slowTestThreshold)) return;
|
|
15430
15550
|
const timeout = setTimeout(() => {
|
|
15431
|
-
|
|
15551
|
+
step.visible = true;
|
|
15432
15552
|
}, this.ctx.config.slowTestThreshold).unref();
|
|
15433
|
-
|
|
15553
|
+
step.onFinish = () => clearTimeout(timeout);
|
|
15554
|
+
}
|
|
15555
|
+
onHookStart(options) {
|
|
15556
|
+
const stats = this.getStepStats(options.entity);
|
|
15557
|
+
if (stats) this.startStep(stats, options.name);
|
|
15434
15558
|
}
|
|
15435
15559
|
onHookEnd(options) {
|
|
15436
|
-
const stats = this.
|
|
15437
|
-
if (stats?.
|
|
15438
|
-
stats.
|
|
15439
|
-
stats.
|
|
15560
|
+
const stats = this.getStepStats(options.entity);
|
|
15561
|
+
if (stats?.step?.name !== options.name) return;
|
|
15562
|
+
stats.step.onFinish();
|
|
15563
|
+
stats.step.visible = false;
|
|
15440
15564
|
}
|
|
15441
15565
|
onTestCaseReady(test) {
|
|
15442
15566
|
// Track slow running tests only on verbose mode
|
|
@@ -15449,11 +15573,11 @@ class SummaryReporter {
|
|
|
15449
15573
|
startTime: performance.now(),
|
|
15450
15574
|
onFinish: () => {}
|
|
15451
15575
|
};
|
|
15452
|
-
const timeout = setTimeout(() => {
|
|
15576
|
+
const timeout = Number.isFinite(this.ctx.config.slowTestThreshold) ? setTimeout(() => {
|
|
15453
15577
|
slowTest.visible = true;
|
|
15454
|
-
}, this.ctx.config.slowTestThreshold).unref();
|
|
15578
|
+
}, this.ctx.config.slowTestThreshold).unref() : void 0;
|
|
15455
15579
|
slowTest.onFinish = () => {
|
|
15456
|
-
slowTest.
|
|
15580
|
+
slowTest.step?.onFinish();
|
|
15457
15581
|
clearTimeout(timeout);
|
|
15458
15582
|
};
|
|
15459
15583
|
stats.tests.set(test.id, slowTest);
|
|
@@ -15493,7 +15617,7 @@ class SummaryReporter {
|
|
|
15493
15617
|
this.removeTestModule(module.id);
|
|
15494
15618
|
this.renderer.schedule();
|
|
15495
15619
|
}
|
|
15496
|
-
|
|
15620
|
+
getStepStats(entity) {
|
|
15497
15621
|
// Track slow running hooks only on verbose mode
|
|
15498
15622
|
if (!this.options.verbose) return;
|
|
15499
15623
|
const module = entity.type === "module" ? entity : entity.module;
|
|
@@ -15510,12 +15634,12 @@ class SummaryReporter {
|
|
|
15510
15634
|
name: testFile.projectName,
|
|
15511
15635
|
color: testFile.projectColor
|
|
15512
15636
|
}) + typecheck + label + testFile.filename + c.dim(!testFile.completed && !testFile.total ? " [queued]" : ` ${testFile.completed}/${testFile.total}`));
|
|
15513
|
-
const slowTasks = [testFile.
|
|
15637
|
+
const slowTasks = [testFile.step, ...testFile.tests.values()].filter((t) => t != null && t.visible);
|
|
15514
15638
|
for (const [index, task] of slowTasks.entries()) {
|
|
15515
15639
|
const elapsed = this.currentTime - task.startTime;
|
|
15516
15640
|
const icon = index === slowTasks.length - 1 ? F_TREE_NODE_END : F_TREE_NODE_MIDDLE;
|
|
15517
15641
|
summary.push(c.bold(c.yellow(` ${icon} `)) + task.name + c.bold(c.yellow(` ${formatTime(Math.max(0, elapsed))}`)));
|
|
15518
|
-
if (task.
|
|
15642
|
+
if (task.step?.visible) summary.push(c.bold(c.yellow(` ${F_TREE_NODE_END} `)) + task.step.name);
|
|
15519
15643
|
}
|
|
15520
15644
|
}
|
|
15521
15645
|
if (this.runningModules.size > 0) summary.push("");
|
|
@@ -15537,7 +15661,7 @@ class SummaryReporter {
|
|
|
15537
15661
|
removeTestModule(id) {
|
|
15538
15662
|
if (!id) return;
|
|
15539
15663
|
const testFile = this.runningModules.get(id);
|
|
15540
|
-
testFile?.
|
|
15664
|
+
testFile?.step?.onFinish();
|
|
15541
15665
|
testFile?.tests?.forEach((test) => test.onFinish());
|
|
15542
15666
|
this.runningModules.delete(id);
|
|
15543
15667
|
clearTimeout(this.finishedModules.get(id));
|
|
@@ -15630,7 +15754,10 @@ class DefaultReporter extends BaseReporter {
|
|
|
15630
15754
|
}
|
|
15631
15755
|
onInit(ctx) {
|
|
15632
15756
|
super.onInit(ctx);
|
|
15633
|
-
this.summary?.onInit(ctx, {
|
|
15757
|
+
this.summary?.onInit(ctx, {
|
|
15758
|
+
verbose: this.verbose,
|
|
15759
|
+
...this.options.summaryOptions
|
|
15760
|
+
});
|
|
15634
15761
|
}
|
|
15635
15762
|
}
|
|
15636
15763
|
|
|
@@ -16075,7 +16202,8 @@ class JsonReporter {
|
|
|
16075
16202
|
}
|
|
16076
16203
|
return filtered;
|
|
16077
16204
|
})() : t.meta,
|
|
16078
|
-
tags: t.tags || []
|
|
16205
|
+
tags: t.tags || [],
|
|
16206
|
+
benchmarks: t.benchmarks
|
|
16079
16207
|
};
|
|
16080
16208
|
});
|
|
16081
16209
|
if (tests.some((t) => t.result?.state === "run" || t.result?.state === "queued")) this.ctx.logger.warn("WARNING: Some tests are still running when generating the JSON report.This is likely an internal bug in Vitest.Please report it to https://github.com/vitest-dev/vitest/issues");
|
|
@@ -16235,6 +16363,18 @@ class JUnitReporter {
|
|
|
16235
16363
|
for (const log of logs) await this.baseLog(escapeXML(log.content));
|
|
16236
16364
|
});
|
|
16237
16365
|
}
|
|
16366
|
+
async writeSystemOut(task) {
|
|
16367
|
+
const logs = this.options.includeConsoleOutput && task.logs ? task.logs.filter((log) => log.type === "stdout") : [];
|
|
16368
|
+
const benchmarks = task.type === "test" ? task.benchmarks : [];
|
|
16369
|
+
if (logs.length === 0 && benchmarks.length === 0) return;
|
|
16370
|
+
await this.writeElement("system-out", {}, async () => {
|
|
16371
|
+
for (const log of logs) await this.baseLog(escapeXML(log.content));
|
|
16372
|
+
if (benchmarks.length > 0) {
|
|
16373
|
+
if (logs.length > 0) await this.baseLog("");
|
|
16374
|
+
await this.baseLog(escapeXML(renderBenchmarkTableText(benchmarks)));
|
|
16375
|
+
}
|
|
16376
|
+
});
|
|
16377
|
+
}
|
|
16238
16378
|
applyTemplate(template, vars) {
|
|
16239
16379
|
if (typeof template === "function") return template(vars);
|
|
16240
16380
|
return template.replace(/\{filename\}/g, () => vars.filename).replace(/\{filepath\}/g, () => vars.filepath).replace(/\{basename\}/g, () => vars.basename).replace(/\{classname\}/g, () => vars.classname).replace(/\{title\}/g, () => vars.title).replace(/\{suitename\}/g, () => vars.suitename).replace(/\{displayName\}/g, () => vars.displayName);
|
|
@@ -16260,10 +16400,8 @@ class JUnitReporter {
|
|
|
16260
16400
|
name: testcaseName,
|
|
16261
16401
|
time: getDuration(task)
|
|
16262
16402
|
}, async () => {
|
|
16263
|
-
|
|
16264
|
-
|
|
16265
|
-
await this.writeLogs(task, "err");
|
|
16266
|
-
}
|
|
16403
|
+
await this.writeSystemOut(task);
|
|
16404
|
+
if (this.options.includeConsoleOutput) await this.writeLogs(task, "err");
|
|
16267
16405
|
if (task.mode === "skip" || task.mode === "todo") await this.logger.log("<skipped/>");
|
|
16268
16406
|
if (task.type === "test" && task.annotations.length) {
|
|
16269
16407
|
await this.logger.log("<properties>");
|
|
@@ -16277,14 +16415,7 @@ class JUnitReporter {
|
|
|
16277
16415
|
}
|
|
16278
16416
|
if (task.result?.state === "fail") {
|
|
16279
16417
|
const errors = task.result.errors || [];
|
|
16280
|
-
for (const error of errors) await this.
|
|
16281
|
-
message: error?.message,
|
|
16282
|
-
type: error?.name
|
|
16283
|
-
}, async () => {
|
|
16284
|
-
if (!error || !this.options.stackTrace) return;
|
|
16285
|
-
const result = this.ctx.logger.formatError(error, { project: this.ctx.getProjectByName(task.file?.projectName ?? "") });
|
|
16286
|
-
await this.baseLog(escapeXML(stripVTControlCharacters(result.output.trim())));
|
|
16287
|
-
});
|
|
16418
|
+
for (const error of errors) await this.writeErrorElement("failure", error, { project: this.ctx.getProjectByName(task.file?.projectName ?? "") });
|
|
16288
16419
|
}
|
|
16289
16420
|
});
|
|
16290
16421
|
}
|
|
@@ -16303,7 +16434,52 @@ class JUnitReporter {
|
|
|
16303
16434
|
if (typeof this.options.suiteNameTemplate === "function") return this.options.suiteNameTemplate(vars);
|
|
16304
16435
|
return this.options.suiteNameTemplate.replace(/\{filepath\}/g, () => vars.filepath).replace(/\{filename\}/g, () => vars.filename).replace(/\{basename\}/g, () => vars.basename).replace(/\{displayName\}/g, () => vars.displayName).replace(/\{title\}/g, () => vars.title);
|
|
16305
16436
|
}
|
|
16306
|
-
async
|
|
16437
|
+
async writeErrorElement(elementName, error, errorOptions) {
|
|
16438
|
+
await this.writeElement(elementName, {
|
|
16439
|
+
message: error?.message,
|
|
16440
|
+
type: error?.name
|
|
16441
|
+
}, async () => {
|
|
16442
|
+
if (!error || !this.options.stackTrace) return;
|
|
16443
|
+
const result = this.ctx.logger.formatError(error, errorOptions);
|
|
16444
|
+
await this.baseLog(escapeXML(stripVTControlCharacters(result.output.trim())));
|
|
16445
|
+
});
|
|
16446
|
+
}
|
|
16447
|
+
async writeUnhandledErrorsTestsuite(unhandledErrors, testModules) {
|
|
16448
|
+
await this.writeElement("testsuite", {
|
|
16449
|
+
name: "vitest unhandled errors",
|
|
16450
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16451
|
+
hostname: this.options.hostname || hostname(),
|
|
16452
|
+
tests: unhandledErrors.length,
|
|
16453
|
+
failures: 0,
|
|
16454
|
+
errors: unhandledErrors.length,
|
|
16455
|
+
skipped: 0,
|
|
16456
|
+
time: "0"
|
|
16457
|
+
}, async () => {
|
|
16458
|
+
// Stable order across runs — workers/projects report errors concurrently.
|
|
16459
|
+
const sortedErrors = [...unhandledErrors].sort((a, b) => {
|
|
16460
|
+
const ka = `${a.VITEST_TEST_PATH ?? ""}\0${a.type ?? ""}\0${a.name ?? ""}\0${a.message ?? ""}`;
|
|
16461
|
+
const kb = `${b.VITEST_TEST_PATH ?? ""}\0${b.type ?? ""}\0${b.name ?? ""}\0${b.message ?? ""}`;
|
|
16462
|
+
return ka < kb ? -1 : ka > kb ? 1 : 0;
|
|
16463
|
+
});
|
|
16464
|
+
for (const error of sortedErrors) {
|
|
16465
|
+
const errorTitle = error.type || error.name || "Unhandled Error";
|
|
16466
|
+
// Only attribute when the path resolves to exactly one module — when
|
|
16467
|
+
// multiple projects share a file, errors lack a project identifier and
|
|
16468
|
+
// we can't disambiguate without one (tracked for follow-up).
|
|
16469
|
+
const matches = error.VITEST_TEST_PATH ? testModules.filter((m) => m.task.filepath === error.VITEST_TEST_PATH) : [];
|
|
16470
|
+
const owningModule = matches.length === 1 ? matches[0] : void 0;
|
|
16471
|
+
await this.writeElement("testcase", {
|
|
16472
|
+
classname: "vitest unhandled errors",
|
|
16473
|
+
file: this.options.addFileAttribute && error.VITEST_TEST_PATH ? relative(this.ctx.config.root, error.VITEST_TEST_PATH) : void 0,
|
|
16474
|
+
name: error.message ? `${errorTitle}: ${error.message}` : errorTitle,
|
|
16475
|
+
time: "0"
|
|
16476
|
+
}, async () => {
|
|
16477
|
+
await this.writeErrorElement("error", error, { project: owningModule?.project });
|
|
16478
|
+
});
|
|
16479
|
+
}
|
|
16480
|
+
});
|
|
16481
|
+
}
|
|
16482
|
+
async onTestRunEnd(testModules, unhandledErrors = []) {
|
|
16307
16483
|
const files = testModules.map((testModule) => testModule.task);
|
|
16308
16484
|
const separator = this.options.ancestorSeparator ?? " > ";
|
|
16309
16485
|
await this.logger.log("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
|
|
@@ -16343,7 +16519,8 @@ class JUnitReporter {
|
|
|
16343
16519
|
suite: null,
|
|
16344
16520
|
file: null,
|
|
16345
16521
|
annotations: [],
|
|
16346
|
-
artifacts: []
|
|
16522
|
+
artifacts: [],
|
|
16523
|
+
benchmarks: []
|
|
16347
16524
|
});
|
|
16348
16525
|
}
|
|
16349
16526
|
return {
|
|
@@ -16361,32 +16538,36 @@ class JUnitReporter {
|
|
|
16361
16538
|
name: this.options.suiteName || "vitest tests",
|
|
16362
16539
|
tests: 0,
|
|
16363
16540
|
failures: 0,
|
|
16364
|
-
errors:
|
|
16541
|
+
errors: unhandledErrors.length,
|
|
16365
16542
|
time: 0
|
|
16366
16543
|
});
|
|
16544
|
+
stats.tests += unhandledErrors.length;
|
|
16545
|
+
// Plain byte compare (not localeCompare) so output is identical across machines and ICU versions.
|
|
16546
|
+
const orderedSuites = transformed.map((file, i) => {
|
|
16547
|
+
const filename = relative(this.ctx.config.root, file.filepath);
|
|
16548
|
+
return {
|
|
16549
|
+
file,
|
|
16550
|
+
filename,
|
|
16551
|
+
suiteName: this.resolveSuiteNameTemplate(files[i], filename)
|
|
16552
|
+
};
|
|
16553
|
+
}).sort((a, b) => a.suiteName < b.suiteName ? -1 : a.suiteName > b.suiteName ? 1 : 0);
|
|
16367
16554
|
await this.writeElement("testsuites", {
|
|
16368
16555
|
...stats,
|
|
16369
16556
|
time: executionTime(stats.time)
|
|
16370
16557
|
}, async () => {
|
|
16371
|
-
for (
|
|
16372
|
-
|
|
16373
|
-
|
|
16374
|
-
|
|
16375
|
-
|
|
16376
|
-
|
|
16377
|
-
|
|
16378
|
-
|
|
16379
|
-
|
|
16380
|
-
|
|
16381
|
-
|
|
16382
|
-
|
|
16383
|
-
|
|
16384
|
-
skipped: file.stats.skipped,
|
|
16385
|
-
time: getDuration(file)
|
|
16386
|
-
}, async () => {
|
|
16387
|
-
await this.writeTasks(file.tasks, filename, file.filepath);
|
|
16388
|
-
});
|
|
16389
|
-
}
|
|
16558
|
+
for (const { file, filename, suiteName } of orderedSuites) await this.writeElement("testsuite", {
|
|
16559
|
+
name: suiteName,
|
|
16560
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16561
|
+
hostname: this.options.hostname || hostname(),
|
|
16562
|
+
tests: file.tasks.length,
|
|
16563
|
+
failures: file.stats.failures,
|
|
16564
|
+
errors: 0,
|
|
16565
|
+
skipped: file.stats.skipped,
|
|
16566
|
+
time: getDuration(file)
|
|
16567
|
+
}, async () => {
|
|
16568
|
+
await this.writeTasks(file.tasks, filename, file.filepath);
|
|
16569
|
+
});
|
|
16570
|
+
if (unhandledErrors.length) await this.writeUnhandledErrorsTestsuite(unhandledErrors, testModules);
|
|
16390
16571
|
});
|
|
16391
16572
|
if (this.reportFile) this.ctx.logger.log(`JUNIT report written to ${this.reportFile}`);
|
|
16392
16573
|
await this.fileFd?.close();
|
|
@@ -16541,231 +16722,11 @@ class VerboseReporter extends DefaultReporter {
|
|
|
16541
16722
|
this.printAnnotations(test, "log", 3);
|
|
16542
16723
|
this.log();
|
|
16543
16724
|
}
|
|
16725
|
+
const inlineBenchmarks = test.benchmarks().filter((b) => b.tasks.length > 0);
|
|
16726
|
+
if (inlineBenchmarks.length > 0) this.printBenchmarkTable(inlineBenchmarks, "");
|
|
16544
16727
|
}
|
|
16545
16728
|
}
|
|
16546
16729
|
|
|
16547
|
-
function createBenchmarkJsonReport(files) {
|
|
16548
|
-
const report = { files: [] };
|
|
16549
|
-
for (const file of files) {
|
|
16550
|
-
const groups = [];
|
|
16551
|
-
for (const task of getTasks(file)) if (task?.type === "suite") {
|
|
16552
|
-
const benchmarks = [];
|
|
16553
|
-
for (const t of task.tasks) {
|
|
16554
|
-
const benchmark = t.meta.benchmark && t.result?.benchmark;
|
|
16555
|
-
if (benchmark) benchmarks.push({
|
|
16556
|
-
id: t.id,
|
|
16557
|
-
...benchmark,
|
|
16558
|
-
samples: []
|
|
16559
|
-
});
|
|
16560
|
-
}
|
|
16561
|
-
if (benchmarks.length) groups.push({
|
|
16562
|
-
fullName: getFullName(task, " > "),
|
|
16563
|
-
benchmarks
|
|
16564
|
-
});
|
|
16565
|
-
}
|
|
16566
|
-
report.files.push({
|
|
16567
|
-
filepath: file.filepath,
|
|
16568
|
-
groups
|
|
16569
|
-
});
|
|
16570
|
-
}
|
|
16571
|
-
return report;
|
|
16572
|
-
}
|
|
16573
|
-
function flattenFormattedBenchmarkReport(report) {
|
|
16574
|
-
const flat = {};
|
|
16575
|
-
for (const file of report.files) for (const group of file.groups) for (const t of group.benchmarks) flat[t.id] = t;
|
|
16576
|
-
return flat;
|
|
16577
|
-
}
|
|
16578
|
-
|
|
16579
|
-
const outputMap = /* @__PURE__ */ new WeakMap();
|
|
16580
|
-
function formatNumber(number) {
|
|
16581
|
-
const res = String(number.toFixed(number < 100 ? 4 : 2)).split(".");
|
|
16582
|
-
return res[0].replace(/(?=(?:\d{3})+$)\B/g, ",") + (res[1] ? `.${res[1]}` : "");
|
|
16583
|
-
}
|
|
16584
|
-
const tableHead = [
|
|
16585
|
-
"name",
|
|
16586
|
-
"hz",
|
|
16587
|
-
"min",
|
|
16588
|
-
"max",
|
|
16589
|
-
"mean",
|
|
16590
|
-
"p75",
|
|
16591
|
-
"p99",
|
|
16592
|
-
"p995",
|
|
16593
|
-
"p999",
|
|
16594
|
-
"rme",
|
|
16595
|
-
"samples"
|
|
16596
|
-
];
|
|
16597
|
-
function renderBenchmarkItems(result) {
|
|
16598
|
-
return [
|
|
16599
|
-
result.name,
|
|
16600
|
-
formatNumber(result.hz || 0),
|
|
16601
|
-
formatNumber(result.min || 0),
|
|
16602
|
-
formatNumber(result.max || 0),
|
|
16603
|
-
formatNumber(result.mean || 0),
|
|
16604
|
-
formatNumber(result.p75 || 0),
|
|
16605
|
-
formatNumber(result.p99 || 0),
|
|
16606
|
-
formatNumber(result.p995 || 0),
|
|
16607
|
-
formatNumber(result.p999 || 0),
|
|
16608
|
-
`±${(result.rme || 0).toFixed(2)}%`,
|
|
16609
|
-
(result.sampleCount || 0).toString()
|
|
16610
|
-
];
|
|
16611
|
-
}
|
|
16612
|
-
function computeColumnWidths(results) {
|
|
16613
|
-
const rows = [tableHead, ...results.map((v) => renderBenchmarkItems(v))];
|
|
16614
|
-
return Array.from(tableHead, (_, i) => Math.max(...rows.map((row) => stripVTControlCharacters(row[i]).length)));
|
|
16615
|
-
}
|
|
16616
|
-
function padRow(row, widths) {
|
|
16617
|
-
return row.map((v, i) => i ? v.padStart(widths[i], " ") : v.padEnd(widths[i], " "));
|
|
16618
|
-
}
|
|
16619
|
-
function renderTableHead(widths) {
|
|
16620
|
-
return " ".repeat(3) + padRow(tableHead, widths).map(c.bold).join(" ");
|
|
16621
|
-
}
|
|
16622
|
-
function renderBenchmark(result, widths) {
|
|
16623
|
-
const padded = padRow(renderBenchmarkItems(result), widths);
|
|
16624
|
-
return [
|
|
16625
|
-
padded[0],
|
|
16626
|
-
c.blue(padded[1]),
|
|
16627
|
-
c.cyan(padded[2]),
|
|
16628
|
-
c.cyan(padded[3]),
|
|
16629
|
-
c.cyan(padded[4]),
|
|
16630
|
-
c.cyan(padded[5]),
|
|
16631
|
-
c.cyan(padded[6]),
|
|
16632
|
-
c.cyan(padded[7]),
|
|
16633
|
-
c.cyan(padded[8]),
|
|
16634
|
-
c.dim(padded[9]),
|
|
16635
|
-
c.dim(padded[10])
|
|
16636
|
-
].join(" ");
|
|
16637
|
-
}
|
|
16638
|
-
function renderTable(options) {
|
|
16639
|
-
const output = [];
|
|
16640
|
-
const benchMap = {};
|
|
16641
|
-
for (const task of options.tasks) if (task.meta.benchmark && task.result?.benchmark) benchMap[task.id] = {
|
|
16642
|
-
current: task.result.benchmark,
|
|
16643
|
-
baseline: options.compare?.[task.id]
|
|
16644
|
-
};
|
|
16645
|
-
const benchCount = Object.entries(benchMap).length;
|
|
16646
|
-
const columnWidths = computeColumnWidths(Object.values(benchMap).flatMap((v) => [v.current, v.baseline]).filter(notNullish));
|
|
16647
|
-
let idx = 0;
|
|
16648
|
-
const padding = " ".repeat(1 );
|
|
16649
|
-
for (const task of options.tasks) {
|
|
16650
|
-
const duration = task.result?.duration;
|
|
16651
|
-
const bench = benchMap[task.id];
|
|
16652
|
-
let prefix = "";
|
|
16653
|
-
if (idx === 0 && task.meta?.benchmark) prefix += `${renderTableHead(columnWidths)}\n${padding}`;
|
|
16654
|
-
prefix += ` ${getStateSymbol(task)} `;
|
|
16655
|
-
let suffix = "";
|
|
16656
|
-
if (task.type === "suite") suffix += c.dim(` (${getTests(task).length})`);
|
|
16657
|
-
if (task.mode === "skip" || task.mode === "todo") suffix += c.dim(c.gray(" [skipped]"));
|
|
16658
|
-
if (duration != null) {
|
|
16659
|
-
const color = duration > options.slowTestThreshold ? c.yellow : c.green;
|
|
16660
|
-
suffix += color(` ${Math.round(duration)}${c.dim("ms")}`);
|
|
16661
|
-
}
|
|
16662
|
-
if (options.showHeap && task.result?.heap != null) suffix += c.magenta(` ${Math.floor(task.result.heap / 1024 / 1024)} MB heap used`);
|
|
16663
|
-
if (bench) {
|
|
16664
|
-
let body = renderBenchmark(bench.current, columnWidths);
|
|
16665
|
-
if (options.compare && bench.baseline) {
|
|
16666
|
-
if (bench.current.hz) {
|
|
16667
|
-
const diff = bench.current.hz / bench.baseline.hz;
|
|
16668
|
-
const diffFixed = diff.toFixed(2);
|
|
16669
|
-
if (diffFixed === "1.0.0") body += c.gray(` [${diffFixed}x]`);
|
|
16670
|
-
if (diff > 1) body += c.blue(` [${diffFixed}x] ⇑`);
|
|
16671
|
-
else body += c.red(` [${diffFixed}x] ⇓`);
|
|
16672
|
-
}
|
|
16673
|
-
output.push(padding + prefix + body + suffix);
|
|
16674
|
-
const bodyBaseline = renderBenchmark(bench.baseline, columnWidths);
|
|
16675
|
-
output.push(`${padding} ${bodyBaseline} ${c.dim("(baseline)")}`);
|
|
16676
|
-
} else {
|
|
16677
|
-
if (bench.current.rank === 1 && benchCount > 1) body += c.bold(c.green(" fastest"));
|
|
16678
|
-
if (bench.current.rank === benchCount && benchCount > 2) body += c.bold(c.gray(" slowest"));
|
|
16679
|
-
output.push(padding + prefix + body + suffix);
|
|
16680
|
-
}
|
|
16681
|
-
} else output.push(padding + prefix + task.name + suffix);
|
|
16682
|
-
if (task.result?.state !== "pass" && outputMap.get(task) != null) {
|
|
16683
|
-
let data = outputMap.get(task);
|
|
16684
|
-
if (typeof data === "string") {
|
|
16685
|
-
data = stripVTControlCharacters(data.trim().split("\n").filter(Boolean).pop());
|
|
16686
|
-
if (data === "") data = void 0;
|
|
16687
|
-
}
|
|
16688
|
-
if (data != null) {
|
|
16689
|
-
const out = ` ${" ".repeat(options.level)}${F_RIGHT} ${data}`;
|
|
16690
|
-
output.push(c.gray(truncateString(out, options.columns)));
|
|
16691
|
-
}
|
|
16692
|
-
}
|
|
16693
|
-
idx++;
|
|
16694
|
-
}
|
|
16695
|
-
return output.filter(Boolean).join("\n");
|
|
16696
|
-
}
|
|
16697
|
-
|
|
16698
|
-
class BenchmarkReporter extends DefaultReporter {
|
|
16699
|
-
compare;
|
|
16700
|
-
async onInit(ctx) {
|
|
16701
|
-
super.onInit(ctx);
|
|
16702
|
-
if (this.ctx.config.benchmark?.compare) {
|
|
16703
|
-
const compareFile = pathe.resolve(this.ctx.config.root, this.ctx.config.benchmark?.compare);
|
|
16704
|
-
try {
|
|
16705
|
-
this.compare = flattenFormattedBenchmarkReport(JSON.parse(await fs__default.promises.readFile(compareFile, "utf-8")));
|
|
16706
|
-
} catch (e) {
|
|
16707
|
-
this.error(`Failed to read '${compareFile}'`, e);
|
|
16708
|
-
}
|
|
16709
|
-
}
|
|
16710
|
-
}
|
|
16711
|
-
onTaskUpdate(packs) {
|
|
16712
|
-
for (const pack of packs) {
|
|
16713
|
-
const task = this.ctx.state.idMap.get(pack[0]);
|
|
16714
|
-
if (task?.type === "suite" && task.result?.state !== "run") task.tasks.filter((task) => task.result?.benchmark).sort((benchA, benchB) => benchA.result.benchmark.mean - benchB.result.benchmark.mean).forEach((bench, idx) => {
|
|
16715
|
-
bench.result.benchmark.rank = Number(idx) + 1;
|
|
16716
|
-
});
|
|
16717
|
-
}
|
|
16718
|
-
}
|
|
16719
|
-
onTestSuiteResult(testSuite) {
|
|
16720
|
-
super.onTestSuiteResult(testSuite);
|
|
16721
|
-
this.printSuiteTable(testSuite);
|
|
16722
|
-
}
|
|
16723
|
-
printTestModule(testModule) {
|
|
16724
|
-
this.printSuiteTable(testModule);
|
|
16725
|
-
}
|
|
16726
|
-
printSuiteTable(testTask) {
|
|
16727
|
-
const state = testTask.state();
|
|
16728
|
-
if (state === "pending" || state === "queued") return;
|
|
16729
|
-
const benches = testTask.task.tasks.filter((t) => t.meta.benchmark);
|
|
16730
|
-
const duration = testTask.task.result?.duration || 0;
|
|
16731
|
-
if (benches.length > 0 && benches.every((t) => t.result?.state !== "run" && t.result?.state !== "queued")) {
|
|
16732
|
-
let title = `\n ${getStateSymbol(testTask.task)} ${formatProjectName(testTask.project)}${getFullName(testTask.task, separator)}`;
|
|
16733
|
-
if (duration != null && duration > this.ctx.config.slowTestThreshold) title += c.yellow(` ${Math.round(duration)}${c.dim("ms")}`);
|
|
16734
|
-
this.log(title);
|
|
16735
|
-
this.log(renderTable({
|
|
16736
|
-
tasks: benches,
|
|
16737
|
-
level: 1,
|
|
16738
|
-
columns: this.ctx.logger.getColumns(),
|
|
16739
|
-
compare: this.compare,
|
|
16740
|
-
showHeap: this.ctx.config.logHeapUsage,
|
|
16741
|
-
slowTestThreshold: this.ctx.config.slowTestThreshold
|
|
16742
|
-
}));
|
|
16743
|
-
}
|
|
16744
|
-
}
|
|
16745
|
-
async onTestRunEnd(testModules, unhandledErrors, reason) {
|
|
16746
|
-
super.onTestRunEnd(testModules, unhandledErrors, reason);
|
|
16747
|
-
// write output for future comparison
|
|
16748
|
-
let outputFile = this.ctx.config.benchmark?.outputJson;
|
|
16749
|
-
if (outputFile) {
|
|
16750
|
-
outputFile = pathe.resolve(this.ctx.config.root, outputFile);
|
|
16751
|
-
const outputDirectory = pathe.dirname(outputFile);
|
|
16752
|
-
if (!fs__default.existsSync(outputDirectory)) await fs__default.promises.mkdir(outputDirectory, { recursive: true });
|
|
16753
|
-
const output = createBenchmarkJsonReport(testModules.map((t) => t.task.file));
|
|
16754
|
-
await fs__default.promises.writeFile(outputFile, JSON.stringify(output, null, 2));
|
|
16755
|
-
this.log(`Benchmark report written to ${outputFile}`);
|
|
16756
|
-
}
|
|
16757
|
-
}
|
|
16758
|
-
}
|
|
16759
|
-
|
|
16760
|
-
class VerboseBenchmarkReporter extends BenchmarkReporter {
|
|
16761
|
-
verbose = true;
|
|
16762
|
-
}
|
|
16763
|
-
|
|
16764
|
-
const BenchmarkReportsMap = {
|
|
16765
|
-
default: BenchmarkReporter,
|
|
16766
|
-
verbose: VerboseBenchmarkReporter
|
|
16767
|
-
};
|
|
16768
|
-
|
|
16769
16730
|
const ReportersMap = {
|
|
16770
16731
|
"default": DefaultReporter,
|
|
16771
16732
|
"agent": MinimalReporter,
|
|
@@ -16809,16 +16770,6 @@ function createReporters(reporterReferences, ctx) {
|
|
|
16809
16770
|
});
|
|
16810
16771
|
return Promise.all(promisedReporters);
|
|
16811
16772
|
}
|
|
16812
|
-
function createBenchmarkReporters(reporterReferences, runner) {
|
|
16813
|
-
const promisedReporters = reporterReferences.map(async (referenceOrInstance) => {
|
|
16814
|
-
if (typeof referenceOrInstance === "string") if (referenceOrInstance in BenchmarkReportsMap) {
|
|
16815
|
-
const BuiltinReporter = BenchmarkReportsMap[referenceOrInstance];
|
|
16816
|
-
return new BuiltinReporter();
|
|
16817
|
-
} else return new (await (loadCustomReporterModule(referenceOrInstance, runner)))();
|
|
16818
|
-
return referenceOrInstance;
|
|
16819
|
-
});
|
|
16820
|
-
return Promise.all(promisedReporters);
|
|
16821
|
-
}
|
|
16822
16773
|
|
|
16823
16774
|
function parseFilter(filter) {
|
|
16824
16775
|
const colonIndex = filter.lastIndexOf(":");
|
|
@@ -16994,6 +16945,12 @@ class ReportedTaskImplementation {
|
|
|
16994
16945
|
return this.task.meta;
|
|
16995
16946
|
}
|
|
16996
16947
|
/**
|
|
16948
|
+
* Console logs recorded during the test execution.
|
|
16949
|
+
*/
|
|
16950
|
+
logs() {
|
|
16951
|
+
return [...this.task.logs || []];
|
|
16952
|
+
}
|
|
16953
|
+
/**
|
|
16997
16954
|
* Creates a new reported task instance and stores it in the project's state for future use.
|
|
16998
16955
|
* @internal
|
|
16999
16956
|
*/
|
|
@@ -17094,6 +17051,14 @@ class TestCase extends ReportedTaskImplementation {
|
|
|
17094
17051
|
return [...this.task.artifacts];
|
|
17095
17052
|
}
|
|
17096
17053
|
/**
|
|
17054
|
+
* @experimental
|
|
17055
|
+
*
|
|
17056
|
+
* A list of benchmarks performed during the test.
|
|
17057
|
+
*/
|
|
17058
|
+
benchmarks() {
|
|
17059
|
+
return [...this.task.benchmarks];
|
|
17060
|
+
}
|
|
17061
|
+
/**
|
|
17097
17062
|
* Useful information about the test like duration, memory usage, etc.
|
|
17098
17063
|
* Diagnostic is only available after the test has finished.
|
|
17099
17064
|
*/
|
|
@@ -17998,21 +17963,23 @@ class TestRun {
|
|
|
17998
17963
|
this.vitest.state.updateUserLog(log);
|
|
17999
17964
|
await this.vitest.report("onUserConsoleLog", log);
|
|
18000
17965
|
}
|
|
17966
|
+
async recordBenchmark(testId, benchmark) {
|
|
17967
|
+
const testCase = this.getTestCaseById(testId, "Benchmark");
|
|
17968
|
+
testCase.task.benchmarks.push(benchmark);
|
|
17969
|
+
await this.vitest.report("onTestCaseBenchmark", testCase, benchmark);
|
|
17970
|
+
}
|
|
18001
17971
|
async recordArtifact(testId, artifact) {
|
|
18002
|
-
const
|
|
18003
|
-
const entity = task && this.vitest.state.getReportedEntity(task);
|
|
18004
|
-
assert$1(task && entity, `Entity must be found for task ${task?.name || testId}`);
|
|
18005
|
-
assert$1(entity.type === "test", `Artifacts can only be recorded on a test, instead got ${entity.type}`);
|
|
17972
|
+
const testCase = this.getTestCaseById(testId, "Artifact");
|
|
18006
17973
|
// annotations won't resolve as artifacts for backwards compatibility until next major
|
|
18007
17974
|
if (artifact.type === "internal:annotation") {
|
|
18008
|
-
await this.resolveTestAttachment(
|
|
18009
|
-
|
|
18010
|
-
await this.vitest.report("onTestCaseAnnotate",
|
|
17975
|
+
await this.resolveTestAttachment(testCase, artifact.annotation.attachment, artifact.annotation.message);
|
|
17976
|
+
testCase.task.annotations.push(artifact.annotation);
|
|
17977
|
+
await this.vitest.report("onTestCaseAnnotate", testCase, artifact.annotation);
|
|
18011
17978
|
return artifact;
|
|
18012
17979
|
}
|
|
18013
|
-
if (Array.isArray(artifact.attachments)) await Promise.all(artifact.attachments.map((attachment) => this.resolveTestAttachment(
|
|
18014
|
-
|
|
18015
|
-
await this.vitest.report("onTestCaseArtifactRecord",
|
|
17980
|
+
if (Array.isArray(artifact.attachments)) await Promise.all(artifact.attachments.map((attachment) => this.resolveTestAttachment(testCase, attachment)));
|
|
17981
|
+
testCase.task.artifacts.push(artifact);
|
|
17982
|
+
await this.vitest.report("onTestCaseArtifactRecord", testCase, artifact);
|
|
18016
17983
|
return artifact;
|
|
18017
17984
|
}
|
|
18018
17985
|
async updated(update, events) {
|
|
@@ -18026,6 +17993,13 @@ class TestRun {
|
|
|
18026
17993
|
// TODO: error handling - what happens if custom reporter throws an error?
|
|
18027
17994
|
await this.vitest.report("onTaskUpdate", update, events);
|
|
18028
17995
|
}
|
|
17996
|
+
getTestCaseById(testId, recordType) {
|
|
17997
|
+
const task = this.vitest.state.idMap.get(testId);
|
|
17998
|
+
const entity = task && this.vitest.state.getReportedEntity(task);
|
|
17999
|
+
assert$1(task && entity, `Entity must be found for task ${task?.name || testId}`);
|
|
18000
|
+
assert$1(entity.type === "test", `${recordType} can only be recorded on a test, instead got ${entity.type}`);
|
|
18001
|
+
return entity;
|
|
18002
|
+
}
|
|
18029
18003
|
async end(specifications, errors, coverage) {
|
|
18030
18004
|
if (coverage) await this.vitest.report("onCoverage", coverage);
|
|
18031
18005
|
// specification won't have the File task if they were filtered by the --shard command
|
|
@@ -18390,7 +18364,7 @@ class Vitest {
|
|
|
18390
18364
|
* The logger instance used to log messages. It's recommended to use this logger instead of `console`.
|
|
18391
18365
|
* It's possible to override stdout and stderr streams when initiating Vitest.
|
|
18392
18366
|
* @example
|
|
18393
|
-
* new Vitest(
|
|
18367
|
+
* new Vitest({
|
|
18394
18368
|
* stdout: new Writable(),
|
|
18395
18369
|
* })
|
|
18396
18370
|
*/
|
|
@@ -18452,8 +18426,20 @@ class Vitest {
|
|
|
18452
18426
|
_cache;
|
|
18453
18427
|
_snapshot;
|
|
18454
18428
|
_coverageProvider;
|
|
18455
|
-
|
|
18456
|
-
|
|
18429
|
+
/**
|
|
18430
|
+
* @deprecated Do not rely on this property, it's always `test`. Scheduled to be removed in the next major.
|
|
18431
|
+
*/
|
|
18432
|
+
mode = "test";
|
|
18433
|
+
constructor(modeOrCliOptions, cliOptionsOrOptions, maybeOptions) {
|
|
18434
|
+
let cliOptions;
|
|
18435
|
+
let options;
|
|
18436
|
+
if (typeof modeOrCliOptions === "string") {
|
|
18437
|
+
cliOptions = cliOptionsOrOptions;
|
|
18438
|
+
options = maybeOptions ?? {};
|
|
18439
|
+
} else {
|
|
18440
|
+
cliOptions = modeOrCliOptions;
|
|
18441
|
+
options = cliOptionsOrOptions ?? {};
|
|
18442
|
+
}
|
|
18457
18443
|
this._cliOptions = cliOptions;
|
|
18458
18444
|
this.logger = new Logger(this, options.stdout, options.stderr);
|
|
18459
18445
|
this.packageInstaller = options.packageInstaller || new VitestPackageInstaller();
|
|
@@ -18571,9 +18557,9 @@ class Vitest {
|
|
|
18571
18557
|
try {
|
|
18572
18558
|
await this.cache.results.readFromCache();
|
|
18573
18559
|
} catch {}
|
|
18574
|
-
|
|
18575
|
-
this.projects = projects;
|
|
18576
|
-
await Promise.all(projects.flatMap((project) => {
|
|
18560
|
+
this.projects = await this.resolveProjects(this._cliOptions);
|
|
18561
|
+
if (this._cliOptions.benchmarkOnly) this.projects = this.projects.filter((c) => c.config.benchmark.enabled);
|
|
18562
|
+
await Promise.all(this.projects.flatMap((project) => {
|
|
18577
18563
|
return project.vite.config.getSortedPluginHooks("configureVitest").map((hook) => hook({
|
|
18578
18564
|
project,
|
|
18579
18565
|
vitest: this,
|
|
@@ -18598,7 +18584,7 @@ class Vitest {
|
|
|
18598
18584
|
// populate will merge all configs into every project,
|
|
18599
18585
|
// we don't want that when just listing tags
|
|
18600
18586
|
if (!this.config.listTags) populateProjectsTags(this.coreWorkspaceProject, this.projects);
|
|
18601
|
-
this.reporters =
|
|
18587
|
+
this.reporters = await createReporters(resolved.reporters, this);
|
|
18602
18588
|
await this._fsCache.ensureCacheIntegrity();
|
|
18603
18589
|
await Promise.all([...this._onSetServer.map((fn) => fn()), this._traces.waitInit()]);
|
|
18604
18590
|
}
|
|
@@ -18725,7 +18711,7 @@ class Vitest {
|
|
|
18725
18711
|
// returns the project only if it matches the filter
|
|
18726
18712
|
const project = getDefaultTestProject(this);
|
|
18727
18713
|
if (!project) return [];
|
|
18728
|
-
return
|
|
18714
|
+
return resolveDefaultProjects(this, new Set([project.name]), [project]);
|
|
18729
18715
|
}
|
|
18730
18716
|
/**
|
|
18731
18717
|
* Glob test files in every project and create a TestSpecification for each file and pool.
|
|
@@ -18767,7 +18753,7 @@ class Vitest {
|
|
|
18767
18753
|
await this.report("onInit", this);
|
|
18768
18754
|
const specifications = [];
|
|
18769
18755
|
for (const file of files) {
|
|
18770
|
-
const specification = this.getProjectByName(file.projectName || "").createSpecification(file.filepath, void 0, file.pool, file.
|
|
18756
|
+
const specification = this.getProjectByName(file.projectName || "").createSpecification(file.filepath, void 0, file.pool, file.id);
|
|
18771
18757
|
specifications.push(specification);
|
|
18772
18758
|
}
|
|
18773
18759
|
await this._testRun.start(specifications);
|
|
@@ -18891,7 +18877,7 @@ class Vitest {
|
|
|
18891
18877
|
// Report coverage for uncovered files
|
|
18892
18878
|
await this.reportCoverage(coverage, true);
|
|
18893
18879
|
});
|
|
18894
|
-
if (!this.config.watch || !(this.config.changed || this.config.related?.length)) throw new FilesNotFoundError(
|
|
18880
|
+
if (!this.config.watch || !(this.config.changed || this.config.related?.length)) throw new FilesNotFoundError();
|
|
18895
18881
|
}
|
|
18896
18882
|
let testModules = {
|
|
18897
18883
|
testModules: [],
|
|
@@ -19075,7 +19061,6 @@ class Vitest {
|
|
|
19075
19061
|
}));
|
|
19076
19062
|
}
|
|
19077
19063
|
async experimental_parseSpecifications(specifications, options) {
|
|
19078
|
-
if (this.mode !== "test") throw new Error(`The \`experimental_parseSpecifications\` does not support "${this.mode}" mode.`);
|
|
19079
19064
|
const limit = limitConcurrency(options?.concurrency ?? (typeof nodeos__default.availableParallelism === "function" ? nodeos__default.availableParallelism() : nodeos__default.cpus().length));
|
|
19080
19065
|
// Phase 1: parse all files in parallel (without mode interpretation)
|
|
19081
19066
|
const results = await Promise.all(specifications.map((specification) => limit(async () => {
|
|
@@ -19097,7 +19082,6 @@ class Vitest {
|
|
|
19097
19082
|
return results.map(({ file }) => this.state.getReportedEntity(file));
|
|
19098
19083
|
}
|
|
19099
19084
|
async experimental_parseSpecification(specification) {
|
|
19100
|
-
if (this.mode !== "test") throw new Error(`The \`experimental_parseSpecification\` does not support "${this.mode}" mode.`);
|
|
19101
19085
|
const file = await astCollectTests(specification.project, specification.moduleId).catch((error) => {
|
|
19102
19086
|
return createFailedFileTask(specification.project, specification.moduleId, error);
|
|
19103
19087
|
});
|
|
@@ -19493,7 +19477,7 @@ function assert(condition, property, name = property) {
|
|
|
19493
19477
|
if (!condition) throw new Error(`The ${name} was not set. It means that \`vitest.${property}\` was called before the Vite server was established. Await the Vitest promise before accessing \`vitest.${property}\`.`);
|
|
19494
19478
|
}
|
|
19495
19479
|
|
|
19496
|
-
async function VitestPlugin(options = {}, vitest = new Vitest(
|
|
19480
|
+
async function VitestPlugin(options = {}, vitest = new Vitest(deepClone(options))) {
|
|
19497
19481
|
const userConfig = deepMerge({}, options);
|
|
19498
19482
|
async function UIPlugin() {
|
|
19499
19483
|
await vitest.packageInstaller.ensureInstalled("@vitest/ui", resolve$1(options.root || process.cwd()), vitest.version);
|
|
@@ -19566,6 +19550,10 @@ async function VitestPlugin(options = {}, vitest = new Vitest("test", deepClone(
|
|
|
19566
19550
|
legalComments: "inline"
|
|
19567
19551
|
}
|
|
19568
19552
|
};
|
|
19553
|
+
if (vitest._cliOptions.benchmarkOnly) {
|
|
19554
|
+
config.test.benchmark ??= {};
|
|
19555
|
+
config.test.benchmark.enabled = true;
|
|
19556
|
+
}
|
|
19569
19557
|
// inherit so it's available in VitestOptimizer
|
|
19570
19558
|
// I cannot wait to rewrite all of this in Vitest 4
|
|
19571
19559
|
if (options.cache != null) config.test.cache = options.cache;
|
|
@@ -19635,8 +19623,20 @@ async function VitestPlugin(options = {}, vitest = new Vitest("test", deepClone(
|
|
|
19635
19623
|
].filter(notNullish);
|
|
19636
19624
|
}
|
|
19637
19625
|
|
|
19638
|
-
async function createVitest(
|
|
19639
|
-
|
|
19626
|
+
async function createVitest(modeOrOptions, optionsOrViteOverrides = {}, viteOverridesOrVitestOptions = {}, maybeVitestOptions = {}) {
|
|
19627
|
+
let options;
|
|
19628
|
+
let viteOverrides;
|
|
19629
|
+
let vitestOptions;
|
|
19630
|
+
if (typeof modeOrOptions === "string") {
|
|
19631
|
+
options = optionsOrViteOverrides;
|
|
19632
|
+
viteOverrides = viteOverridesOrVitestOptions;
|
|
19633
|
+
vitestOptions = maybeVitestOptions;
|
|
19634
|
+
} else {
|
|
19635
|
+
options = modeOrOptions;
|
|
19636
|
+
viteOverrides = optionsOrViteOverrides;
|
|
19637
|
+
vitestOptions = viteOverridesOrVitestOptions;
|
|
19638
|
+
}
|
|
19639
|
+
const ctx = new Vitest(deepClone(options), vitestOptions);
|
|
19640
19640
|
const root = slash(resolve$2(options.root || process.cwd()));
|
|
19641
19641
|
const configPath = options.config === false ? false : options.config ? resolveModule(options.config, { paths: [root] }) ?? resolve$2(root, options.config) : any(configFiles, { cwd: root });
|
|
19642
19642
|
options.config = configPath;
|
|
@@ -19644,7 +19644,7 @@ async function createVitest(mode, options, viteOverrides = {}, vitestOptions = {
|
|
|
19644
19644
|
const config = {
|
|
19645
19645
|
configFile: configPath,
|
|
19646
19646
|
configLoader: options.configLoader,
|
|
19647
|
-
mode: options.mode ||
|
|
19647
|
+
mode: options.mode || "test",
|
|
19648
19648
|
plugins: await VitestPlugin(restOptions, ctx)
|
|
19649
19649
|
};
|
|
19650
19650
|
try {
|
|
@@ -19951,15 +19951,25 @@ function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
|
|
|
19951
19951
|
};
|
|
19952
19952
|
}
|
|
19953
19953
|
|
|
19954
|
-
|
|
19955
|
-
|
|
19956
|
-
|
|
19957
|
-
|
|
19958
|
-
|
|
19959
|
-
|
|
19954
|
+
async function startVitest(modeOrCliFilters, cliFiltersOrOptions, optionsOrViteOverrides, viteOverridesOrVitestOptions, maybeVitestOptions) {
|
|
19955
|
+
let cliFilters;
|
|
19956
|
+
let options;
|
|
19957
|
+
let viteOverrides;
|
|
19958
|
+
let vitestOptions;
|
|
19959
|
+
if (typeof modeOrCliFilters === "string") {
|
|
19960
|
+
cliFilters = cliFiltersOrOptions ?? [];
|
|
19961
|
+
options = optionsOrViteOverrides ?? {};
|
|
19962
|
+
viteOverrides = viteOverridesOrVitestOptions;
|
|
19963
|
+
vitestOptions = maybeVitestOptions;
|
|
19964
|
+
} else {
|
|
19965
|
+
cliFilters = modeOrCliFilters ?? [];
|
|
19966
|
+
options = cliFiltersOrOptions ?? {};
|
|
19967
|
+
viteOverrides = optionsOrViteOverrides;
|
|
19968
|
+
vitestOptions = viteOverridesOrVitestOptions;
|
|
19969
|
+
}
|
|
19960
19970
|
const root = resolve$1(options.root || process.cwd());
|
|
19961
|
-
const ctx = await prepareVitest(
|
|
19962
|
-
if (
|
|
19971
|
+
const ctx = await prepareVitest(options, viteOverrides, vitestOptions, cliFilters);
|
|
19972
|
+
if (ctx._coverageOptions.enabled) {
|
|
19963
19973
|
const requiredPackages = CoverageProviderMap[ctx._coverageOptions.provider || "v8"];
|
|
19964
19974
|
if (requiredPackages) {
|
|
19965
19975
|
if (!await ctx.packageInstaller.ensureInstalled(requiredPackages, root, ctx.version)) {
|
|
@@ -20007,7 +20017,22 @@ async function startVitest(mode, cliFilters = [], options = {}, viteOverrides, v
|
|
|
20007
20017
|
}
|
|
20008
20018
|
}
|
|
20009
20019
|
}
|
|
20010
|
-
async function prepareVitest(
|
|
20020
|
+
async function prepareVitest(modeOrOptions, optionsOrViteOverrides, viteOverridesOrVitestOptions, vitestOptionsOrCliFilters, maybeCliFilters) {
|
|
20021
|
+
let options;
|
|
20022
|
+
let viteOverrides;
|
|
20023
|
+
let vitestOptions;
|
|
20024
|
+
let cliFilters;
|
|
20025
|
+
if (typeof modeOrOptions === "string") {
|
|
20026
|
+
options = optionsOrViteOverrides ?? {};
|
|
20027
|
+
viteOverrides = viteOverridesOrVitestOptions;
|
|
20028
|
+
vitestOptions = vitestOptionsOrCliFilters;
|
|
20029
|
+
cliFilters = maybeCliFilters;
|
|
20030
|
+
} else {
|
|
20031
|
+
options = modeOrOptions ?? {};
|
|
20032
|
+
viteOverrides = optionsOrViteOverrides;
|
|
20033
|
+
vitestOptions = viteOverridesOrVitestOptions;
|
|
20034
|
+
cliFilters = vitestOptionsOrCliFilters;
|
|
20035
|
+
}
|
|
20011
20036
|
process.env.TEST = "true";
|
|
20012
20037
|
process.env.VITEST = "true";
|
|
20013
20038
|
process.env.NODE_ENV ??= "test";
|
|
@@ -20015,7 +20040,7 @@ async function prepareVitest(mode, options = {}, viteOverrides, vitestOptions, c
|
|
|
20015
20040
|
if (options.standalone && (cliFilters?.length || 0) > 0) options.standalone = false;
|
|
20016
20041
|
// this shouldn't affect _application root_ that can be changed inside config
|
|
20017
20042
|
const root = resolve$1(options.root || process.cwd());
|
|
20018
|
-
const ctx = await createVitest(
|
|
20043
|
+
const ctx = await createVitest(options, viteOverrides, vitestOptions);
|
|
20019
20044
|
const environmentPackage = getEnvPackageName(ctx.config.environment);
|
|
20020
20045
|
if (environmentPackage && !await ctx.packageInstaller.ensureInstalled(environmentPackage, root)) {
|
|
20021
20046
|
process.exitCode = 1;
|
|
@@ -20124,4 +20149,4 @@ var cliApi = /*#__PURE__*/Object.freeze({
|
|
|
20124
20149
|
startVitest: startVitest
|
|
20125
20150
|
});
|
|
20126
20151
|
|
|
20127
|
-
export {
|
|
20152
|
+
export { startVitest as A, BaseCoverageProvider as B, cliApi as C, 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, BaseSequencer as b, DotReporter as c, GithubActionsReporter as d, JsonReporter as e, TapReporter as f, FilesNotFoundError as g, ThreadsPoolWorker as h, TypecheckPoolWorker as i, VerboseReporter as j, VitestPackageInstaller as k, VmForksPoolWorker as l, VmThreadsPoolWorker as m, createDebugger as n, createMethodsRPC as o, createViteLogger as p, createVitest as q, resolveConfig$1 as r, escapeTestName as s, experimental_getRunnerTask as t, getFilePoolName as u, isFileServingAllowed as v, isValidApiRequest as w, registerConsoleShortcuts as x, resolveApiServerConfig as y, resolveFsAllow as z };
|