vitest 5.0.0-beta.3 → 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.Bay6B1Dz.js → base.BEGVMQrS.js} +6 -6
- package/dist/chunks/{browser.d.DM1g8UNp.d.ts → browser.d.BGxB4Xum.d.ts} +4 -25
- package/dist/chunks/{cac.DoK9yX-i.js → cac.CyXAEMkE.js} +22 -31
- package/dist/chunks/{cli-api.BCY9ylNq.js → cli-api.DJMXq34b.js} +502 -549
- package/dist/chunks/{config.d.C0UMwus7.d.ts → config.d.DXq1aBpy.d.ts} +7 -26
- package/dist/chunks/{creator.BqL2U_x4.js → creator.D66cVXYh.js} +2 -2
- package/dist/chunks/{defaults.DVfzlTkU.js → defaults.CUUnbOrq.js} +5 -3
- package/dist/chunks/global.d.BtKPuz2X.d.ts +194 -0
- package/dist/chunks/{globals.8_qjZdeE.js → globals.BuY-yD0m.js} +2 -1
- package/dist/chunks/{index.ukHtlBbI.js → index.CE58PZNH.js} +355 -146
- package/dist/chunks/{index.PuMGMNHF.js → index.CcluKS59.js} +4 -4
- package/dist/chunks/{index.CbgUM9E5.js → index.nQFVd50u.js} +2 -1
- package/dist/chunks/{init-forks.OoZmDo1g.js → init-forks.Ce3vGWgL.js} +1 -1
- package/dist/chunks/{init-threads.eSHAowcx.js → init-threads.8e1OLv5v.js} +1 -1
- package/dist/chunks/{init.YjNsCb-_.js → init.6qx-LaHs.js} +30 -2
- package/dist/chunks/{nativeModuleMocker.DKpFw0pk.js → nativeModuleMocker.DDZfQXLs.js} +1 -1
- package/dist/chunks/{plugin.d.C00LxKL6.d.ts → plugin.d.B7MTG_Fe.d.ts} +71 -82
- package/dist/chunks/{rpc.d.7JZuxZ8u.d.ts → rpc.d.OQ_EZi1Z.d.ts} +18 -2
- package/dist/chunks/{setup-common.eQsbxe88.js → setup-common.DdEF_hkE.js} +1 -1
- package/dist/chunks/{vm.BE_VOfSs.js → vm.Bu7mmcZq.js} +2 -2
- package/dist/chunks/{worker.d.Dv3hDCFf.d.ts → worker.d.yR22cs6X.d.ts} +3 -2
- package/dist/cli.js +1 -1
- package/dist/config.cjs +1 -1
- package/dist/config.d.ts +6 -8
- package/dist/config.js +1 -1
- package/dist/index.d.ts +23 -40
- package/dist/index.js +2 -1
- package/dist/module-evaluator.d.ts +4 -1
- package/dist/module-evaluator.js +14 -19
- package/dist/node.d.ts +20 -12
- package/dist/node.js +5 -5
- 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 +15 -15
- package/dist/chunks/global.d.DZbA5YnY.d.ts +0 -101
|
@@ -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,7 +22,7 @@ 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.
|
|
25
|
+
import { c as configDefaults, e as benchmarkConfigDefaults, a as coverageConfigDefaults } from './defaults.CUUnbOrq.js';
|
|
27
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';
|
|
@@ -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(() => ""));
|
|
@@ -2261,8 +2283,10 @@ function resolveInlineWorkerOption(value) {
|
|
|
2261
2283
|
if (typeof value === "string" && value.trim().endsWith("%")) return getWorkersCountByPercentage(value);
|
|
2262
2284
|
else return Number(value);
|
|
2263
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;
|
|
2264
2289
|
function resolveConfig$1(vitest, options, viteConfig) {
|
|
2265
|
-
const mode = vitest.mode;
|
|
2266
2290
|
const logger = vitest.logger;
|
|
2267
2291
|
if (options.dom) {
|
|
2268
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}"`));
|
|
@@ -2271,9 +2295,9 @@ function resolveConfig$1(vitest, options, viteConfig) {
|
|
|
2271
2295
|
const resolved = {
|
|
2272
2296
|
...configDefaults,
|
|
2273
2297
|
...options,
|
|
2274
|
-
root: viteConfig.root
|
|
2275
|
-
mode
|
|
2298
|
+
root: viteConfig.root
|
|
2276
2299
|
};
|
|
2300
|
+
resolved.mode ??= viteConfig.mode ?? "test";
|
|
2277
2301
|
if (resolved.retry && typeof resolved.retry === "object" && typeof resolved.retry.condition === "function") {
|
|
2278
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."));
|
|
2279
2303
|
resolved.retry = {
|
|
@@ -2305,6 +2329,10 @@ function resolveConfig$1(vitest, options, viteConfig) {
|
|
|
2305
2329
|
resolved.name = typeof options.name === "string" ? options.name : options.name?.label || "";
|
|
2306
2330
|
resolved.color = typeof options.name !== "string" ? options.name?.color : void 0;
|
|
2307
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
|
+
};
|
|
2308
2336
|
const inspector = resolved.inspect || resolved.inspectBrk;
|
|
2309
2337
|
resolved.inspector = {
|
|
2310
2338
|
...resolved.inspector,
|
|
@@ -2329,7 +2357,7 @@ function resolveConfig$1(vitest, options, viteConfig) {
|
|
|
2329
2357
|
if (resolved.standalone && !resolved.watch) throw new Error(`Vitest standalone mode requires --watch`);
|
|
2330
2358
|
if (resolved.mergeReports && resolved.watch) throw new Error(`Cannot merge reports with --watch enabled`);
|
|
2331
2359
|
if (resolved.maxWorkers) resolved.maxWorkers = resolveInlineWorkerOption(resolved.maxWorkers);
|
|
2332
|
-
if (!(options.fileParallelism ??
|
|
2360
|
+
if (!(options.fileParallelism ?? true))
|
|
2333
2361
|
// ignore user config, parallelism cannot be implemented without limiting workers
|
|
2334
2362
|
resolved.maxWorkers = 1;
|
|
2335
2363
|
if (resolved.maxConcurrency === 0) {
|
|
@@ -2492,25 +2520,6 @@ function resolveConfig$1(vitest, options, viteConfig) {
|
|
|
2492
2520
|
resolved.pool ??= "threads";
|
|
2493
2521
|
if (resolved.pool === "vmForks" || resolved.pool === "vmThreads" || resolved.pool === "typescript") resolved.isolate = false;
|
|
2494
2522
|
if (process.env.VITEST_MAX_WORKERS) resolved.maxWorkers = Number.parseInt(process.env.VITEST_MAX_WORKERS);
|
|
2495
|
-
if (mode === "benchmark") {
|
|
2496
|
-
resolved.benchmark = {
|
|
2497
|
-
...benchmarkConfigDefaults,
|
|
2498
|
-
...resolved.benchmark
|
|
2499
|
-
};
|
|
2500
|
-
// override test config
|
|
2501
|
-
resolved.coverage.enabled = false;
|
|
2502
|
-
resolved.typecheck.enabled = false;
|
|
2503
|
-
resolved.include = resolved.benchmark.include;
|
|
2504
|
-
resolved.exclude = resolved.benchmark.exclude;
|
|
2505
|
-
resolved.includeSource = resolved.benchmark.includeSource;
|
|
2506
|
-
const reporters = Array.from(new Set([...toArray(resolved.benchmark.reporters), ...toArray(options.reporter)])).filter(Boolean);
|
|
2507
|
-
if (reporters.length) resolved.benchmark.reporters = reporters;
|
|
2508
|
-
else resolved.benchmark.reporters = ["default"];
|
|
2509
|
-
if (options.outputFile) resolved.benchmark.outputFile = options.outputFile;
|
|
2510
|
-
// --compare from cli
|
|
2511
|
-
if (options.compare) resolved.benchmark.compare = options.compare;
|
|
2512
|
-
if (options.outputJson) resolved.benchmark.outputJson = options.outputJson;
|
|
2513
|
-
}
|
|
2514
2523
|
if (typeof resolved.diff === "string") {
|
|
2515
2524
|
resolved.diff = resolvePath(resolved.diff, resolved.root);
|
|
2516
2525
|
resolved.forceRerunTriggers.push(resolved.diff);
|
|
@@ -2546,25 +2555,23 @@ function resolveConfig$1(vitest, options, viteConfig) {
|
|
|
2546
2555
|
// Inline reporter, e.g. { reporter: [{ onFinish() { method() } }] }
|
|
2547
2556
|
resolved.reporters.push(reporter);
|
|
2548
2557
|
}
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
if (typeof reporterName === "string") configReportersMap.set(reporterName, reporterOptions);
|
|
2565
|
-
}
|
|
2566
|
-
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);
|
|
2567
2573
|
}
|
|
2574
|
+
resolved.reporters = Array.from(new Set(toArray(cliReporters))).filter(Boolean).map((reporter) => [reporter, configReportersMap.get(reporter) || {}]);
|
|
2568
2575
|
}
|
|
2569
2576
|
resolved.mergeReportsLabel = process.env.VITEST_BLOB_LABEL;
|
|
2570
2577
|
for (const reporter of resolved.reporters) if (Array.isArray(reporter) && reporter[0] === "blob") {
|
|
@@ -2600,12 +2607,15 @@ function resolveConfig$1(vitest, options, viteConfig) {
|
|
|
2600
2607
|
};
|
|
2601
2608
|
resolved.typecheck ??= {};
|
|
2602
2609
|
resolved.typecheck.enabled ??= false;
|
|
2603
|
-
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
|
+
}
|
|
2604
2614
|
resolved.browser.enabled ??= false;
|
|
2605
2615
|
resolved.browser.headless ??= isCI;
|
|
2606
2616
|
if (resolved.browser.isolate) logger.console.warn(c.yellow("`browser.isolate` is deprecated. Use top-level `isolate` instead."));
|
|
2607
2617
|
resolved.browser.isolate ??= resolved.isolate ?? true;
|
|
2608
|
-
resolved.browser.fileParallelism ??= options.fileParallelism ??
|
|
2618
|
+
resolved.browser.fileParallelism ??= options.fileParallelism ?? true;
|
|
2609
2619
|
// disable in headless mode by default, and if CI is detected
|
|
2610
2620
|
resolved.browser.ui ??= resolved.browser.headless === true ? false : !isCI;
|
|
2611
2621
|
resolved.browser.commands ??= {};
|
|
@@ -2617,7 +2627,7 @@ function resolveConfig$1(vitest, options, viteConfig) {
|
|
|
2617
2627
|
resolved.browser.viewport.height ??= 896;
|
|
2618
2628
|
resolved.browser.locators ??= {};
|
|
2619
2629
|
resolved.browser.locators.testIdAttribute ??= "data-testid";
|
|
2620
|
-
resolved.browser.locators.exact ??=
|
|
2630
|
+
resolved.browser.locators.exact ??= true;
|
|
2621
2631
|
resolved.browser.locators.errorFormat ??= "all";
|
|
2622
2632
|
if (typeof resolved.browser.provider === "string") {
|
|
2623
2633
|
const source = `@vitest/browser-${resolved.browser.provider}`;
|
|
@@ -3569,8 +3579,8 @@ class ServerModuleRunner extends ModuleRunner {
|
|
|
3569
3579
|
|
|
3570
3580
|
class FilesNotFoundError extends Error {
|
|
3571
3581
|
code = "VITEST_FILES_NOT_FOUND";
|
|
3572
|
-
constructor(
|
|
3573
|
-
super(`No
|
|
3582
|
+
constructor() {
|
|
3583
|
+
super(`No test files found`);
|
|
3574
3584
|
}
|
|
3575
3585
|
}
|
|
3576
3586
|
class GitNotFoundError extends Error {
|
|
@@ -4148,144 +4158,6 @@ function getDefs(c) {
|
|
|
4148
4158
|
};
|
|
4149
4159
|
}
|
|
4150
4160
|
|
|
4151
|
-
async function collectTests(ctx, filepath) {
|
|
4152
|
-
const request = await ctx.vite.environments.ssr.transformRequest(filepath);
|
|
4153
|
-
if (!request) return null;
|
|
4154
|
-
const ast = await parseAstAsync(request.code);
|
|
4155
|
-
const projectName = ctx.name;
|
|
4156
|
-
const file = {
|
|
4157
|
-
...createFileTask$1(filepath, ctx.config.root, projectName, void 0, void 0, { typecheck: true }),
|
|
4158
|
-
start: ast.start,
|
|
4159
|
-
end: ast.end,
|
|
4160
|
-
mode: "run"
|
|
4161
|
-
};
|
|
4162
|
-
file.file = file;
|
|
4163
|
-
const definitions = [];
|
|
4164
|
-
const getName = (callee) => {
|
|
4165
|
-
if (!callee) return null;
|
|
4166
|
-
if (callee.type === "Identifier") return callee.name;
|
|
4167
|
-
if (callee.type === "CallExpression") return getName(callee.callee);
|
|
4168
|
-
if (callee.type === "TaggedTemplateExpression") return getName(callee.tag);
|
|
4169
|
-
if (callee.type === "MemberExpression") {
|
|
4170
|
-
if (callee.object?.type === "Identifier" && [
|
|
4171
|
-
"it",
|
|
4172
|
-
"test",
|
|
4173
|
-
"describe",
|
|
4174
|
-
"suite"
|
|
4175
|
-
].includes(callee.object.name)) return callee.object?.name;
|
|
4176
|
-
// direct call as `__vite_ssr_exports_0__.test()`
|
|
4177
|
-
if (callee.object?.name?.startsWith("__vite_ssr_")) return getName(callee.property);
|
|
4178
|
-
// call as `__vite_ssr__.test.skip()`
|
|
4179
|
-
return getName(callee.object?.property);
|
|
4180
|
-
}
|
|
4181
|
-
// unwrap (0, ...)
|
|
4182
|
-
if (callee.type === "SequenceExpression" && callee.expressions.length === 2) {
|
|
4183
|
-
const [e0, e1] = callee.expressions;
|
|
4184
|
-
if (e0.type === "Literal" && e0.value === 0) return getName(e1);
|
|
4185
|
-
}
|
|
4186
|
-
return null;
|
|
4187
|
-
};
|
|
4188
|
-
ancestor(ast, { CallExpression(node) {
|
|
4189
|
-
const { callee } = node;
|
|
4190
|
-
const name = getName(callee);
|
|
4191
|
-
if (!name) return;
|
|
4192
|
-
if (![
|
|
4193
|
-
"it",
|
|
4194
|
-
"test",
|
|
4195
|
-
"describe",
|
|
4196
|
-
"suite"
|
|
4197
|
-
].includes(name)) return;
|
|
4198
|
-
const property = callee?.property?.name;
|
|
4199
|
-
let mode = !property || property === name ? "run" : property;
|
|
4200
|
-
// they will be picked up in the next iteration
|
|
4201
|
-
if ([
|
|
4202
|
-
"each",
|
|
4203
|
-
"for",
|
|
4204
|
-
"skipIf",
|
|
4205
|
-
"runIf"
|
|
4206
|
-
].includes(mode)) return;
|
|
4207
|
-
let start;
|
|
4208
|
-
const end = node.end;
|
|
4209
|
-
// .each
|
|
4210
|
-
if (callee.type === "CallExpression") start = callee.end;
|
|
4211
|
-
else if (callee.type === "TaggedTemplateExpression") start = callee.end + 1;
|
|
4212
|
-
else start = node.start;
|
|
4213
|
-
const { arguments: [messageNode] } = node;
|
|
4214
|
-
const message = messageNode?.type === "Literal" || messageNode?.type === "TemplateLiteral" ? request.code.slice(messageNode.start + 1, messageNode.end - 1) : request.code.slice(messageNode.start, messageNode.end);
|
|
4215
|
-
// cannot statically analyze, so we always skip it
|
|
4216
|
-
if (mode === "skipIf" || mode === "runIf") mode = "skip";
|
|
4217
|
-
definitions.push({
|
|
4218
|
-
start,
|
|
4219
|
-
end,
|
|
4220
|
-
name: message,
|
|
4221
|
-
type: name === "it" || name === "test" ? "test" : "suite",
|
|
4222
|
-
mode,
|
|
4223
|
-
task: null
|
|
4224
|
-
});
|
|
4225
|
-
} });
|
|
4226
|
-
let lastSuite = file;
|
|
4227
|
-
const updateLatestSuite = (index) => {
|
|
4228
|
-
while (lastSuite.suite && lastSuite.end < index) lastSuite = lastSuite.suite;
|
|
4229
|
-
return lastSuite;
|
|
4230
|
-
};
|
|
4231
|
-
definitions.sort((a, b) => a.start - b.start).forEach((definition) => {
|
|
4232
|
-
const latestSuite = updateLatestSuite(definition.start);
|
|
4233
|
-
let mode = definition.mode;
|
|
4234
|
-
if (latestSuite.mode !== "run")
|
|
4235
|
-
// inherit suite mode, if it's set
|
|
4236
|
-
mode = latestSuite.mode;
|
|
4237
|
-
if (definition.type === "suite") {
|
|
4238
|
-
const task = {
|
|
4239
|
-
type: definition.type,
|
|
4240
|
-
id: "",
|
|
4241
|
-
suite: latestSuite,
|
|
4242
|
-
file,
|
|
4243
|
-
tasks: [],
|
|
4244
|
-
mode,
|
|
4245
|
-
name: definition.name,
|
|
4246
|
-
fullName: createTaskName([lastSuite.fullName, definition.name]),
|
|
4247
|
-
fullTestName: createTaskName([lastSuite.fullTestName, definition.name]),
|
|
4248
|
-
end: definition.end,
|
|
4249
|
-
start: definition.start,
|
|
4250
|
-
meta: { typecheck: true }
|
|
4251
|
-
};
|
|
4252
|
-
definition.task = task;
|
|
4253
|
-
latestSuite.tasks.push(task);
|
|
4254
|
-
lastSuite = task;
|
|
4255
|
-
return;
|
|
4256
|
-
}
|
|
4257
|
-
const task = {
|
|
4258
|
-
type: definition.type,
|
|
4259
|
-
id: "",
|
|
4260
|
-
suite: latestSuite,
|
|
4261
|
-
file,
|
|
4262
|
-
mode,
|
|
4263
|
-
timeout: 0,
|
|
4264
|
-
context: {},
|
|
4265
|
-
name: definition.name,
|
|
4266
|
-
fullName: createTaskName([lastSuite.fullName, definition.name]),
|
|
4267
|
-
fullTestName: createTaskName([lastSuite.fullTestName, definition.name]),
|
|
4268
|
-
end: definition.end,
|
|
4269
|
-
start: definition.start,
|
|
4270
|
-
annotations: [],
|
|
4271
|
-
artifacts: [],
|
|
4272
|
-
meta: { typecheck: true }
|
|
4273
|
-
};
|
|
4274
|
-
definition.task = task;
|
|
4275
|
-
latestSuite.tasks.push(task);
|
|
4276
|
-
});
|
|
4277
|
-
calculateSuiteHash(file);
|
|
4278
|
-
const hasOnly = someTasksAreOnly(file);
|
|
4279
|
-
interpretTaskModes(file, ctx.config.testNamePattern, void 0, void 0, void 0, hasOnly, false, ctx.config.allowOnly);
|
|
4280
|
-
return {
|
|
4281
|
-
file,
|
|
4282
|
-
parsed: request.code,
|
|
4283
|
-
filepath,
|
|
4284
|
-
map: request.map,
|
|
4285
|
-
definitions
|
|
4286
|
-
};
|
|
4287
|
-
}
|
|
4288
|
-
|
|
4289
4161
|
const newLineRegExp = /\r?\n/;
|
|
4290
4162
|
const errCodeRegExp = /error TS(?<errCode>\d+)/;
|
|
4291
4163
|
async function makeTscErrorInfo(errInfo) {
|
|
@@ -4366,7 +4238,7 @@ class Typechecker {
|
|
|
4366
4238
|
this._onWatcherRerun = fn;
|
|
4367
4239
|
}
|
|
4368
4240
|
async collectFileTests(filepath) {
|
|
4369
|
-
return
|
|
4241
|
+
return astCollectFileInformation(this.project, filepath, { pool: "typescript" });
|
|
4370
4242
|
}
|
|
4371
4243
|
getFiles() {
|
|
4372
4244
|
return this.files;
|
|
@@ -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,
|
|
@@ -12354,7 +12263,12 @@ function serializeConfig(project) {
|
|
|
12354
12263
|
})(config.browser),
|
|
12355
12264
|
standalone: config.standalone,
|
|
12356
12265
|
printConsoleTrace: config.printConsoleTrace ?? globalConfig.printConsoleTrace,
|
|
12357
|
-
benchmark:
|
|
12266
|
+
benchmark: {
|
|
12267
|
+
enabled: config.benchmark.enabled,
|
|
12268
|
+
retainSamples: config.benchmark.retainSamples,
|
|
12269
|
+
suppressExportGetterWarnings: config.benchmark.suppressExportGetterWarnings,
|
|
12270
|
+
projectName: config.benchmark.projectName
|
|
12271
|
+
},
|
|
12358
12272
|
serializedDefines: config.browser.enabled ? "" : project._serializedDefines || "",
|
|
12359
12273
|
experimental: {
|
|
12360
12274
|
fsModuleCache: config.experimental.fsModuleCache ?? false,
|
|
@@ -13365,7 +13279,8 @@ function WorkspaceVitestPlugin(project, options) {
|
|
|
13365
13279
|
name: "vitest:project:name",
|
|
13366
13280
|
enforce: "post",
|
|
13367
13281
|
config(viteConfig) {
|
|
13368
|
-
|
|
13282
|
+
viteConfig.test ??= {};
|
|
13283
|
+
const testConfig = viteConfig.test;
|
|
13369
13284
|
let { label: name, color } = typeof testConfig.name === "string" ? { label: testConfig.name } : {
|
|
13370
13285
|
label: "",
|
|
13371
13286
|
...testConfig.name
|
|
@@ -13377,6 +13292,10 @@ function WorkspaceVitestPlugin(project, options) {
|
|
|
13377
13292
|
if (existsSync(pkgJsonPath)) name = JSON.parse(readFileSync(pkgJsonPath, "utf-8")).name;
|
|
13378
13293
|
if (typeof name !== "string" || !name) name = basename(dir);
|
|
13379
13294
|
} else name = options.workspacePath.toString();
|
|
13295
|
+
if (project.vitest._cliOptions.benchmarkOnly) {
|
|
13296
|
+
viteConfig.test.benchmark ??= {};
|
|
13297
|
+
viteConfig.test.benchmark.enabled = true;
|
|
13298
|
+
}
|
|
13380
13299
|
const isBrowserEnabled = viteConfig.test?.browser?.enabled ?? (viteConfig.test?.browser && project.vitest._cliOptions.browser?.enabled);
|
|
13381
13300
|
// keep project names to potentially filter it out
|
|
13382
13301
|
const workspaceNames = [name];
|
|
@@ -13389,6 +13308,7 @@ function WorkspaceVitestPlugin(project, options) {
|
|
|
13389
13308
|
instance.name ??= name ? `${name} (${instance.browser})` : instance.browser;
|
|
13390
13309
|
if (isBrowserEnabled) workspaceNames.push(instance.name);
|
|
13391
13310
|
});
|
|
13311
|
+
if (viteConfig.test?.benchmark?.enabled) workspaceNames.push(name ? `${name} (bench)` : "bench");
|
|
13392
13312
|
// if there is `--project=...` filter, check if any of the potential projects match
|
|
13393
13313
|
// if projects don't match, we ignore the test project altogether
|
|
13394
13314
|
// if some of them match, they will later be filtered again by `resolveWorkspace`
|
|
@@ -13721,6 +13641,7 @@ class TestProject {
|
|
|
13721
13641
|
* Temporary directory for the project. This is unique for each project. Vitest stores transformed content here.
|
|
13722
13642
|
*/
|
|
13723
13643
|
tmpDir;
|
|
13644
|
+
benchmark = new BenchmarkManager(this);
|
|
13724
13645
|
/** @internal */ typechecker;
|
|
13725
13646
|
/** @internal */ _config;
|
|
13726
13647
|
/** @internal */ _vite;
|
|
@@ -14126,7 +14047,7 @@ class TestProject {
|
|
|
14126
14047
|
return project;
|
|
14127
14048
|
}
|
|
14128
14049
|
/** @internal */
|
|
14129
|
-
static
|
|
14050
|
+
static _cloneTestProject(parent, config) {
|
|
14130
14051
|
const clone = new TestProject(parent.vitest, void 0, parent.tmpDir);
|
|
14131
14052
|
clone.runner = parent.runner;
|
|
14132
14053
|
clone._vite = parent._vite;
|
|
@@ -14288,7 +14209,58 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
|
|
|
14288
14209
|
}
|
|
14289
14210
|
names.add(name);
|
|
14290
14211
|
}
|
|
14291
|
-
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;
|
|
14292
14264
|
}
|
|
14293
14265
|
async function resolveBrowserProjects(vitest, names, resolvedProjects) {
|
|
14294
14266
|
const removeProjects = /* @__PURE__ */ new Set();
|
|
@@ -14329,7 +14301,7 @@ async function resolveBrowserProjects(vitest, names, resolvedProjects) {
|
|
|
14329
14301
|
names.add(name);
|
|
14330
14302
|
const clonedConfig = cloneConfig(project, config);
|
|
14331
14303
|
clonedConfig.name = name;
|
|
14332
|
-
const clone = TestProject.
|
|
14304
|
+
const clone = TestProject._cloneTestProject(project, clonedConfig);
|
|
14333
14305
|
resolvedProjects.push(clone);
|
|
14334
14306
|
});
|
|
14335
14307
|
removeProjects.add(project);
|
|
@@ -14454,8 +14426,10 @@ function getDefaultTestProject(vitest) {
|
|
|
14454
14426
|
}
|
|
14455
14427
|
function getPotentialProjectNames(project) {
|
|
14456
14428
|
const names = [project.name];
|
|
14429
|
+
// TODO: include benchmarks in browsers
|
|
14457
14430
|
if (project.config.browser.instances) names.push(...project.config.browser.instances.map((i) => i.name));
|
|
14458
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");
|
|
14459
14433
|
return names;
|
|
14460
14434
|
}
|
|
14461
14435
|
|
|
@@ -14687,6 +14661,66 @@ function createReport(ctx, scope) {
|
|
|
14687
14661
|
};
|
|
14688
14662
|
}
|
|
14689
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
|
+
|
|
14690
14724
|
const BADGE_PADDING = " ";
|
|
14691
14725
|
class BaseReporter {
|
|
14692
14726
|
start = 0;
|
|
@@ -14700,6 +14734,7 @@ class BaseReporter {
|
|
|
14700
14734
|
silent;
|
|
14701
14735
|
_filesInWatchMode = /* @__PURE__ */ new Map();
|
|
14702
14736
|
_timeStart = formatTimeString(/* @__PURE__ */ new Date());
|
|
14737
|
+
_perProjectBenchmarks = /* @__PURE__ */ new Map();
|
|
14703
14738
|
constructor(options = {}) {
|
|
14704
14739
|
this.isTTY = options.isTTY ?? isTTY;
|
|
14705
14740
|
this.silent = options.silent;
|
|
@@ -14721,17 +14756,34 @@ class BaseReporter {
|
|
|
14721
14756
|
onTestRunStart(_specifications) {
|
|
14722
14757
|
this.start = performance$1.now();
|
|
14723
14758
|
this._timeStart = formatTimeString(/* @__PURE__ */ new Date());
|
|
14759
|
+
this._perProjectBenchmarks.clear();
|
|
14724
14760
|
}
|
|
14725
14761
|
onTestRunEnd(testModules, unhandledErrors, _reason) {
|
|
14726
14762
|
const files = testModules.map((testModule) => testModule.task);
|
|
14727
14763
|
const errors = [...unhandledErrors];
|
|
14728
14764
|
this.end = performance$1.now();
|
|
14729
14765
|
if (!files.length && !errors.length) this.ctx.logger.printNoTestFound(this.ctx.filenamePattern);
|
|
14730
|
-
else
|
|
14766
|
+
else {
|
|
14767
|
+
this.printPerProjectBenchmarks();
|
|
14768
|
+
this.reportSummary(files, errors);
|
|
14769
|
+
}
|
|
14731
14770
|
}
|
|
14732
14771
|
onTestCaseResult(testCase) {
|
|
14733
14772
|
if (testCase.result().state === "failed") this.logFailedTask(testCase.task);
|
|
14734
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
|
+
}
|
|
14735
14787
|
onTestSuiteResult(testSuite) {
|
|
14736
14788
|
if (testSuite.state() === "failed") this.logFailedTask(testSuite.task);
|
|
14737
14789
|
}
|
|
@@ -14790,9 +14842,13 @@ class BaseReporter {
|
|
|
14790
14842
|
const { duration = 0 } = test.diagnostic() || {};
|
|
14791
14843
|
const padding = this.getTestIndentation(test.task);
|
|
14792
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);
|
|
14793
14848
|
if (testResult.state === "failed") this.log(c.red(` ${padding}${taskFail} ${this.getTestName(test.task, separator)}`) + suffix);
|
|
14794
|
-
else if (duration > this.ctx.config.slowTestThreshold) this.log(` ${padding}${c.yellow(c.dim(F_CHECK))} ${this.getTestName(test.task, separator)}
|
|
14795
|
-
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);
|
|
14796
14852
|
}
|
|
14797
14853
|
getModuleLog(testModule, counts) {
|
|
14798
14854
|
let state = c.dim(`${counts.tests} test${counts.tests > 1 ? "s" : ""}`);
|
|
@@ -14946,8 +15002,7 @@ class BaseReporter {
|
|
|
14946
15002
|
reportSummary(files, errors) {
|
|
14947
15003
|
this.printErrorsSummary(files, errors);
|
|
14948
15004
|
const leakCount = this.printLeaksSummary();
|
|
14949
|
-
|
|
14950
|
-
else this.reportTestSummary(files, errors, leakCount);
|
|
15005
|
+
this.reportTestSummary(files, errors, leakCount);
|
|
14951
15006
|
}
|
|
14952
15007
|
reportTestSummary(files, errors, leakCount) {
|
|
14953
15008
|
this.log();
|
|
@@ -15012,19 +15067,23 @@ class BaseReporter {
|
|
|
15012
15067
|
}
|
|
15013
15068
|
}
|
|
15014
15069
|
if (allImports.length === 0) return;
|
|
15015
|
-
|
|
15016
|
-
|
|
15017
|
-
|
|
15018
|
-
|
|
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
|
+
}
|
|
15019
15080
|
// Determine if we should print
|
|
15020
|
-
const shouldFail = failOnDanger &&
|
|
15081
|
+
const shouldFail = failOnDanger && dangerImportsCount > 0;
|
|
15021
15082
|
if (!(print === true || print === "on-warn" && hasWarnImports || shouldFail)) return;
|
|
15022
15083
|
const sortedImports = allImports.sort((a, b) => b.totalTime - a.totalTime);
|
|
15023
15084
|
const maxTotalTime = sortedImports[0].totalTime;
|
|
15024
15085
|
const limit = this.ctx.config.experimental.importDurations.limit;
|
|
15025
15086
|
const topImports = sortedImports.slice(0, limit);
|
|
15026
|
-
const totalSelfTime = allImports.reduce((sum, imp) => sum + imp.selfTime, 0);
|
|
15027
|
-
const totalTotalTime = allImports.reduce((sum, imp) => sum + imp.totalTime, 0);
|
|
15028
15087
|
const slowestImport = sortedImports[0];
|
|
15029
15088
|
this.log();
|
|
15030
15089
|
this.log(c.bold("Import Duration Breakdown") + c.dim(` (Top ${limit})`));
|
|
@@ -15064,7 +15123,7 @@ class BaseReporter {
|
|
|
15064
15123
|
// Fail if danger threshold exceeded
|
|
15065
15124
|
if (shouldFail) {
|
|
15066
15125
|
this.log();
|
|
15067
|
-
this.ctx.logger.error(`ERROR: ${
|
|
15126
|
+
this.ctx.logger.error(`ERROR: ${dangerImportsCount} import(s) exceeded the danger threshold of ${thresholds.danger}ms`);
|
|
15068
15127
|
process.exitCode = 1;
|
|
15069
15128
|
}
|
|
15070
15129
|
}
|
|
@@ -15131,21 +15190,63 @@ class BaseReporter {
|
|
|
15131
15190
|
}
|
|
15132
15191
|
return leakWithStacks.size;
|
|
15133
15192
|
}
|
|
15134
|
-
|
|
15135
|
-
|
|
15136
|
-
|
|
15137
|
-
for (const
|
|
15138
|
-
|
|
15139
|
-
|
|
15140
|
-
|
|
15141
|
-
|
|
15142
|
-
|
|
15143
|
-
|
|
15144
|
-
|
|
15145
|
-
|
|
15146
|
-
|
|
15147
|
-
|
|
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;
|
|
15148
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
|
+
}
|
|
15149
15250
|
}
|
|
15150
15251
|
}
|
|
15151
15252
|
printTaskErrors(tasks, errorDivider) {
|
|
@@ -15203,7 +15304,7 @@ function deepEqual(a, b) {
|
|
|
15203
15304
|
const keysA = Object.keys(a);
|
|
15204
15305
|
const keysB = Object.keys(b);
|
|
15205
15306
|
if (keysA.length !== keysB.length) return false;
|
|
15206
|
-
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;
|
|
15207
15308
|
return true;
|
|
15208
15309
|
}
|
|
15209
15310
|
function sum(items, cb) {
|
|
@@ -15436,28 +15537,30 @@ class SummaryReporter {
|
|
|
15436
15537
|
this.maxParallelTests = Math.max(this.maxParallelTests, this.runningModules.size);
|
|
15437
15538
|
this.renderer.schedule();
|
|
15438
15539
|
}
|
|
15439
|
-
|
|
15440
|
-
const
|
|
15441
|
-
|
|
15442
|
-
const hook = {
|
|
15443
|
-
name: options.name,
|
|
15540
|
+
startStep(stats, name) {
|
|
15541
|
+
const step = {
|
|
15542
|
+
name,
|
|
15444
15543
|
visible: false,
|
|
15445
15544
|
startTime: performance.now(),
|
|
15446
15545
|
onFinish: () => {}
|
|
15447
15546
|
};
|
|
15448
|
-
stats.
|
|
15449
|
-
stats.
|
|
15547
|
+
stats.step?.onFinish?.();
|
|
15548
|
+
stats.step = step;
|
|
15450
15549
|
if (!Number.isFinite(this.ctx.config.slowTestThreshold)) return;
|
|
15451
15550
|
const timeout = setTimeout(() => {
|
|
15452
|
-
|
|
15551
|
+
step.visible = true;
|
|
15453
15552
|
}, this.ctx.config.slowTestThreshold).unref();
|
|
15454
|
-
|
|
15553
|
+
step.onFinish = () => clearTimeout(timeout);
|
|
15554
|
+
}
|
|
15555
|
+
onHookStart(options) {
|
|
15556
|
+
const stats = this.getStepStats(options.entity);
|
|
15557
|
+
if (stats) this.startStep(stats, options.name);
|
|
15455
15558
|
}
|
|
15456
15559
|
onHookEnd(options) {
|
|
15457
|
-
const stats = this.
|
|
15458
|
-
if (stats?.
|
|
15459
|
-
stats.
|
|
15460
|
-
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;
|
|
15461
15564
|
}
|
|
15462
15565
|
onTestCaseReady(test) {
|
|
15463
15566
|
// Track slow running tests only on verbose mode
|
|
@@ -15474,7 +15577,7 @@ class SummaryReporter {
|
|
|
15474
15577
|
slowTest.visible = true;
|
|
15475
15578
|
}, this.ctx.config.slowTestThreshold).unref() : void 0;
|
|
15476
15579
|
slowTest.onFinish = () => {
|
|
15477
|
-
slowTest.
|
|
15580
|
+
slowTest.step?.onFinish();
|
|
15478
15581
|
clearTimeout(timeout);
|
|
15479
15582
|
};
|
|
15480
15583
|
stats.tests.set(test.id, slowTest);
|
|
@@ -15514,7 +15617,7 @@ class SummaryReporter {
|
|
|
15514
15617
|
this.removeTestModule(module.id);
|
|
15515
15618
|
this.renderer.schedule();
|
|
15516
15619
|
}
|
|
15517
|
-
|
|
15620
|
+
getStepStats(entity) {
|
|
15518
15621
|
// Track slow running hooks only on verbose mode
|
|
15519
15622
|
if (!this.options.verbose) return;
|
|
15520
15623
|
const module = entity.type === "module" ? entity : entity.module;
|
|
@@ -15531,12 +15634,12 @@ class SummaryReporter {
|
|
|
15531
15634
|
name: testFile.projectName,
|
|
15532
15635
|
color: testFile.projectColor
|
|
15533
15636
|
}) + typecheck + label + testFile.filename + c.dim(!testFile.completed && !testFile.total ? " [queued]" : ` ${testFile.completed}/${testFile.total}`));
|
|
15534
|
-
const slowTasks = [testFile.
|
|
15637
|
+
const slowTasks = [testFile.step, ...testFile.tests.values()].filter((t) => t != null && t.visible);
|
|
15535
15638
|
for (const [index, task] of slowTasks.entries()) {
|
|
15536
15639
|
const elapsed = this.currentTime - task.startTime;
|
|
15537
15640
|
const icon = index === slowTasks.length - 1 ? F_TREE_NODE_END : F_TREE_NODE_MIDDLE;
|
|
15538
15641
|
summary.push(c.bold(c.yellow(` ${icon} `)) + task.name + c.bold(c.yellow(` ${formatTime(Math.max(0, elapsed))}`)));
|
|
15539
|
-
if (task.
|
|
15642
|
+
if (task.step?.visible) summary.push(c.bold(c.yellow(` ${F_TREE_NODE_END} `)) + task.step.name);
|
|
15540
15643
|
}
|
|
15541
15644
|
}
|
|
15542
15645
|
if (this.runningModules.size > 0) summary.push("");
|
|
@@ -15558,7 +15661,7 @@ class SummaryReporter {
|
|
|
15558
15661
|
removeTestModule(id) {
|
|
15559
15662
|
if (!id) return;
|
|
15560
15663
|
const testFile = this.runningModules.get(id);
|
|
15561
|
-
testFile?.
|
|
15664
|
+
testFile?.step?.onFinish();
|
|
15562
15665
|
testFile?.tests?.forEach((test) => test.onFinish());
|
|
15563
15666
|
this.runningModules.delete(id);
|
|
15564
15667
|
clearTimeout(this.finishedModules.get(id));
|
|
@@ -16099,7 +16202,8 @@ class JsonReporter {
|
|
|
16099
16202
|
}
|
|
16100
16203
|
return filtered;
|
|
16101
16204
|
})() : t.meta,
|
|
16102
|
-
tags: t.tags || []
|
|
16205
|
+
tags: t.tags || [],
|
|
16206
|
+
benchmarks: t.benchmarks
|
|
16103
16207
|
};
|
|
16104
16208
|
});
|
|
16105
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");
|
|
@@ -16259,6 +16363,18 @@ class JUnitReporter {
|
|
|
16259
16363
|
for (const log of logs) await this.baseLog(escapeXML(log.content));
|
|
16260
16364
|
});
|
|
16261
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
|
+
}
|
|
16262
16378
|
applyTemplate(template, vars) {
|
|
16263
16379
|
if (typeof template === "function") return template(vars);
|
|
16264
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);
|
|
@@ -16284,10 +16400,8 @@ class JUnitReporter {
|
|
|
16284
16400
|
name: testcaseName,
|
|
16285
16401
|
time: getDuration(task)
|
|
16286
16402
|
}, async () => {
|
|
16287
|
-
|
|
16288
|
-
|
|
16289
|
-
await this.writeLogs(task, "err");
|
|
16290
|
-
}
|
|
16403
|
+
await this.writeSystemOut(task);
|
|
16404
|
+
if (this.options.includeConsoleOutput) await this.writeLogs(task, "err");
|
|
16291
16405
|
if (task.mode === "skip" || task.mode === "todo") await this.logger.log("<skipped/>");
|
|
16292
16406
|
if (task.type === "test" && task.annotations.length) {
|
|
16293
16407
|
await this.logger.log("<properties>");
|
|
@@ -16405,7 +16519,8 @@ class JUnitReporter {
|
|
|
16405
16519
|
suite: null,
|
|
16406
16520
|
file: null,
|
|
16407
16521
|
annotations: [],
|
|
16408
|
-
artifacts: []
|
|
16522
|
+
artifacts: [],
|
|
16523
|
+
benchmarks: []
|
|
16409
16524
|
});
|
|
16410
16525
|
}
|
|
16411
16526
|
return {
|
|
@@ -16607,231 +16722,11 @@ class VerboseReporter extends DefaultReporter {
|
|
|
16607
16722
|
this.printAnnotations(test, "log", 3);
|
|
16608
16723
|
this.log();
|
|
16609
16724
|
}
|
|
16725
|
+
const inlineBenchmarks = test.benchmarks().filter((b) => b.tasks.length > 0);
|
|
16726
|
+
if (inlineBenchmarks.length > 0) this.printBenchmarkTable(inlineBenchmarks, "");
|
|
16610
16727
|
}
|
|
16611
16728
|
}
|
|
16612
16729
|
|
|
16613
|
-
function createBenchmarkJsonReport(files) {
|
|
16614
|
-
const report = { files: [] };
|
|
16615
|
-
for (const file of files) {
|
|
16616
|
-
const groups = [];
|
|
16617
|
-
for (const task of getTasks(file)) if (task?.type === "suite") {
|
|
16618
|
-
const benchmarks = [];
|
|
16619
|
-
for (const t of task.tasks) {
|
|
16620
|
-
const benchmark = t.meta.benchmark && t.result?.benchmark;
|
|
16621
|
-
if (benchmark) benchmarks.push({
|
|
16622
|
-
id: t.id,
|
|
16623
|
-
...benchmark,
|
|
16624
|
-
samples: []
|
|
16625
|
-
});
|
|
16626
|
-
}
|
|
16627
|
-
if (benchmarks.length) groups.push({
|
|
16628
|
-
fullName: getFullName(task, " > "),
|
|
16629
|
-
benchmarks
|
|
16630
|
-
});
|
|
16631
|
-
}
|
|
16632
|
-
report.files.push({
|
|
16633
|
-
filepath: file.filepath,
|
|
16634
|
-
groups
|
|
16635
|
-
});
|
|
16636
|
-
}
|
|
16637
|
-
return report;
|
|
16638
|
-
}
|
|
16639
|
-
function flattenFormattedBenchmarkReport(report) {
|
|
16640
|
-
const flat = {};
|
|
16641
|
-
for (const file of report.files) for (const group of file.groups) for (const t of group.benchmarks) flat[t.id] = t;
|
|
16642
|
-
return flat;
|
|
16643
|
-
}
|
|
16644
|
-
|
|
16645
|
-
const outputMap = /* @__PURE__ */ new WeakMap();
|
|
16646
|
-
function formatNumber(number) {
|
|
16647
|
-
const res = String(number.toFixed(number < 100 ? 4 : 2)).split(".");
|
|
16648
|
-
return res[0].replace(/(?=(?:\d{3})+$)\B/g, ",") + (res[1] ? `.${res[1]}` : "");
|
|
16649
|
-
}
|
|
16650
|
-
const tableHead = [
|
|
16651
|
-
"name",
|
|
16652
|
-
"hz",
|
|
16653
|
-
"min",
|
|
16654
|
-
"max",
|
|
16655
|
-
"mean",
|
|
16656
|
-
"p75",
|
|
16657
|
-
"p99",
|
|
16658
|
-
"p995",
|
|
16659
|
-
"p999",
|
|
16660
|
-
"rme",
|
|
16661
|
-
"samples"
|
|
16662
|
-
];
|
|
16663
|
-
function renderBenchmarkItems(result) {
|
|
16664
|
-
return [
|
|
16665
|
-
result.name,
|
|
16666
|
-
formatNumber(result.hz || 0),
|
|
16667
|
-
formatNumber(result.min || 0),
|
|
16668
|
-
formatNumber(result.max || 0),
|
|
16669
|
-
formatNumber(result.mean || 0),
|
|
16670
|
-
formatNumber(result.p75 || 0),
|
|
16671
|
-
formatNumber(result.p99 || 0),
|
|
16672
|
-
formatNumber(result.p995 || 0),
|
|
16673
|
-
formatNumber(result.p999 || 0),
|
|
16674
|
-
`±${(result.rme || 0).toFixed(2)}%`,
|
|
16675
|
-
(result.sampleCount || 0).toString()
|
|
16676
|
-
];
|
|
16677
|
-
}
|
|
16678
|
-
function computeColumnWidths(results) {
|
|
16679
|
-
const rows = [tableHead, ...results.map((v) => renderBenchmarkItems(v))];
|
|
16680
|
-
return Array.from(tableHead, (_, i) => Math.max(...rows.map((row) => stripVTControlCharacters(row[i]).length)));
|
|
16681
|
-
}
|
|
16682
|
-
function padRow(row, widths) {
|
|
16683
|
-
return row.map((v, i) => i ? v.padStart(widths[i], " ") : v.padEnd(widths[i], " "));
|
|
16684
|
-
}
|
|
16685
|
-
function renderTableHead(widths) {
|
|
16686
|
-
return " ".repeat(3) + padRow(tableHead, widths).map(c.bold).join(" ");
|
|
16687
|
-
}
|
|
16688
|
-
function renderBenchmark(result, widths) {
|
|
16689
|
-
const padded = padRow(renderBenchmarkItems(result), widths);
|
|
16690
|
-
return [
|
|
16691
|
-
padded[0],
|
|
16692
|
-
c.blue(padded[1]),
|
|
16693
|
-
c.cyan(padded[2]),
|
|
16694
|
-
c.cyan(padded[3]),
|
|
16695
|
-
c.cyan(padded[4]),
|
|
16696
|
-
c.cyan(padded[5]),
|
|
16697
|
-
c.cyan(padded[6]),
|
|
16698
|
-
c.cyan(padded[7]),
|
|
16699
|
-
c.cyan(padded[8]),
|
|
16700
|
-
c.dim(padded[9]),
|
|
16701
|
-
c.dim(padded[10])
|
|
16702
|
-
].join(" ");
|
|
16703
|
-
}
|
|
16704
|
-
function renderTable(options) {
|
|
16705
|
-
const output = [];
|
|
16706
|
-
const benchMap = {};
|
|
16707
|
-
for (const task of options.tasks) if (task.meta.benchmark && task.result?.benchmark) benchMap[task.id] = {
|
|
16708
|
-
current: task.result.benchmark,
|
|
16709
|
-
baseline: options.compare?.[task.id]
|
|
16710
|
-
};
|
|
16711
|
-
const benchCount = Object.entries(benchMap).length;
|
|
16712
|
-
const columnWidths = computeColumnWidths(Object.values(benchMap).flatMap((v) => [v.current, v.baseline]).filter(notNullish));
|
|
16713
|
-
let idx = 0;
|
|
16714
|
-
const padding = " ".repeat(1 );
|
|
16715
|
-
for (const task of options.tasks) {
|
|
16716
|
-
const duration = task.result?.duration;
|
|
16717
|
-
const bench = benchMap[task.id];
|
|
16718
|
-
let prefix = "";
|
|
16719
|
-
if (idx === 0 && task.meta?.benchmark) prefix += `${renderTableHead(columnWidths)}\n${padding}`;
|
|
16720
|
-
prefix += ` ${getStateSymbol(task)} `;
|
|
16721
|
-
let suffix = "";
|
|
16722
|
-
if (task.type === "suite") suffix += c.dim(` (${getTests(task).length})`);
|
|
16723
|
-
if (task.mode === "skip" || task.mode === "todo") suffix += c.dim(c.gray(" [skipped]"));
|
|
16724
|
-
if (duration != null) {
|
|
16725
|
-
const color = duration > options.slowTestThreshold ? c.yellow : c.green;
|
|
16726
|
-
suffix += color(` ${Math.round(duration)}${c.dim("ms")}`);
|
|
16727
|
-
}
|
|
16728
|
-
if (options.showHeap && task.result?.heap != null) suffix += c.magenta(` ${Math.floor(task.result.heap / 1024 / 1024)} MB heap used`);
|
|
16729
|
-
if (bench) {
|
|
16730
|
-
let body = renderBenchmark(bench.current, columnWidths);
|
|
16731
|
-
if (options.compare && bench.baseline) {
|
|
16732
|
-
if (bench.current.hz) {
|
|
16733
|
-
const diff = bench.current.hz / bench.baseline.hz;
|
|
16734
|
-
const diffFixed = diff.toFixed(2);
|
|
16735
|
-
if (diffFixed === "1.0.0") body += c.gray(` [${diffFixed}x]`);
|
|
16736
|
-
if (diff > 1) body += c.blue(` [${diffFixed}x] ⇑`);
|
|
16737
|
-
else body += c.red(` [${diffFixed}x] ⇓`);
|
|
16738
|
-
}
|
|
16739
|
-
output.push(padding + prefix + body + suffix);
|
|
16740
|
-
const bodyBaseline = renderBenchmark(bench.baseline, columnWidths);
|
|
16741
|
-
output.push(`${padding} ${bodyBaseline} ${c.dim("(baseline)")}`);
|
|
16742
|
-
} else {
|
|
16743
|
-
if (bench.current.rank === 1 && benchCount > 1) body += c.bold(c.green(" fastest"));
|
|
16744
|
-
if (bench.current.rank === benchCount && benchCount > 2) body += c.bold(c.gray(" slowest"));
|
|
16745
|
-
output.push(padding + prefix + body + suffix);
|
|
16746
|
-
}
|
|
16747
|
-
} else output.push(padding + prefix + task.name + suffix);
|
|
16748
|
-
if (task.result?.state !== "pass" && outputMap.get(task) != null) {
|
|
16749
|
-
let data = outputMap.get(task);
|
|
16750
|
-
if (typeof data === "string") {
|
|
16751
|
-
data = stripVTControlCharacters(data.trim().split("\n").filter(Boolean).pop());
|
|
16752
|
-
if (data === "") data = void 0;
|
|
16753
|
-
}
|
|
16754
|
-
if (data != null) {
|
|
16755
|
-
const out = ` ${" ".repeat(options.level)}${F_RIGHT} ${data}`;
|
|
16756
|
-
output.push(c.gray(truncateString(out, options.columns)));
|
|
16757
|
-
}
|
|
16758
|
-
}
|
|
16759
|
-
idx++;
|
|
16760
|
-
}
|
|
16761
|
-
return output.filter(Boolean).join("\n");
|
|
16762
|
-
}
|
|
16763
|
-
|
|
16764
|
-
class BenchmarkReporter extends DefaultReporter {
|
|
16765
|
-
compare;
|
|
16766
|
-
async onInit(ctx) {
|
|
16767
|
-
super.onInit(ctx);
|
|
16768
|
-
if (this.ctx.config.benchmark?.compare) {
|
|
16769
|
-
const compareFile = pathe.resolve(this.ctx.config.root, this.ctx.config.benchmark?.compare);
|
|
16770
|
-
try {
|
|
16771
|
-
this.compare = flattenFormattedBenchmarkReport(JSON.parse(await fs__default.promises.readFile(compareFile, "utf-8")));
|
|
16772
|
-
} catch (e) {
|
|
16773
|
-
this.error(`Failed to read '${compareFile}'`, e);
|
|
16774
|
-
}
|
|
16775
|
-
}
|
|
16776
|
-
}
|
|
16777
|
-
onTaskUpdate(packs) {
|
|
16778
|
-
for (const pack of packs) {
|
|
16779
|
-
const task = this.ctx.state.idMap.get(pack[0]);
|
|
16780
|
-
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) => {
|
|
16781
|
-
bench.result.benchmark.rank = Number(idx) + 1;
|
|
16782
|
-
});
|
|
16783
|
-
}
|
|
16784
|
-
}
|
|
16785
|
-
onTestSuiteResult(testSuite) {
|
|
16786
|
-
super.onTestSuiteResult(testSuite);
|
|
16787
|
-
this.printSuiteTable(testSuite);
|
|
16788
|
-
}
|
|
16789
|
-
printTestModule(testModule) {
|
|
16790
|
-
this.printSuiteTable(testModule);
|
|
16791
|
-
}
|
|
16792
|
-
printSuiteTable(testTask) {
|
|
16793
|
-
const state = testTask.state();
|
|
16794
|
-
if (state === "pending" || state === "queued") return;
|
|
16795
|
-
const benches = testTask.task.tasks.filter((t) => t.meta.benchmark);
|
|
16796
|
-
const duration = testTask.task.result?.duration || 0;
|
|
16797
|
-
if (benches.length > 0 && benches.every((t) => t.result?.state !== "run" && t.result?.state !== "queued")) {
|
|
16798
|
-
let title = `\n ${getStateSymbol(testTask.task)} ${formatProjectName(testTask.project)}${getFullName(testTask.task, separator)}`;
|
|
16799
|
-
if (duration != null && duration > this.ctx.config.slowTestThreshold) title += c.yellow(` ${Math.round(duration)}${c.dim("ms")}`);
|
|
16800
|
-
this.log(title);
|
|
16801
|
-
this.log(renderTable({
|
|
16802
|
-
tasks: benches,
|
|
16803
|
-
level: 1,
|
|
16804
|
-
columns: this.ctx.logger.getColumns(),
|
|
16805
|
-
compare: this.compare,
|
|
16806
|
-
showHeap: this.ctx.config.logHeapUsage,
|
|
16807
|
-
slowTestThreshold: this.ctx.config.slowTestThreshold
|
|
16808
|
-
}));
|
|
16809
|
-
}
|
|
16810
|
-
}
|
|
16811
|
-
async onTestRunEnd(testModules, unhandledErrors, reason) {
|
|
16812
|
-
super.onTestRunEnd(testModules, unhandledErrors, reason);
|
|
16813
|
-
// write output for future comparison
|
|
16814
|
-
let outputFile = this.ctx.config.benchmark?.outputJson;
|
|
16815
|
-
if (outputFile) {
|
|
16816
|
-
outputFile = pathe.resolve(this.ctx.config.root, outputFile);
|
|
16817
|
-
const outputDirectory = pathe.dirname(outputFile);
|
|
16818
|
-
if (!fs__default.existsSync(outputDirectory)) await fs__default.promises.mkdir(outputDirectory, { recursive: true });
|
|
16819
|
-
const output = createBenchmarkJsonReport(testModules.map((t) => t.task.file));
|
|
16820
|
-
await fs__default.promises.writeFile(outputFile, JSON.stringify(output, null, 2));
|
|
16821
|
-
this.log(`Benchmark report written to ${outputFile}`);
|
|
16822
|
-
}
|
|
16823
|
-
}
|
|
16824
|
-
}
|
|
16825
|
-
|
|
16826
|
-
class VerboseBenchmarkReporter extends BenchmarkReporter {
|
|
16827
|
-
verbose = true;
|
|
16828
|
-
}
|
|
16829
|
-
|
|
16830
|
-
const BenchmarkReportsMap = {
|
|
16831
|
-
default: BenchmarkReporter,
|
|
16832
|
-
verbose: VerboseBenchmarkReporter
|
|
16833
|
-
};
|
|
16834
|
-
|
|
16835
16730
|
const ReportersMap = {
|
|
16836
16731
|
"default": DefaultReporter,
|
|
16837
16732
|
"agent": MinimalReporter,
|
|
@@ -16875,16 +16770,6 @@ function createReporters(reporterReferences, ctx) {
|
|
|
16875
16770
|
});
|
|
16876
16771
|
return Promise.all(promisedReporters);
|
|
16877
16772
|
}
|
|
16878
|
-
function createBenchmarkReporters(reporterReferences, runner) {
|
|
16879
|
-
const promisedReporters = reporterReferences.map(async (referenceOrInstance) => {
|
|
16880
|
-
if (typeof referenceOrInstance === "string") if (referenceOrInstance in BenchmarkReportsMap) {
|
|
16881
|
-
const BuiltinReporter = BenchmarkReportsMap[referenceOrInstance];
|
|
16882
|
-
return new BuiltinReporter();
|
|
16883
|
-
} else return new (await (loadCustomReporterModule(referenceOrInstance, runner)))();
|
|
16884
|
-
return referenceOrInstance;
|
|
16885
|
-
});
|
|
16886
|
-
return Promise.all(promisedReporters);
|
|
16887
|
-
}
|
|
16888
16773
|
|
|
16889
16774
|
function parseFilter(filter) {
|
|
16890
16775
|
const colonIndex = filter.lastIndexOf(":");
|
|
@@ -17166,6 +17051,14 @@ class TestCase extends ReportedTaskImplementation {
|
|
|
17166
17051
|
return [...this.task.artifacts];
|
|
17167
17052
|
}
|
|
17168
17053
|
/**
|
|
17054
|
+
* @experimental
|
|
17055
|
+
*
|
|
17056
|
+
* A list of benchmarks performed during the test.
|
|
17057
|
+
*/
|
|
17058
|
+
benchmarks() {
|
|
17059
|
+
return [...this.task.benchmarks];
|
|
17060
|
+
}
|
|
17061
|
+
/**
|
|
17169
17062
|
* Useful information about the test like duration, memory usage, etc.
|
|
17170
17063
|
* Diagnostic is only available after the test has finished.
|
|
17171
17064
|
*/
|
|
@@ -18070,21 +17963,23 @@ class TestRun {
|
|
|
18070
17963
|
this.vitest.state.updateUserLog(log);
|
|
18071
17964
|
await this.vitest.report("onUserConsoleLog", log);
|
|
18072
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
|
+
}
|
|
18073
17971
|
async recordArtifact(testId, artifact) {
|
|
18074
|
-
const
|
|
18075
|
-
const entity = task && this.vitest.state.getReportedEntity(task);
|
|
18076
|
-
assert$1(task && entity, `Entity must be found for task ${task?.name || testId}`);
|
|
18077
|
-
assert$1(entity.type === "test", `Artifacts can only be recorded on a test, instead got ${entity.type}`);
|
|
17972
|
+
const testCase = this.getTestCaseById(testId, "Artifact");
|
|
18078
17973
|
// annotations won't resolve as artifacts for backwards compatibility until next major
|
|
18079
17974
|
if (artifact.type === "internal:annotation") {
|
|
18080
|
-
await this.resolveTestAttachment(
|
|
18081
|
-
|
|
18082
|
-
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);
|
|
18083
17978
|
return artifact;
|
|
18084
17979
|
}
|
|
18085
|
-
if (Array.isArray(artifact.attachments)) await Promise.all(artifact.attachments.map((attachment) => this.resolveTestAttachment(
|
|
18086
|
-
|
|
18087
|
-
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);
|
|
18088
17983
|
return artifact;
|
|
18089
17984
|
}
|
|
18090
17985
|
async updated(update, events) {
|
|
@@ -18098,6 +17993,13 @@ class TestRun {
|
|
|
18098
17993
|
// TODO: error handling - what happens if custom reporter throws an error?
|
|
18099
17994
|
await this.vitest.report("onTaskUpdate", update, events);
|
|
18100
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
|
+
}
|
|
18101
18003
|
async end(specifications, errors, coverage) {
|
|
18102
18004
|
if (coverage) await this.vitest.report("onCoverage", coverage);
|
|
18103
18005
|
// specification won't have the File task if they were filtered by the --shard command
|
|
@@ -18462,7 +18364,7 @@ class Vitest {
|
|
|
18462
18364
|
* The logger instance used to log messages. It's recommended to use this logger instead of `console`.
|
|
18463
18365
|
* It's possible to override stdout and stderr streams when initiating Vitest.
|
|
18464
18366
|
* @example
|
|
18465
|
-
* new Vitest(
|
|
18367
|
+
* new Vitest({
|
|
18466
18368
|
* stdout: new Writable(),
|
|
18467
18369
|
* })
|
|
18468
18370
|
*/
|
|
@@ -18524,8 +18426,20 @@ class Vitest {
|
|
|
18524
18426
|
_cache;
|
|
18525
18427
|
_snapshot;
|
|
18526
18428
|
_coverageProvider;
|
|
18527
|
-
|
|
18528
|
-
|
|
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
|
+
}
|
|
18529
18443
|
this._cliOptions = cliOptions;
|
|
18530
18444
|
this.logger = new Logger(this, options.stdout, options.stderr);
|
|
18531
18445
|
this.packageInstaller = options.packageInstaller || new VitestPackageInstaller();
|
|
@@ -18643,9 +18557,9 @@ class Vitest {
|
|
|
18643
18557
|
try {
|
|
18644
18558
|
await this.cache.results.readFromCache();
|
|
18645
18559
|
} catch {}
|
|
18646
|
-
|
|
18647
|
-
this.projects = projects;
|
|
18648
|
-
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) => {
|
|
18649
18563
|
return project.vite.config.getSortedPluginHooks("configureVitest").map((hook) => hook({
|
|
18650
18564
|
project,
|
|
18651
18565
|
vitest: this,
|
|
@@ -18670,7 +18584,7 @@ class Vitest {
|
|
|
18670
18584
|
// populate will merge all configs into every project,
|
|
18671
18585
|
// we don't want that when just listing tags
|
|
18672
18586
|
if (!this.config.listTags) populateProjectsTags(this.coreWorkspaceProject, this.projects);
|
|
18673
|
-
this.reporters =
|
|
18587
|
+
this.reporters = await createReporters(resolved.reporters, this);
|
|
18674
18588
|
await this._fsCache.ensureCacheIntegrity();
|
|
18675
18589
|
await Promise.all([...this._onSetServer.map((fn) => fn()), this._traces.waitInit()]);
|
|
18676
18590
|
}
|
|
@@ -18797,7 +18711,7 @@ class Vitest {
|
|
|
18797
18711
|
// returns the project only if it matches the filter
|
|
18798
18712
|
const project = getDefaultTestProject(this);
|
|
18799
18713
|
if (!project) return [];
|
|
18800
|
-
return
|
|
18714
|
+
return resolveDefaultProjects(this, new Set([project.name]), [project]);
|
|
18801
18715
|
}
|
|
18802
18716
|
/**
|
|
18803
18717
|
* Glob test files in every project and create a TestSpecification for each file and pool.
|
|
@@ -18963,7 +18877,7 @@ class Vitest {
|
|
|
18963
18877
|
// Report coverage for uncovered files
|
|
18964
18878
|
await this.reportCoverage(coverage, true);
|
|
18965
18879
|
});
|
|
18966
|
-
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();
|
|
18967
18881
|
}
|
|
18968
18882
|
let testModules = {
|
|
18969
18883
|
testModules: [],
|
|
@@ -19147,7 +19061,6 @@ class Vitest {
|
|
|
19147
19061
|
}));
|
|
19148
19062
|
}
|
|
19149
19063
|
async experimental_parseSpecifications(specifications, options) {
|
|
19150
|
-
if (this.mode !== "test") throw new Error(`The \`experimental_parseSpecifications\` does not support "${this.mode}" mode.`);
|
|
19151
19064
|
const limit = limitConcurrency(options?.concurrency ?? (typeof nodeos__default.availableParallelism === "function" ? nodeos__default.availableParallelism() : nodeos__default.cpus().length));
|
|
19152
19065
|
// Phase 1: parse all files in parallel (without mode interpretation)
|
|
19153
19066
|
const results = await Promise.all(specifications.map((specification) => limit(async () => {
|
|
@@ -19169,7 +19082,6 @@ class Vitest {
|
|
|
19169
19082
|
return results.map(({ file }) => this.state.getReportedEntity(file));
|
|
19170
19083
|
}
|
|
19171
19084
|
async experimental_parseSpecification(specification) {
|
|
19172
|
-
if (this.mode !== "test") throw new Error(`The \`experimental_parseSpecification\` does not support "${this.mode}" mode.`);
|
|
19173
19085
|
const file = await astCollectTests(specification.project, specification.moduleId).catch((error) => {
|
|
19174
19086
|
return createFailedFileTask(specification.project, specification.moduleId, error);
|
|
19175
19087
|
});
|
|
@@ -19565,7 +19477,7 @@ function assert(condition, property, name = property) {
|
|
|
19565
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}\`.`);
|
|
19566
19478
|
}
|
|
19567
19479
|
|
|
19568
|
-
async function VitestPlugin(options = {}, vitest = new Vitest(
|
|
19480
|
+
async function VitestPlugin(options = {}, vitest = new Vitest(deepClone(options))) {
|
|
19569
19481
|
const userConfig = deepMerge({}, options);
|
|
19570
19482
|
async function UIPlugin() {
|
|
19571
19483
|
await vitest.packageInstaller.ensureInstalled("@vitest/ui", resolve$1(options.root || process.cwd()), vitest.version);
|
|
@@ -19638,6 +19550,10 @@ async function VitestPlugin(options = {}, vitest = new Vitest("test", deepClone(
|
|
|
19638
19550
|
legalComments: "inline"
|
|
19639
19551
|
}
|
|
19640
19552
|
};
|
|
19553
|
+
if (vitest._cliOptions.benchmarkOnly) {
|
|
19554
|
+
config.test.benchmark ??= {};
|
|
19555
|
+
config.test.benchmark.enabled = true;
|
|
19556
|
+
}
|
|
19641
19557
|
// inherit so it's available in VitestOptimizer
|
|
19642
19558
|
// I cannot wait to rewrite all of this in Vitest 4
|
|
19643
19559
|
if (options.cache != null) config.test.cache = options.cache;
|
|
@@ -19707,8 +19623,20 @@ async function VitestPlugin(options = {}, vitest = new Vitest("test", deepClone(
|
|
|
19707
19623
|
].filter(notNullish);
|
|
19708
19624
|
}
|
|
19709
19625
|
|
|
19710
|
-
async function createVitest(
|
|
19711
|
-
|
|
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);
|
|
19712
19640
|
const root = slash(resolve$2(options.root || process.cwd()));
|
|
19713
19641
|
const configPath = options.config === false ? false : options.config ? resolveModule(options.config, { paths: [root] }) ?? resolve$2(root, options.config) : any(configFiles, { cwd: root });
|
|
19714
19642
|
options.config = configPath;
|
|
@@ -19716,7 +19644,7 @@ async function createVitest(mode, options, viteOverrides = {}, vitestOptions = {
|
|
|
19716
19644
|
const config = {
|
|
19717
19645
|
configFile: configPath,
|
|
19718
19646
|
configLoader: options.configLoader,
|
|
19719
|
-
mode: options.mode ||
|
|
19647
|
+
mode: options.mode || "test",
|
|
19720
19648
|
plugins: await VitestPlugin(restOptions, ctx)
|
|
19721
19649
|
};
|
|
19722
19650
|
try {
|
|
@@ -20023,15 +19951,25 @@ function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
|
|
|
20023
19951
|
};
|
|
20024
19952
|
}
|
|
20025
19953
|
|
|
20026
|
-
|
|
20027
|
-
|
|
20028
|
-
|
|
20029
|
-
|
|
20030
|
-
|
|
20031
|
-
|
|
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
|
+
}
|
|
20032
19970
|
const root = resolve$1(options.root || process.cwd());
|
|
20033
|
-
const ctx = await prepareVitest(
|
|
20034
|
-
if (
|
|
19971
|
+
const ctx = await prepareVitest(options, viteOverrides, vitestOptions, cliFilters);
|
|
19972
|
+
if (ctx._coverageOptions.enabled) {
|
|
20035
19973
|
const requiredPackages = CoverageProviderMap[ctx._coverageOptions.provider || "v8"];
|
|
20036
19974
|
if (requiredPackages) {
|
|
20037
19975
|
if (!await ctx.packageInstaller.ensureInstalled(requiredPackages, root, ctx.version)) {
|
|
@@ -20079,7 +20017,22 @@ async function startVitest(mode, cliFilters = [], options = {}, viteOverrides, v
|
|
|
20079
20017
|
}
|
|
20080
20018
|
}
|
|
20081
20019
|
}
|
|
20082
|
-
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
|
+
}
|
|
20083
20036
|
process.env.TEST = "true";
|
|
20084
20037
|
process.env.VITEST = "true";
|
|
20085
20038
|
process.env.NODE_ENV ??= "test";
|
|
@@ -20087,7 +20040,7 @@ async function prepareVitest(mode, options = {}, viteOverrides, vitestOptions, c
|
|
|
20087
20040
|
if (options.standalone && (cliFilters?.length || 0) > 0) options.standalone = false;
|
|
20088
20041
|
// this shouldn't affect _application root_ that can be changed inside config
|
|
20089
20042
|
const root = resolve$1(options.root || process.cwd());
|
|
20090
|
-
const ctx = await createVitest(
|
|
20043
|
+
const ctx = await createVitest(options, viteOverrides, vitestOptions);
|
|
20091
20044
|
const environmentPackage = getEnvPackageName(ctx.config.environment);
|
|
20092
20045
|
if (environmentPackage && !await ctx.packageInstaller.ensureInstalled(environmentPackage, root)) {
|
|
20093
20046
|
process.exitCode = 1;
|
|
@@ -20196,4 +20149,4 @@ var cliApi = /*#__PURE__*/Object.freeze({
|
|
|
20196
20149
|
startVitest: startVitest
|
|
20197
20150
|
});
|
|
20198
20151
|
|
|
20199
|
-
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 };
|