vitest 3.1.3 → 3.2.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +0 -232
- package/dist/browser.d.ts +4 -2
- package/dist/browser.js +3 -4
- package/dist/chunks/{base.DslwPSCy.js → base.DwtwORaC.js} +2 -2
- package/dist/chunks/{cac.BN2e7cE1.js → cac.I9MLYfT-.js} +14 -10
- package/dist/chunks/{cli-api.Bti1vevt.js → cli-api.d6IK1pnk.js} +945 -852
- package/dist/chunks/{coverage.87S59-Sl.js → coverage.OGU09Jbh.js} +129 -4216
- package/dist/chunks/{creator.CuL7xDWI.js → creator.DGAdZ4Hj.js} +18 -39
- package/dist/chunks/{environment.d.Dmw5ulng.d.ts → environment.d.D8YDy2v5.d.ts} +2 -1
- package/dist/chunks/{execute.BpmIjFTD.js → execute.JlGHLJZT.js} +3 -5
- package/dist/chunks/{global.d.CXRAxnWc.d.ts → global.d.BPa1eL3O.d.ts} +17 -12
- package/dist/chunks/{globals.CZAEe_Gf.js → globals.CpxW8ccg.js} +2 -3
- package/dist/chunks/{index.Bw6JxgX8.js → index.CK1YOQaa.js} +7 -7
- package/dist/chunks/{index.De2FqGmR.js → index.CV36oG_L.js} +896 -957
- package/dist/chunks/{index.B0uVAVvx.js → index.CfXMNXHg.js} +2 -14
- package/dist/chunks/index.CmC5OK9L.js +275 -0
- package/dist/chunks/{index.Cu2UlluP.js → index.DswW_LEs.js} +2 -2
- package/dist/chunks/{index.DBIGubLC.js → index.X0nbfr6-.js} +7 -7
- package/dist/chunks/{reporters.d.DG9VKi4m.d.ts → reporters.d.CLC9rhKy.d.ts} +68 -11
- package/dist/chunks/{runBaseTests.BV8m0B-u.js → runBaseTests.Dn2vyej_.js} +5 -6
- package/dist/chunks/{setup-common.AQcDs321.js → setup-common.CYo3Y0dD.js} +1 -3
- package/dist/chunks/typechecker.DnTrplSJ.js +897 -0
- package/dist/chunks/{vi.ClIskdbk.js → vi.BFR5YIgu.js} +3 -0
- package/dist/chunks/{vite.d.D3ndlJcw.d.ts → vite.d.CBZ3M_ru.d.ts} +1 -1
- package/dist/chunks/{vm.CuLHT1BG.js → vm.C1HHjtNS.js} +1 -1
- package/dist/chunks/{worker.d.CHGSOG0s.d.ts → worker.d.CoCI7hzP.d.ts} +1 -1
- package/dist/chunks/{worker.d.C-KN07Ls.d.ts → worker.d.D5Xdi-Zr.d.ts} +1 -1
- package/dist/cli.js +20 -1
- package/dist/config.cjs +3 -0
- package/dist/config.d.ts +8 -5
- package/dist/config.js +3 -0
- package/dist/coverage.d.ts +3 -3
- package/dist/coverage.js +4 -7
- package/dist/environments.d.ts +2 -2
- package/dist/execute.d.ts +2 -2
- package/dist/execute.js +1 -1
- package/dist/index.d.ts +45 -32
- package/dist/index.js +2 -3
- package/dist/node.d.ts +8 -8
- package/dist/node.js +16 -18
- package/dist/reporters.d.ts +3 -3
- package/dist/reporters.js +14 -14
- package/dist/runners.d.ts +1 -1
- package/dist/runners.js +2 -2
- package/dist/workers/forks.js +2 -2
- package/dist/workers/runVmTests.js +5 -6
- package/dist/workers/threads.js +2 -2
- package/dist/workers/vmForks.js +2 -2
- package/dist/workers/vmThreads.js +2 -2
- package/dist/workers.d.ts +3 -3
- package/dist/workers.js +3 -3
- package/package.json +15 -19
- package/dist/chunks/run-once.Dimr7O9f.js +0 -47
- package/dist/chunks/typechecker.DYQbn8uK.js +0 -956
- package/dist/chunks/utils.Cc45eY3L.js +0 -200
- package/dist/utils.d.ts +0 -3
- package/dist/utils.js +0 -2
|
@@ -1,540 +1,219 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { performance as performance$1 } from 'node:perf_hooks';
|
|
2
2
|
import { getTestName, hasFailed, getFullName, getTests, getSuites, getTasks } from '@vitest/runner/utils';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
3
|
+
import { slash, toArray, isPrimitive, inspect, positionToOffset, lineSplitRE } from '@vitest/utils';
|
|
4
|
+
import { parseStacktrace, parseErrorStacktrace } from '@vitest/utils/source-map';
|
|
5
|
+
import { isAbsolute, relative, dirname, basename, resolve, normalize } from 'pathe';
|
|
5
6
|
import c from 'tinyrainbow';
|
|
6
|
-
import { t as truncateString, e as errorBanner, F as F_POINTER, d as divider, f as formatTimeString, a as taskFail, b as F_CHECK, g as getStateSymbol, c as formatProjectName, h as F_RIGHT, w as withLabel, 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 } from './utils.Cc45eY3L.js';
|
|
7
|
-
import { stripVTControlCharacters } from 'node:util';
|
|
8
|
-
import { positionToOffset, lineSplitRE, isPrimitive, inspect, toArray, notNullish } from '@vitest/utils';
|
|
9
|
-
import { performance as performance$1 } from 'node:perf_hooks';
|
|
10
|
-
import { parseErrorStacktrace, parseStacktrace } from '@vitest/utils/source-map';
|
|
11
7
|
import { i as isTTY } from './env.Dq0hM4Xv.js';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
8
|
+
import { h as hasFailedSnapshot, g as getOutputFile, T as TypeCheckError } from './typechecker.DnTrplSJ.js';
|
|
9
|
+
import { stripVTControlCharacters } from 'node:util';
|
|
10
|
+
import { existsSync, readFileSync, promises } from 'node:fs';
|
|
11
|
+
import { mkdir, writeFile, readdir, stat, readFile } from 'node:fs/promises';
|
|
14
12
|
import { Console } from 'node:console';
|
|
15
13
|
import { Writable } from 'node:stream';
|
|
16
14
|
import { createRequire } from 'node:module';
|
|
17
15
|
import { hostname } from 'node:os';
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
const ignore = {};
|
|
30
|
-
const object = 'object';
|
|
31
|
-
|
|
32
|
-
const noop = (_, value) => value;
|
|
33
|
-
|
|
34
|
-
const primitives = value => (
|
|
35
|
-
value instanceof Primitive ? Primitive(value) : value
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
const Primitives = (_, value) => (
|
|
39
|
-
typeof value === primitive ? new Primitive(value) : value
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
const revive = (input, parsed, output, $) => {
|
|
43
|
-
const lazy = [];
|
|
44
|
-
for (let ke = keys(output), {length} = ke, y = 0; y < length; y++) {
|
|
45
|
-
const k = ke[y];
|
|
46
|
-
const value = output[k];
|
|
47
|
-
if (value instanceof Primitive) {
|
|
48
|
-
const tmp = input[value];
|
|
49
|
-
if (typeof tmp === object && !parsed.has(tmp)) {
|
|
50
|
-
parsed.add(tmp);
|
|
51
|
-
output[k] = ignore;
|
|
52
|
-
lazy.push({k, a: [input, parsed, tmp, $]});
|
|
53
|
-
}
|
|
54
|
-
else
|
|
55
|
-
output[k] = $.call(output, k, tmp);
|
|
56
|
-
}
|
|
57
|
-
else if (output[k] !== ignore)
|
|
58
|
-
output[k] = $.call(output, k, value);
|
|
59
|
-
}
|
|
60
|
-
for (let {length} = lazy, i = 0; i < length; i++) {
|
|
61
|
-
const {k, a} = lazy[i];
|
|
62
|
-
output[k] = $.call(output, k, revive.apply(null, a));
|
|
63
|
-
}
|
|
64
|
-
return output;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const set = (known, input, value) => {
|
|
68
|
-
const index = Primitive(input.push(value) - 1);
|
|
69
|
-
known.set(value, index);
|
|
70
|
-
return index;
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Converts a specialized flatted string into a JS value.
|
|
75
|
-
* @param {string} text
|
|
76
|
-
* @param {(this: any, key: string, value: any) => any} [reviver]
|
|
77
|
-
* @returns {any}
|
|
78
|
-
*/
|
|
79
|
-
const parse = (text, reviver) => {
|
|
80
|
-
const input = $parse(text, Primitives).map(primitives);
|
|
81
|
-
const value = input[0];
|
|
82
|
-
const $ = reviver || noop;
|
|
83
|
-
const tmp = typeof value === object && value ?
|
|
84
|
-
revive(input, new Set, value, $) :
|
|
85
|
-
value;
|
|
86
|
-
return $.call({'': tmp}, '', tmp);
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Converts a JS value into a specialized flatted string.
|
|
91
|
-
* @param {any} value
|
|
92
|
-
* @param {((this: any, key: string, value: any) => any) | (string | number)[] | null | undefined} [replacer]
|
|
93
|
-
* @param {string | number | undefined} [space]
|
|
94
|
-
* @returns {string}
|
|
95
|
-
*/
|
|
96
|
-
const stringify = (value, replacer, space) => {
|
|
97
|
-
const $ = replacer && typeof replacer === object ?
|
|
98
|
-
(k, v) => (k === '' || -1 < replacer.indexOf(k) ? v : void 0) :
|
|
99
|
-
(replacer || noop);
|
|
100
|
-
const known = new Map;
|
|
101
|
-
const input = [];
|
|
102
|
-
const output = [];
|
|
103
|
-
let i = +set(known, input, $.call({'': value}, '', value));
|
|
104
|
-
let firstRun = !i;
|
|
105
|
-
while (i < input.length) {
|
|
106
|
-
firstRun = true;
|
|
107
|
-
output[i] = $stringify(input[i++], replace, space);
|
|
108
|
-
}
|
|
109
|
-
return '[' + output.join(',') + ']';
|
|
110
|
-
function replace(key, value) {
|
|
111
|
-
if (firstRun) {
|
|
112
|
-
firstRun = !firstRun;
|
|
113
|
-
return value;
|
|
114
|
-
}
|
|
115
|
-
const after = $.call(this, key, value);
|
|
116
|
-
switch (typeof after) {
|
|
117
|
-
case object:
|
|
118
|
-
if (after === null) return after;
|
|
119
|
-
case primitive:
|
|
120
|
-
return known.get(after) || set(known, input, after);
|
|
121
|
-
}
|
|
122
|
-
return after;
|
|
123
|
-
}
|
|
124
|
-
};
|
|
17
|
+
const F_RIGHT = "→";
|
|
18
|
+
const F_DOWN = "↓";
|
|
19
|
+
const F_DOWN_RIGHT = "↳";
|
|
20
|
+
const F_POINTER = "❯";
|
|
21
|
+
const F_DOT = "·";
|
|
22
|
+
const F_CHECK = "✓";
|
|
23
|
+
const F_CROSS = "×";
|
|
24
|
+
const F_LONG_DASH = "⎯";
|
|
25
|
+
const F_TREE_NODE_MIDDLE = "├──";
|
|
26
|
+
const F_TREE_NODE_END = "└──";
|
|
125
27
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
28
|
+
const pointer = c.yellow(F_POINTER);
|
|
29
|
+
const skipped = c.dim(c.gray(F_DOWN));
|
|
30
|
+
const benchmarkPass = c.green(F_DOT);
|
|
31
|
+
const testPass = c.green(F_CHECK);
|
|
32
|
+
const taskFail = c.red(F_CROSS);
|
|
33
|
+
const suiteFail = c.red(F_POINTER);
|
|
34
|
+
const pending$1 = c.gray("·");
|
|
35
|
+
const labelDefaultColors = [
|
|
36
|
+
c.bgYellow,
|
|
37
|
+
c.bgCyan,
|
|
38
|
+
c.bgGreen,
|
|
39
|
+
c.bgMagenta
|
|
40
|
+
];
|
|
41
|
+
function getCols(delta = 0) {
|
|
42
|
+
let length = process.stdout?.columns;
|
|
43
|
+
if (!length || Number.isNaN(length)) {
|
|
44
|
+
length = 30;
|
|
45
|
+
}
|
|
46
|
+
return Math.max(length + delta, 0);
|
|
145
47
|
}
|
|
146
|
-
function
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
frameFilter: project.config.onStackTrace,
|
|
160
|
-
ignoreStackEntries: options.fullStack ? [] : undefined
|
|
161
|
-
});
|
|
48
|
+
function errorBanner(message) {
|
|
49
|
+
return divider(c.bold(c.bgRed(` ${message} `)), null, null, c.red);
|
|
50
|
+
}
|
|
51
|
+
function divider(text, left, right, color) {
|
|
52
|
+
const cols = getCols();
|
|
53
|
+
const c = color || ((text) => text);
|
|
54
|
+
if (text) {
|
|
55
|
+
const textLength = stripVTControlCharacters(text).length;
|
|
56
|
+
if (left == null && right != null) {
|
|
57
|
+
left = cols - textLength - right;
|
|
58
|
+
} else {
|
|
59
|
+
left = left ?? Math.floor((cols - textLength) / 2);
|
|
60
|
+
right = cols - textLength - left;
|
|
162
61
|
}
|
|
163
|
-
|
|
62
|
+
left = Math.max(0, left);
|
|
63
|
+
right = Math.max(0, right);
|
|
64
|
+
return `${c(F_LONG_DASH.repeat(left))}${text}${c(F_LONG_DASH.repeat(right))}`;
|
|
65
|
+
}
|
|
66
|
+
return F_LONG_DASH.repeat(cols);
|
|
164
67
|
}
|
|
165
|
-
function
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
68
|
+
function formatTestPath(root, path) {
|
|
69
|
+
if (isAbsolute(path)) {
|
|
70
|
+
path = relative(root, path);
|
|
71
|
+
}
|
|
72
|
+
const dir = dirname(path);
|
|
73
|
+
const ext = path.match(/(\.(spec|test)\.[cm]?[tj]sx?)$/)?.[0] || "";
|
|
74
|
+
const base = basename(path, ext);
|
|
75
|
+
return slash(c.dim(`${dir}/`) + c.bold(base)) + c.dim(ext);
|
|
76
|
+
}
|
|
77
|
+
function renderSnapshotSummary(rootDir, snapshots) {
|
|
78
|
+
const summary = [];
|
|
79
|
+
if (snapshots.added) {
|
|
80
|
+
summary.push(c.bold(c.green(`${snapshots.added} written`)));
|
|
174
81
|
}
|
|
175
|
-
if (
|
|
176
|
-
|
|
177
|
-
e = {
|
|
178
|
-
message: e ?? error.message,
|
|
179
|
-
stack: error.stack
|
|
180
|
-
};
|
|
82
|
+
if (snapshots.unmatched) {
|
|
83
|
+
summary.push(c.bold(c.red(`${snapshots.unmatched} failed`)));
|
|
181
84
|
}
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
return;
|
|
85
|
+
if (snapshots.updated) {
|
|
86
|
+
summary.push(c.bold(c.green(`${snapshots.updated} updated `)));
|
|
185
87
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
return false;
|
|
88
|
+
if (snapshots.filesRemoved) {
|
|
89
|
+
if (snapshots.didUpdate) {
|
|
90
|
+
summary.push(c.bold(c.green(`${snapshots.filesRemoved} files removed `)));
|
|
91
|
+
} else {
|
|
92
|
+
summary.push(c.bold(c.yellow(`${snapshots.filesRemoved} files obsolete `)));
|
|
192
93
|
}
|
|
193
|
-
});
|
|
194
|
-
if (type) {
|
|
195
|
-
printErrorType(type, project.vitest);
|
|
196
94
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
95
|
+
if (snapshots.filesRemovedList && snapshots.filesRemovedList.length) {
|
|
96
|
+
const [head, ...tail] = snapshots.filesRemovedList;
|
|
97
|
+
summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, head)}`);
|
|
98
|
+
tail.forEach((key) => {
|
|
99
|
+
summary.push(` ${c.gray(F_DOT)} ${formatTestPath(rootDir, key)}`);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
if (snapshots.unchecked) {
|
|
103
|
+
if (snapshots.didUpdate) {
|
|
104
|
+
summary.push(c.bold(c.green(`${snapshots.unchecked} removed`)));
|
|
105
|
+
} else {
|
|
106
|
+
summary.push(c.bold(c.yellow(`${snapshots.unchecked} obsolete`)));
|
|
204
107
|
}
|
|
108
|
+
snapshots.uncheckedKeysByFile.forEach((uncheckedFile) => {
|
|
109
|
+
summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, uncheckedFile.filePath)}`);
|
|
110
|
+
uncheckedFile.keys.forEach((key) => summary.push(` ${c.gray(F_DOT)} ${key}`));
|
|
111
|
+
});
|
|
205
112
|
}
|
|
206
|
-
|
|
207
|
-
|
|
113
|
+
return summary;
|
|
114
|
+
}
|
|
115
|
+
function countTestErrors(tasks) {
|
|
116
|
+
return tasks.reduce((c, i) => c + (i.result?.errors?.length || 0), 0);
|
|
117
|
+
}
|
|
118
|
+
function getStateString$1(tasks, name = "tests", showTotal = true) {
|
|
119
|
+
if (tasks.length === 0) {
|
|
120
|
+
return c.dim(`no ${name}`);
|
|
121
|
+
}
|
|
122
|
+
const passed = tasks.filter((i) => i.result?.state === "pass");
|
|
123
|
+
const failed = tasks.filter((i) => i.result?.state === "fail");
|
|
124
|
+
const skipped = tasks.filter((i) => i.mode === "skip");
|
|
125
|
+
const todo = tasks.filter((i) => i.mode === "todo");
|
|
126
|
+
return [
|
|
127
|
+
failed.length ? c.bold(c.red(`${failed.length} failed`)) : null,
|
|
128
|
+
passed.length ? c.bold(c.green(`${passed.length} passed`)) : null,
|
|
129
|
+
skipped.length ? c.yellow(`${skipped.length} skipped`) : null,
|
|
130
|
+
todo.length ? c.gray(`${todo.length} todo`) : null
|
|
131
|
+
].filter(Boolean).join(c.dim(" | ")) + (showTotal ? c.gray(` (${tasks.length})`) : "");
|
|
132
|
+
}
|
|
133
|
+
function getStateSymbol(task) {
|
|
134
|
+
if (task.mode === "skip" || task.mode === "todo") {
|
|
135
|
+
return skipped;
|
|
208
136
|
}
|
|
209
|
-
if (
|
|
210
|
-
|
|
211
|
-
logger.error([
|
|
212
|
-
err.plugin && ` Plugin: ${c.magenta(err.plugin)}`,
|
|
213
|
-
err.id && ` File: ${c.cyan(err.id)}${err.loc ? `:${err.loc.line}:${err.loc.column}` : ""}`,
|
|
214
|
-
err.frame && c.yellow(err.frame.split(/\r?\n/g).map((l) => ` `.repeat(2) + l).join(`\n`))
|
|
215
|
-
].filter(Boolean).join("\n"));
|
|
137
|
+
if (!task.result) {
|
|
138
|
+
return pending$1;
|
|
216
139
|
}
|
|
217
|
-
if (
|
|
218
|
-
|
|
140
|
+
if (task.result.state === "run" || task.result.state === "queued") {
|
|
141
|
+
if (task.type === "suite") {
|
|
142
|
+
return pointer;
|
|
143
|
+
}
|
|
219
144
|
}
|
|
220
|
-
if (
|
|
221
|
-
|
|
222
|
-
} else {
|
|
223
|
-
const errorProperties = printProperties ? getErrorProperties(e) : {};
|
|
224
|
-
printStack(logger, project, stacks, nearest, errorProperties, (s) => {
|
|
225
|
-
if (showCodeFrame && s === nearest && nearest) {
|
|
226
|
-
const sourceCode = readFileSync(nearest.file, "utf-8");
|
|
227
|
-
logger.error(generateCodeFrame(sourceCode.length > 1e5 ? sourceCode : logger.highlight(nearest.file, sourceCode), 4, s));
|
|
228
|
-
}
|
|
229
|
-
});
|
|
145
|
+
if (task.result.state === "pass") {
|
|
146
|
+
return task.meta?.benchmark ? benchmarkPass : testPass;
|
|
230
147
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const afterEnvTeardown = e.VITEST_AFTER_ENV_TEARDOWN;
|
|
234
|
-
if (testPath) {
|
|
235
|
-
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.`));
|
|
148
|
+
if (task.result.state === "fail") {
|
|
149
|
+
return task.type === "suite" ? suiteFail : taskFail;
|
|
236
150
|
}
|
|
237
|
-
|
|
238
|
-
|
|
151
|
+
return " ";
|
|
152
|
+
}
|
|
153
|
+
function formatTimeString(date) {
|
|
154
|
+
return date.toTimeString().split(" ")[0];
|
|
155
|
+
}
|
|
156
|
+
function formatTime(time) {
|
|
157
|
+
if (time > 1e3) {
|
|
158
|
+
return `${(time / 1e3).toFixed(2)}s`;
|
|
239
159
|
}
|
|
240
|
-
|
|
241
|
-
|
|
160
|
+
return `${Math.round(time)}ms`;
|
|
161
|
+
}
|
|
162
|
+
function formatProjectName(project, suffix = " ") {
|
|
163
|
+
if (!project?.name) {
|
|
164
|
+
return "";
|
|
242
165
|
}
|
|
243
|
-
if (
|
|
244
|
-
|
|
245
|
-
printErrorInner(e.cause, project, {
|
|
246
|
-
showCodeFrame: false,
|
|
247
|
-
logger: options.logger,
|
|
248
|
-
parseErrorStacktrace: options.parseErrorStacktrace
|
|
249
|
-
});
|
|
166
|
+
if (!c.isColorSupported) {
|
|
167
|
+
return `|${project.name}|${suffix}`;
|
|
250
168
|
}
|
|
251
|
-
|
|
252
|
-
|
|
169
|
+
let background = project.color && c[`bg${capitalize(project.color)}`];
|
|
170
|
+
if (!background) {
|
|
171
|
+
const index = project.name.split("").reduce((acc, v, idx) => acc + v.charCodeAt(0) + idx, 0);
|
|
172
|
+
background = labelDefaultColors[index % labelDefaultColors.length];
|
|
173
|
+
}
|
|
174
|
+
return c.black(background(` ${project.name} `)) + suffix;
|
|
253
175
|
}
|
|
254
|
-
function
|
|
255
|
-
|
|
176
|
+
function withLabel(color, label, message) {
|
|
177
|
+
const bgColor = `bg${color.charAt(0).toUpperCase()}${color.slice(1)}`;
|
|
178
|
+
return `${c.bold(c[bgColor](` ${label} `))} ${message ? c[color](message) : ""}`;
|
|
256
179
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
"stack",
|
|
260
|
-
"cause",
|
|
261
|
-
"stacks",
|
|
262
|
-
"stackStr",
|
|
263
|
-
"type",
|
|
264
|
-
"showDiff",
|
|
265
|
-
"ok",
|
|
266
|
-
"operator",
|
|
267
|
-
"diff",
|
|
268
|
-
"codeFrame",
|
|
269
|
-
"actual",
|
|
270
|
-
"expected",
|
|
271
|
-
"diffOptions",
|
|
272
|
-
"sourceURL",
|
|
273
|
-
"column",
|
|
274
|
-
"line",
|
|
275
|
-
"fileName",
|
|
276
|
-
"lineNumber",
|
|
277
|
-
"columnNumber",
|
|
278
|
-
"VITEST_TEST_NAME",
|
|
279
|
-
"VITEST_TEST_PATH",
|
|
280
|
-
"VITEST_AFTER_ENV_TEARDOWN",
|
|
281
|
-
...Object.getOwnPropertyNames(Error.prototype),
|
|
282
|
-
...Object.getOwnPropertyNames(Object.prototype)
|
|
283
|
-
]);
|
|
284
|
-
function getErrorProperties(e) {
|
|
285
|
-
const errorObject = Object.create(null);
|
|
286
|
-
if (e.name === "AssertionError") {
|
|
287
|
-
return errorObject;
|
|
288
|
-
}
|
|
289
|
-
for (const key of Object.getOwnPropertyNames(e)) {
|
|
290
|
-
if (!skipErrorProperties.has(key)) {
|
|
291
|
-
errorObject[key] = e[key];
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
return errorObject;
|
|
295
|
-
}
|
|
296
|
-
const esmErrors = ["Cannot use import statement outside a module", "Unexpected token 'export'"];
|
|
297
|
-
function handleImportOutsideModuleError(stack, logger) {
|
|
298
|
-
if (!esmErrors.some((e) => stack.includes(e))) {
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
const path = normalize(stack.split("\n")[0].trim());
|
|
302
|
-
let name = path.split("/node_modules/").pop() || "";
|
|
303
|
-
if (name?.startsWith("@")) {
|
|
304
|
-
name = name.split("/").slice(0, 2).join("/");
|
|
305
|
-
} else {
|
|
306
|
-
name = name.split("/")[0];
|
|
307
|
-
}
|
|
308
|
-
if (name) {
|
|
309
|
-
printModuleWarningForPackage(logger, path, name);
|
|
310
|
-
} else {
|
|
311
|
-
printModuleWarningForSourceCode(logger, path);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
function printModuleWarningForPackage(logger, path, name) {
|
|
315
|
-
logger.error(c.yellow(`Module ${path} seems to be an ES Module but shipped in a CommonJS package. ` + `You might want to create an issue to the package ${c.bold(`"${name}"`)} asking ` + "them to ship the file in .mjs extension or add \"type\": \"module\" in their package.json." + "\n\n" + "As a temporary workaround you can try to inline the package by updating your config:" + "\n\n" + c.gray(c.dim("// vitest.config.js")) + "\n" + c.green(`export default {
|
|
316
|
-
test: {
|
|
317
|
-
server: {
|
|
318
|
-
deps: {
|
|
319
|
-
inline: [
|
|
320
|
-
${c.yellow(c.bold(`"${name}"`))}
|
|
321
|
-
]
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}\n`)));
|
|
180
|
+
function padSummaryTitle(str) {
|
|
181
|
+
return c.dim(`${str.padStart(11)} `);
|
|
326
182
|
}
|
|
327
|
-
function
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
const errorName = error.name || error.nameStr || "Unknown Error";
|
|
332
|
-
if (!error.message) {
|
|
333
|
-
logger.error(error);
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
if (error.message.length > 5e3) {
|
|
337
|
-
logger.error(`${c.red(c.bold(errorName))}: ${error.message}`);
|
|
338
|
-
} else {
|
|
339
|
-
logger.error(c.red(`${c.bold(errorName)}: ${error.message}`));
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
function printStack(logger, project, stack, highlight, errorProperties, onStack) {
|
|
343
|
-
for (const frame of stack) {
|
|
344
|
-
const color = frame === highlight ? c.cyan : c.gray;
|
|
345
|
-
const path = relative(project.config.root, frame.file);
|
|
346
|
-
logger.error(color(` ${c.dim(F_POINTER)} ${[frame.method, `${path}:${c.dim(`${frame.line}:${frame.column}`)}`].filter(Boolean).join(" ")}`));
|
|
347
|
-
onStack?.(frame);
|
|
348
|
-
}
|
|
349
|
-
if (stack.length) {
|
|
350
|
-
logger.error();
|
|
351
|
-
}
|
|
352
|
-
if (hasProperties(errorProperties)) {
|
|
353
|
-
logger.error(c.red(c.dim(divider())));
|
|
354
|
-
const propertiesString = inspect(errorProperties);
|
|
355
|
-
logger.error(c.red(c.bold("Serialized Error:")), c.gray(propertiesString));
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
function hasProperties(obj) {
|
|
359
|
-
for (const _key in obj) {
|
|
360
|
-
return true;
|
|
361
|
-
}
|
|
362
|
-
return false;
|
|
363
|
-
}
|
|
364
|
-
function generateCodeFrame(source, indent = 0, loc, range = 2) {
|
|
365
|
-
const start = typeof loc === "object" ? positionToOffset(source, loc.line, loc.column) : loc;
|
|
366
|
-
const end = start;
|
|
367
|
-
const lines = source.split(lineSplitRE);
|
|
368
|
-
const nl = /\r\n/.test(source) ? 2 : 1;
|
|
369
|
-
let count = 0;
|
|
370
|
-
let res = [];
|
|
371
|
-
const columns = process.stdout?.columns || 80;
|
|
372
|
-
for (let i = 0; i < lines.length; i++) {
|
|
373
|
-
count += lines[i].length + nl;
|
|
374
|
-
if (count >= start) {
|
|
375
|
-
for (let j = i - range; j <= i + range || end > count; j++) {
|
|
376
|
-
if (j < 0 || j >= lines.length) {
|
|
377
|
-
continue;
|
|
378
|
-
}
|
|
379
|
-
const lineLength = lines[j].length;
|
|
380
|
-
if (stripVTControlCharacters(lines[j]).length > 200) {
|
|
381
|
-
return "";
|
|
382
|
-
}
|
|
383
|
-
res.push(lineNo(j + 1) + truncateString(lines[j].replace(/\t/g, " "), columns - 5 - indent));
|
|
384
|
-
if (j === i) {
|
|
385
|
-
const pad = start - (count - lineLength) + (nl - 1);
|
|
386
|
-
const length = Math.max(1, end > count ? lineLength - pad : end - start);
|
|
387
|
-
res.push(lineNo() + " ".repeat(pad) + c.red("^".repeat(length)));
|
|
388
|
-
} else if (j > i) {
|
|
389
|
-
if (end > count) {
|
|
390
|
-
const length = Math.max(1, Math.min(end - count, lineLength));
|
|
391
|
-
res.push(lineNo() + c.red("^".repeat(length)));
|
|
392
|
-
}
|
|
393
|
-
count += lineLength + 1;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
break;
|
|
397
|
-
}
|
|
183
|
+
function truncateString(text, maxLength) {
|
|
184
|
+
const plainText = stripVTControlCharacters(text);
|
|
185
|
+
if (plainText.length <= maxLength) {
|
|
186
|
+
return text;
|
|
398
187
|
}
|
|
399
|
-
|
|
400
|
-
res = res.map((line) => " ".repeat(indent) + line);
|
|
401
|
-
}
|
|
402
|
-
return res.join("\n");
|
|
188
|
+
return `${plainText.slice(0, maxLength - 1)}…`;
|
|
403
189
|
}
|
|
404
|
-
function
|
|
405
|
-
return
|
|
190
|
+
function capitalize(text) {
|
|
191
|
+
return `${text[0].toUpperCase()}${text.slice(1)}`;
|
|
406
192
|
}
|
|
407
193
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
if (!mod[1].file) {
|
|
432
|
-
return null;
|
|
433
|
-
}
|
|
434
|
-
return [
|
|
435
|
-
mod[0],
|
|
436
|
-
mod[1].file,
|
|
437
|
-
mod[1].url
|
|
438
|
-
];
|
|
439
|
-
}).filter((x) => x != null)];
|
|
440
|
-
});
|
|
441
|
-
const report = [
|
|
442
|
-
this.ctx.version,
|
|
443
|
-
files,
|
|
444
|
-
errors,
|
|
445
|
-
modules,
|
|
446
|
-
coverage,
|
|
447
|
-
executionTime
|
|
448
|
-
];
|
|
449
|
-
const reportFile = resolve(this.ctx.config.root, outputFile);
|
|
450
|
-
await writeBlob(report, reportFile);
|
|
451
|
-
this.ctx.logger.log("blob report written to", reportFile);
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
async function writeBlob(content, filename) {
|
|
455
|
-
const report = stringify(content);
|
|
456
|
-
const dir = dirname(filename);
|
|
457
|
-
if (!existsSync(dir)) {
|
|
458
|
-
await mkdir(dir, { recursive: true });
|
|
459
|
-
}
|
|
460
|
-
await writeFile(filename, report, "utf-8");
|
|
461
|
-
}
|
|
462
|
-
async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
|
|
463
|
-
const resolvedDir = resolve(process.cwd(), blobsDirectory);
|
|
464
|
-
const blobsFiles = await readdir(resolvedDir);
|
|
465
|
-
const promises = blobsFiles.map(async (filename) => {
|
|
466
|
-
const fullPath = resolve(resolvedDir, filename);
|
|
467
|
-
const stats = await stat(fullPath);
|
|
468
|
-
if (!stats.isFile()) {
|
|
469
|
-
throw new TypeError(`vitest.mergeReports() expects all paths in "${blobsDirectory}" to be files generated by the blob reporter, but "${filename}" is not a file`);
|
|
470
|
-
}
|
|
471
|
-
const content = await readFile(fullPath, "utf-8");
|
|
472
|
-
const [version, files, errors, moduleKeys, coverage, executionTime] = parse(content);
|
|
473
|
-
if (!version) {
|
|
474
|
-
throw new TypeError(`vitest.mergeReports() expects all paths in "${blobsDirectory}" to be files generated by the blob reporter, but "${filename}" is not a valid blob file`);
|
|
475
|
-
}
|
|
476
|
-
return {
|
|
477
|
-
version,
|
|
478
|
-
files,
|
|
479
|
-
errors,
|
|
480
|
-
moduleKeys,
|
|
481
|
-
coverage,
|
|
482
|
-
file: filename,
|
|
483
|
-
executionTime
|
|
484
|
-
};
|
|
485
|
-
});
|
|
486
|
-
const blobs = await Promise.all(promises);
|
|
487
|
-
if (!blobs.length) {
|
|
488
|
-
throw new Error(`vitest.mergeReports() requires at least one blob file in "${blobsDirectory}" directory, but none were found`);
|
|
489
|
-
}
|
|
490
|
-
const versions = new Set(blobs.map((blob) => blob.version));
|
|
491
|
-
if (versions.size > 1) {
|
|
492
|
-
throw new Error(`vitest.mergeReports() requires all blob files to be generated by the same Vitest version, received\n\n${blobs.map((b) => `- "${b.file}" uses v${b.version}`).join("\n")}`);
|
|
493
|
-
}
|
|
494
|
-
if (!versions.has(currentVersion)) {
|
|
495
|
-
throw new Error(`the blobs in "${blobsDirectory}" were generated by a different version of Vitest. Expected v${currentVersion}, but received v${blobs[0].version}`);
|
|
496
|
-
}
|
|
497
|
-
const projects = Object.fromEntries(projectsArray.map((p) => [p.name, p]));
|
|
498
|
-
blobs.forEach((blob) => {
|
|
499
|
-
blob.moduleKeys.forEach(([projectName, moduleIds]) => {
|
|
500
|
-
const project = projects[projectName];
|
|
501
|
-
if (!project) {
|
|
502
|
-
return;
|
|
503
|
-
}
|
|
504
|
-
moduleIds.forEach(([moduleId, file, url]) => {
|
|
505
|
-
const moduleNode = project.vite.moduleGraph.createFileOnlyEntry(file);
|
|
506
|
-
moduleNode.url = url;
|
|
507
|
-
moduleNode.id = moduleId;
|
|
508
|
-
project.vite.moduleGraph.idToModuleMap.set(moduleId, moduleNode);
|
|
509
|
-
});
|
|
510
|
-
});
|
|
511
|
-
});
|
|
512
|
-
const files = blobs.flatMap((blob) => blob.files).sort((f1, f2) => {
|
|
513
|
-
const time1 = f1.result?.startTime || 0;
|
|
514
|
-
const time2 = f2.result?.startTime || 0;
|
|
515
|
-
return time1 - time2;
|
|
516
|
-
});
|
|
517
|
-
const errors = blobs.flatMap((blob) => blob.errors);
|
|
518
|
-
const coverages = blobs.map((blob) => blob.coverage);
|
|
519
|
-
const executionTimes = blobs.map((blob) => blob.executionTime);
|
|
520
|
-
return {
|
|
521
|
-
files,
|
|
522
|
-
errors,
|
|
523
|
-
coverages,
|
|
524
|
-
executionTimes
|
|
525
|
-
};
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
class HangingProcessReporter {
|
|
529
|
-
whyRunning;
|
|
530
|
-
onInit() {
|
|
531
|
-
const _require = createRequire(import.meta.url);
|
|
532
|
-
this.whyRunning = _require("why-is-node-running");
|
|
533
|
-
}
|
|
534
|
-
onProcessTimeout() {
|
|
535
|
-
this.whyRunning?.();
|
|
536
|
-
}
|
|
537
|
-
}
|
|
194
|
+
var utils = /*#__PURE__*/Object.freeze({
|
|
195
|
+
__proto__: null,
|
|
196
|
+
benchmarkPass: benchmarkPass,
|
|
197
|
+
countTestErrors: countTestErrors,
|
|
198
|
+
divider: divider,
|
|
199
|
+
errorBanner: errorBanner,
|
|
200
|
+
formatProjectName: formatProjectName,
|
|
201
|
+
formatTestPath: formatTestPath,
|
|
202
|
+
formatTime: formatTime,
|
|
203
|
+
formatTimeString: formatTimeString,
|
|
204
|
+
getStateString: getStateString$1,
|
|
205
|
+
getStateSymbol: getStateSymbol,
|
|
206
|
+
padSummaryTitle: padSummaryTitle,
|
|
207
|
+
pending: pending$1,
|
|
208
|
+
pointer: pointer,
|
|
209
|
+
renderSnapshotSummary: renderSnapshotSummary,
|
|
210
|
+
skipped: skipped,
|
|
211
|
+
suiteFail: suiteFail,
|
|
212
|
+
taskFail: taskFail,
|
|
213
|
+
testPass: testPass,
|
|
214
|
+
truncateString: truncateString,
|
|
215
|
+
withLabel: withLabel
|
|
216
|
+
});
|
|
538
217
|
|
|
539
218
|
const BADGE_PADDING = " ";
|
|
540
219
|
class BaseReporter {
|
|
@@ -687,7 +366,7 @@ class BaseReporter {
|
|
|
687
366
|
title += ` ${c.bgBlue(c.bold(" TS "))}`;
|
|
688
367
|
}
|
|
689
368
|
if (testModule.project.name) {
|
|
690
|
-
title += ` ${formatProjectName(testModule.project
|
|
369
|
+
title += ` ${formatProjectName(testModule.project, "")}`;
|
|
691
370
|
}
|
|
692
371
|
return ` ${title} ${testModule.task.name} ${suffix}`;
|
|
693
372
|
}
|
|
@@ -893,7 +572,8 @@ class BaseReporter {
|
|
|
893
572
|
continue;
|
|
894
573
|
}
|
|
895
574
|
const groupName = getFullName(group, c.dim(" > "));
|
|
896
|
-
this.
|
|
575
|
+
const project = this.ctx.projects.find((p) => p.name === bench.file.projectName);
|
|
576
|
+
this.log(` ${formatProjectName(project)}${bench.name}${c.dim(` - ${groupName}`)}`);
|
|
897
577
|
const siblings = group.tasks.filter((i) => i.meta.benchmark && i.result?.benchmark && i !== bench).sort((a, b) => a.result.benchmark.rank - b.result.benchmark.rank);
|
|
898
578
|
for (const sibling of siblings) {
|
|
899
579
|
const number = (sibling.result.benchmark.mean / bench.result.benchmark.mean).toFixed(2);
|
|
@@ -928,11 +608,12 @@ class BaseReporter {
|
|
|
928
608
|
for (const task of tasks) {
|
|
929
609
|
const filepath = task?.filepath || "";
|
|
930
610
|
const projectName = task?.projectName || task.file?.projectName || "";
|
|
611
|
+
const project = this.ctx.projects.find((p) => p.name === projectName);
|
|
931
612
|
let name = getFullName(task, c.dim(" > "));
|
|
932
613
|
if (filepath) {
|
|
933
614
|
name += c.dim(` [ ${this.relative(filepath)} ]`);
|
|
934
615
|
}
|
|
935
|
-
this.ctx.logger.error(`${c.bgRed(c.bold(" FAIL "))} ${formatProjectName(
|
|
616
|
+
this.ctx.logger.error(`${c.bgRed(c.bold(" FAIL "))} ${formatProjectName(project)}${name}`);
|
|
936
617
|
}
|
|
937
618
|
const screenshotPaths = tasks.map((t) => t.meta?.failScreenshotPath).filter((screenshot) => screenshot != null);
|
|
938
619
|
this.ctx.logger.printError(error, {
|
|
@@ -945,25 +626,252 @@ class BaseReporter {
|
|
|
945
626
|
}
|
|
946
627
|
}
|
|
947
628
|
}
|
|
948
|
-
function sum(items, cb) {
|
|
949
|
-
return items.reduce((total, next) => {
|
|
950
|
-
return total + Math.max(cb(next) || 0, 0);
|
|
951
|
-
}, 0);
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
class BasicReporter extends BaseReporter {
|
|
955
|
-
constructor() {
|
|
956
|
-
super();
|
|
957
|
-
this.isTTY = false;
|
|
629
|
+
function sum(items, cb) {
|
|
630
|
+
return items.reduce((total, next) => {
|
|
631
|
+
return total + Math.max(cb(next) || 0, 0);
|
|
632
|
+
}, 0);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
class BasicReporter extends BaseReporter {
|
|
636
|
+
constructor() {
|
|
637
|
+
super();
|
|
638
|
+
this.isTTY = false;
|
|
639
|
+
}
|
|
640
|
+
onInit(ctx) {
|
|
641
|
+
super.onInit(ctx);
|
|
642
|
+
ctx.logger.deprecate(`'basic' reporter is deprecated and will be removed in Vitest v3.\n` + `Remove 'basic' from 'reporters' option. To match 'basic' reporter 100%, use configuration:\n${JSON.stringify({ test: { reporters: [["default", { summary: false }]] } }, null, 2)}`);
|
|
643
|
+
}
|
|
644
|
+
reportSummary(files, errors) {
|
|
645
|
+
this.ctx.logger.log();
|
|
646
|
+
return super.reportSummary(files, errors);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/// <reference types="../types/index.d.ts" />
|
|
651
|
+
|
|
652
|
+
// (c) 2020-present Andrea Giammarchi
|
|
653
|
+
|
|
654
|
+
const {parse: $parse, stringify: $stringify} = JSON;
|
|
655
|
+
const {keys} = Object;
|
|
656
|
+
|
|
657
|
+
const Primitive = String; // it could be Number
|
|
658
|
+
const primitive = 'string'; // it could be 'number'
|
|
659
|
+
|
|
660
|
+
const ignore = {};
|
|
661
|
+
const object = 'object';
|
|
662
|
+
|
|
663
|
+
const noop = (_, value) => value;
|
|
664
|
+
|
|
665
|
+
const primitives = value => (
|
|
666
|
+
value instanceof Primitive ? Primitive(value) : value
|
|
667
|
+
);
|
|
668
|
+
|
|
669
|
+
const Primitives = (_, value) => (
|
|
670
|
+
typeof value === primitive ? new Primitive(value) : value
|
|
671
|
+
);
|
|
672
|
+
|
|
673
|
+
const revive = (input, parsed, output, $) => {
|
|
674
|
+
const lazy = [];
|
|
675
|
+
for (let ke = keys(output), {length} = ke, y = 0; y < length; y++) {
|
|
676
|
+
const k = ke[y];
|
|
677
|
+
const value = output[k];
|
|
678
|
+
if (value instanceof Primitive) {
|
|
679
|
+
const tmp = input[value];
|
|
680
|
+
if (typeof tmp === object && !parsed.has(tmp)) {
|
|
681
|
+
parsed.add(tmp);
|
|
682
|
+
output[k] = ignore;
|
|
683
|
+
lazy.push({k, a: [input, parsed, tmp, $]});
|
|
684
|
+
}
|
|
685
|
+
else
|
|
686
|
+
output[k] = $.call(output, k, tmp);
|
|
687
|
+
}
|
|
688
|
+
else if (output[k] !== ignore)
|
|
689
|
+
output[k] = $.call(output, k, value);
|
|
690
|
+
}
|
|
691
|
+
for (let {length} = lazy, i = 0; i < length; i++) {
|
|
692
|
+
const {k, a} = lazy[i];
|
|
693
|
+
output[k] = $.call(output, k, revive.apply(null, a));
|
|
694
|
+
}
|
|
695
|
+
return output;
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
const set = (known, input, value) => {
|
|
699
|
+
const index = Primitive(input.push(value) - 1);
|
|
700
|
+
known.set(value, index);
|
|
701
|
+
return index;
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Converts a specialized flatted string into a JS value.
|
|
706
|
+
* @param {string} text
|
|
707
|
+
* @param {(this: any, key: string, value: any) => any} [reviver]
|
|
708
|
+
* @returns {any}
|
|
709
|
+
*/
|
|
710
|
+
const parse = (text, reviver) => {
|
|
711
|
+
const input = $parse(text, Primitives).map(primitives);
|
|
712
|
+
const value = input[0];
|
|
713
|
+
const $ = reviver || noop;
|
|
714
|
+
const tmp = typeof value === object && value ?
|
|
715
|
+
revive(input, new Set, value, $) :
|
|
716
|
+
value;
|
|
717
|
+
return $.call({'': tmp}, '', tmp);
|
|
718
|
+
};
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Converts a JS value into a specialized flatted string.
|
|
722
|
+
* @param {any} value
|
|
723
|
+
* @param {((this: any, key: string, value: any) => any) | (string | number)[] | null | undefined} [replacer]
|
|
724
|
+
* @param {string | number | undefined} [space]
|
|
725
|
+
* @returns {string}
|
|
726
|
+
*/
|
|
727
|
+
const stringify = (value, replacer, space) => {
|
|
728
|
+
const $ = replacer && typeof replacer === object ?
|
|
729
|
+
(k, v) => (k === '' || -1 < replacer.indexOf(k) ? v : void 0) :
|
|
730
|
+
(replacer || noop);
|
|
731
|
+
const known = new Map;
|
|
732
|
+
const input = [];
|
|
733
|
+
const output = [];
|
|
734
|
+
let i = +set(known, input, $.call({'': value}, '', value));
|
|
735
|
+
let firstRun = !i;
|
|
736
|
+
while (i < input.length) {
|
|
737
|
+
firstRun = true;
|
|
738
|
+
output[i] = $stringify(input[i++], replace, space);
|
|
739
|
+
}
|
|
740
|
+
return '[' + output.join(',') + ']';
|
|
741
|
+
function replace(key, value) {
|
|
742
|
+
if (firstRun) {
|
|
743
|
+
firstRun = !firstRun;
|
|
744
|
+
return value;
|
|
745
|
+
}
|
|
746
|
+
const after = $.call(this, key, value);
|
|
747
|
+
switch (typeof after) {
|
|
748
|
+
case object:
|
|
749
|
+
if (after === null) return after;
|
|
750
|
+
case primitive:
|
|
751
|
+
return known.get(after) || set(known, input, after);
|
|
752
|
+
}
|
|
753
|
+
return after;
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
class BlobReporter {
|
|
758
|
+
start = 0;
|
|
759
|
+
ctx;
|
|
760
|
+
options;
|
|
761
|
+
constructor(options) {
|
|
762
|
+
this.options = options;
|
|
763
|
+
}
|
|
764
|
+
onInit(ctx) {
|
|
765
|
+
if (ctx.config.watch) {
|
|
766
|
+
throw new Error("Blob reporter is not supported in watch mode");
|
|
767
|
+
}
|
|
768
|
+
this.ctx = ctx;
|
|
769
|
+
this.start = performance.now();
|
|
770
|
+
}
|
|
771
|
+
async onFinished(files = [], errors = [], coverage) {
|
|
772
|
+
const executionTime = performance.now() - this.start;
|
|
773
|
+
let outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "blob");
|
|
774
|
+
if (!outputFile) {
|
|
775
|
+
const shard = this.ctx.config.shard;
|
|
776
|
+
outputFile = shard ? `.vitest-reports/blob-${shard.index}-${shard.count}.json` : ".vitest-reports/blob.json";
|
|
777
|
+
}
|
|
778
|
+
const modules = this.ctx.projects.map((project) => {
|
|
779
|
+
return [project.name, [...project.vite.moduleGraph.idToModuleMap.entries()].map((mod) => {
|
|
780
|
+
if (!mod[1].file) {
|
|
781
|
+
return null;
|
|
782
|
+
}
|
|
783
|
+
return [
|
|
784
|
+
mod[0],
|
|
785
|
+
mod[1].file,
|
|
786
|
+
mod[1].url
|
|
787
|
+
];
|
|
788
|
+
}).filter((x) => x != null)];
|
|
789
|
+
});
|
|
790
|
+
const report = [
|
|
791
|
+
this.ctx.version,
|
|
792
|
+
files,
|
|
793
|
+
errors,
|
|
794
|
+
modules,
|
|
795
|
+
coverage,
|
|
796
|
+
executionTime
|
|
797
|
+
];
|
|
798
|
+
const reportFile = resolve(this.ctx.config.root, outputFile);
|
|
799
|
+
await writeBlob(report, reportFile);
|
|
800
|
+
this.ctx.logger.log("blob report written to", reportFile);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
async function writeBlob(content, filename) {
|
|
804
|
+
const report = stringify(content);
|
|
805
|
+
const dir = dirname(filename);
|
|
806
|
+
if (!existsSync(dir)) {
|
|
807
|
+
await mkdir(dir, { recursive: true });
|
|
808
|
+
}
|
|
809
|
+
await writeFile(filename, report, "utf-8");
|
|
810
|
+
}
|
|
811
|
+
async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
|
|
812
|
+
const resolvedDir = resolve(process.cwd(), blobsDirectory);
|
|
813
|
+
const blobsFiles = await readdir(resolvedDir);
|
|
814
|
+
const promises = blobsFiles.map(async (filename) => {
|
|
815
|
+
const fullPath = resolve(resolvedDir, filename);
|
|
816
|
+
const stats = await stat(fullPath);
|
|
817
|
+
if (!stats.isFile()) {
|
|
818
|
+
throw new TypeError(`vitest.mergeReports() expects all paths in "${blobsDirectory}" to be files generated by the blob reporter, but "${filename}" is not a file`);
|
|
819
|
+
}
|
|
820
|
+
const content = await readFile(fullPath, "utf-8");
|
|
821
|
+
const [version, files, errors, moduleKeys, coverage, executionTime] = parse(content);
|
|
822
|
+
if (!version) {
|
|
823
|
+
throw new TypeError(`vitest.mergeReports() expects all paths in "${blobsDirectory}" to be files generated by the blob reporter, but "${filename}" is not a valid blob file`);
|
|
824
|
+
}
|
|
825
|
+
return {
|
|
826
|
+
version,
|
|
827
|
+
files,
|
|
828
|
+
errors,
|
|
829
|
+
moduleKeys,
|
|
830
|
+
coverage,
|
|
831
|
+
file: filename,
|
|
832
|
+
executionTime
|
|
833
|
+
};
|
|
834
|
+
});
|
|
835
|
+
const blobs = await Promise.all(promises);
|
|
836
|
+
if (!blobs.length) {
|
|
837
|
+
throw new Error(`vitest.mergeReports() requires at least one blob file in "${blobsDirectory}" directory, but none were found`);
|
|
958
838
|
}
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
839
|
+
const versions = new Set(blobs.map((blob) => blob.version));
|
|
840
|
+
if (versions.size > 1) {
|
|
841
|
+
throw new Error(`vitest.mergeReports() requires all blob files to be generated by the same Vitest version, received\n\n${blobs.map((b) => `- "${b.file}" uses v${b.version}`).join("\n")}`);
|
|
962
842
|
}
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
return super.reportSummary(files, errors);
|
|
843
|
+
if (!versions.has(currentVersion)) {
|
|
844
|
+
throw new Error(`the blobs in "${blobsDirectory}" were generated by a different version of Vitest. Expected v${currentVersion}, but received v${blobs[0].version}`);
|
|
966
845
|
}
|
|
846
|
+
const projects = Object.fromEntries(projectsArray.map((p) => [p.name, p]));
|
|
847
|
+
blobs.forEach((blob) => {
|
|
848
|
+
blob.moduleKeys.forEach(([projectName, moduleIds]) => {
|
|
849
|
+
const project = projects[projectName];
|
|
850
|
+
if (!project) {
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
moduleIds.forEach(([moduleId, file, url]) => {
|
|
854
|
+
const moduleNode = project.vite.moduleGraph.createFileOnlyEntry(file);
|
|
855
|
+
moduleNode.url = url;
|
|
856
|
+
moduleNode.id = moduleId;
|
|
857
|
+
project.vite.moduleGraph.idToModuleMap.set(moduleId, moduleNode);
|
|
858
|
+
});
|
|
859
|
+
});
|
|
860
|
+
});
|
|
861
|
+
const files = blobs.flatMap((blob) => blob.files).sort((f1, f2) => {
|
|
862
|
+
const time1 = f1.result?.startTime || 0;
|
|
863
|
+
const time2 = f2.result?.startTime || 0;
|
|
864
|
+
return time1 - time2;
|
|
865
|
+
});
|
|
866
|
+
const errors = blobs.flatMap((blob) => blob.errors);
|
|
867
|
+
const coverages = blobs.map((blob) => blob.coverage);
|
|
868
|
+
const executionTimes = blobs.map((blob) => blob.executionTime);
|
|
869
|
+
return {
|
|
870
|
+
files,
|
|
871
|
+
errors,
|
|
872
|
+
coverages,
|
|
873
|
+
executionTimes
|
|
874
|
+
};
|
|
967
875
|
}
|
|
968
876
|
|
|
969
877
|
const DEFAULT_RENDER_INTERVAL_MS = 1e3;
|
|
@@ -1292,7 +1200,10 @@ class SummaryReporter {
|
|
|
1292
1200
|
const summary = [""];
|
|
1293
1201
|
for (const testFile of Array.from(this.runningModules.values()).sort(sortRunningModules)) {
|
|
1294
1202
|
const typecheck = testFile.typecheck ? `${c.bgBlue(c.bold(" TS "))} ` : "";
|
|
1295
|
-
summary.push(c.bold(c.yellow(` ${F_POINTER} `)) + formatProjectName(
|
|
1203
|
+
summary.push(c.bold(c.yellow(` ${F_POINTER} `)) + formatProjectName({
|
|
1204
|
+
name: testFile.projectName,
|
|
1205
|
+
color: testFile.projectColor
|
|
1206
|
+
}) + typecheck + testFile.filename + c.dim(!testFile.completed && !testFile.total ? " [queued]" : ` ${testFile.completed}/${testFile.total}`));
|
|
1296
1207
|
const slowTasks = [testFile.hook, ...Array.from(testFile.tests.values())].filter((t) => t != null && t.visible);
|
|
1297
1208
|
for (const [index, task] of slowTasks.entries()) {
|
|
1298
1209
|
const elapsed = this.currentTime - task.startTime;
|
|
@@ -1355,207 +1266,490 @@ function sortRunningModules(a, b) {
|
|
|
1355
1266
|
if ((a.projectName || "") > (b.projectName || "")) {
|
|
1356
1267
|
return 1;
|
|
1357
1268
|
}
|
|
1358
|
-
if ((a.projectName || "") < (b.projectName || "")) {
|
|
1359
|
-
return -1;
|
|
1269
|
+
if ((a.projectName || "") < (b.projectName || "")) {
|
|
1270
|
+
return -1;
|
|
1271
|
+
}
|
|
1272
|
+
return a.filename.localeCompare(b.filename);
|
|
1273
|
+
}
|
|
1274
|
+
function initializeStats(module) {
|
|
1275
|
+
return {
|
|
1276
|
+
total: 0,
|
|
1277
|
+
completed: 0,
|
|
1278
|
+
filename: module.task.name,
|
|
1279
|
+
projectName: module.project.name,
|
|
1280
|
+
projectColor: module.project.color,
|
|
1281
|
+
tests: new Map(),
|
|
1282
|
+
typecheck: !!module.task.meta.typecheck
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
class DefaultReporter extends BaseReporter {
|
|
1287
|
+
options;
|
|
1288
|
+
summary;
|
|
1289
|
+
constructor(options = {}) {
|
|
1290
|
+
super(options);
|
|
1291
|
+
this.options = {
|
|
1292
|
+
summary: true,
|
|
1293
|
+
...options
|
|
1294
|
+
};
|
|
1295
|
+
if (!this.isTTY) {
|
|
1296
|
+
this.options.summary = false;
|
|
1297
|
+
}
|
|
1298
|
+
if (this.options.summary) {
|
|
1299
|
+
this.summary = new SummaryReporter();
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
onTestRunStart(specifications) {
|
|
1303
|
+
this.summary?.onTestRunStart(specifications);
|
|
1304
|
+
}
|
|
1305
|
+
onTestModuleQueued(file) {
|
|
1306
|
+
this.summary?.onTestModuleQueued(file);
|
|
1307
|
+
}
|
|
1308
|
+
onTestModuleCollected(module) {
|
|
1309
|
+
this.summary?.onTestModuleCollected(module);
|
|
1310
|
+
}
|
|
1311
|
+
onTestModuleEnd(module) {
|
|
1312
|
+
super.onTestModuleEnd(module);
|
|
1313
|
+
this.summary?.onTestModuleEnd(module);
|
|
1314
|
+
}
|
|
1315
|
+
onTestCaseReady(test) {
|
|
1316
|
+
this.summary?.onTestCaseReady(test);
|
|
1317
|
+
}
|
|
1318
|
+
onTestCaseResult(test) {
|
|
1319
|
+
super.onTestCaseResult(test);
|
|
1320
|
+
this.summary?.onTestCaseResult(test);
|
|
1321
|
+
}
|
|
1322
|
+
onHookStart(hook) {
|
|
1323
|
+
this.summary?.onHookStart(hook);
|
|
1324
|
+
}
|
|
1325
|
+
onHookEnd(hook) {
|
|
1326
|
+
this.summary?.onHookEnd(hook);
|
|
1327
|
+
}
|
|
1328
|
+
onInit(ctx) {
|
|
1329
|
+
super.onInit(ctx);
|
|
1330
|
+
this.summary?.onInit(ctx, { verbose: this.verbose });
|
|
1331
|
+
}
|
|
1332
|
+
onPathsCollected(paths = []) {
|
|
1333
|
+
if (this.isTTY) {
|
|
1334
|
+
if (this.renderSucceed === undefined) {
|
|
1335
|
+
this.renderSucceed = !!this.renderSucceed;
|
|
1336
|
+
}
|
|
1337
|
+
if (this.renderSucceed !== true) {
|
|
1338
|
+
this.renderSucceed = paths.length <= 1;
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
onTestRunEnd() {
|
|
1343
|
+
this.summary?.onTestRunEnd();
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
class DotReporter extends BaseReporter {
|
|
1348
|
+
renderer;
|
|
1349
|
+
tests = new Map();
|
|
1350
|
+
finishedTests = new Set();
|
|
1351
|
+
onInit(ctx) {
|
|
1352
|
+
super.onInit(ctx);
|
|
1353
|
+
if (this.isTTY) {
|
|
1354
|
+
this.renderer = new WindowRenderer({
|
|
1355
|
+
logger: ctx.logger,
|
|
1356
|
+
getWindow: () => this.createSummary()
|
|
1357
|
+
});
|
|
1358
|
+
this.ctx.onClose(() => this.renderer?.stop());
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
printTestModule(testModule) {
|
|
1362
|
+
if (!this.isTTY) {
|
|
1363
|
+
super.printTestModule(testModule);
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
onWatcherRerun(files, trigger) {
|
|
1367
|
+
this.tests.clear();
|
|
1368
|
+
this.renderer?.start();
|
|
1369
|
+
super.onWatcherRerun(files, trigger);
|
|
1370
|
+
}
|
|
1371
|
+
onFinished(files, errors) {
|
|
1372
|
+
if (this.isTTY) {
|
|
1373
|
+
const finalLog = formatTests(Array.from(this.tests.values()));
|
|
1374
|
+
this.ctx.logger.log(finalLog);
|
|
1375
|
+
}
|
|
1376
|
+
this.tests.clear();
|
|
1377
|
+
this.renderer?.finish();
|
|
1378
|
+
super.onFinished(files, errors);
|
|
1379
|
+
}
|
|
1380
|
+
onTestModuleCollected(module) {
|
|
1381
|
+
for (const test of module.children.allTests()) {
|
|
1382
|
+
this.onTestCaseReady(test);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
onTestCaseReady(test) {
|
|
1386
|
+
if (this.finishedTests.has(test.id)) {
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
this.tests.set(test.id, test.result().state || "run");
|
|
1390
|
+
this.renderer?.schedule();
|
|
1391
|
+
}
|
|
1392
|
+
onTestCaseResult(test) {
|
|
1393
|
+
super.onTestCaseResult(test);
|
|
1394
|
+
this.finishedTests.add(test.id);
|
|
1395
|
+
this.tests.set(test.id, test.result().state || "skipped");
|
|
1396
|
+
this.renderer?.schedule();
|
|
1397
|
+
}
|
|
1398
|
+
onTestModuleEnd(testModule) {
|
|
1399
|
+
super.onTestModuleEnd(testModule);
|
|
1400
|
+
if (!this.isTTY) {
|
|
1401
|
+
return;
|
|
1402
|
+
}
|
|
1403
|
+
const columns = this.ctx.logger.getColumns();
|
|
1404
|
+
if (this.tests.size < columns) {
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
const finishedTests = Array.from(this.tests).filter((entry) => entry[1] !== "pending");
|
|
1408
|
+
if (finishedTests.length < columns) {
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
const states = [];
|
|
1412
|
+
let count = 0;
|
|
1413
|
+
for (const [id, state] of finishedTests) {
|
|
1414
|
+
if (count++ >= columns) {
|
|
1415
|
+
break;
|
|
1416
|
+
}
|
|
1417
|
+
this.tests.delete(id);
|
|
1418
|
+
states.push(state);
|
|
1419
|
+
}
|
|
1420
|
+
this.ctx.logger.log(formatTests(states));
|
|
1421
|
+
this.renderer?.schedule();
|
|
1422
|
+
}
|
|
1423
|
+
createSummary() {
|
|
1424
|
+
return [formatTests(Array.from(this.tests.values())), ""];
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
const pass = {
|
|
1428
|
+
char: "·",
|
|
1429
|
+
color: c.green
|
|
1430
|
+
};
|
|
1431
|
+
const fail = {
|
|
1432
|
+
char: "x",
|
|
1433
|
+
color: c.red
|
|
1434
|
+
};
|
|
1435
|
+
const pending = {
|
|
1436
|
+
char: "*",
|
|
1437
|
+
color: c.yellow
|
|
1438
|
+
};
|
|
1439
|
+
const skip = {
|
|
1440
|
+
char: "-",
|
|
1441
|
+
color: (char) => c.dim(c.gray(char))
|
|
1442
|
+
};
|
|
1443
|
+
function getIcon(state) {
|
|
1444
|
+
switch (state) {
|
|
1445
|
+
case "passed": return pass;
|
|
1446
|
+
case "failed": return fail;
|
|
1447
|
+
case "skipped": return skip;
|
|
1448
|
+
default: return pending;
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
/**
|
|
1452
|
+
* Format test states into string while keeping ANSI escapes at minimal.
|
|
1453
|
+
* Sibling icons with same color are merged into a single c.color() call.
|
|
1454
|
+
*/
|
|
1455
|
+
function formatTests(states) {
|
|
1456
|
+
let currentIcon = pending;
|
|
1457
|
+
let count = 0;
|
|
1458
|
+
let output = "";
|
|
1459
|
+
for (const state of states) {
|
|
1460
|
+
const icon = getIcon(state);
|
|
1461
|
+
if (currentIcon === icon) {
|
|
1462
|
+
count++;
|
|
1463
|
+
continue;
|
|
1464
|
+
}
|
|
1465
|
+
output += currentIcon.color(currentIcon.char.repeat(count));
|
|
1466
|
+
count = 1;
|
|
1467
|
+
currentIcon = icon;
|
|
1360
1468
|
}
|
|
1361
|
-
|
|
1469
|
+
output += currentIcon.color(currentIcon.char.repeat(count));
|
|
1470
|
+
return output;
|
|
1362
1471
|
}
|
|
1363
|
-
|
|
1472
|
+
|
|
1473
|
+
function capturePrintError(error, ctx, options) {
|
|
1474
|
+
let output = "";
|
|
1475
|
+
const writable = new Writable({ write(chunk, _encoding, callback) {
|
|
1476
|
+
output += String(chunk);
|
|
1477
|
+
callback();
|
|
1478
|
+
} });
|
|
1479
|
+
const console = new Console(writable);
|
|
1480
|
+
const logger = {
|
|
1481
|
+
error: console.error.bind(console),
|
|
1482
|
+
highlight: ctx.logger.highlight.bind(ctx.logger)
|
|
1483
|
+
};
|
|
1484
|
+
const result = printError(error, ctx, logger, {
|
|
1485
|
+
showCodeFrame: false,
|
|
1486
|
+
...options
|
|
1487
|
+
});
|
|
1364
1488
|
return {
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
filename: module.task.name,
|
|
1368
|
-
projectName: module.project.name,
|
|
1369
|
-
tests: new Map(),
|
|
1370
|
-
typecheck: !!module.task.meta.typecheck
|
|
1489
|
+
nearest: result?.nearest,
|
|
1490
|
+
output
|
|
1371
1491
|
};
|
|
1372
1492
|
}
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1493
|
+
function printError(error, ctx, logger, options) {
|
|
1494
|
+
const project = options.project ?? ctx.coreWorkspaceProject ?? ctx.projects[0];
|
|
1495
|
+
return printErrorInner(error, project, {
|
|
1496
|
+
logger,
|
|
1497
|
+
type: options.type,
|
|
1498
|
+
showCodeFrame: options.showCodeFrame,
|
|
1499
|
+
screenshotPaths: options.screenshotPaths,
|
|
1500
|
+
printProperties: options.verbose,
|
|
1501
|
+
parseErrorStacktrace(error) {
|
|
1502
|
+
if (options.task?.file.pool === "browser" && project.browser) {
|
|
1503
|
+
return project.browser.parseErrorStacktrace(error, { ignoreStackEntries: options.fullStack ? [] : undefined });
|
|
1504
|
+
}
|
|
1505
|
+
return parseErrorStacktrace(error, {
|
|
1506
|
+
frameFilter: project.config.onStackTrace,
|
|
1507
|
+
ignoreStackEntries: options.fullStack ? [] : undefined
|
|
1508
|
+
});
|
|
1388
1509
|
}
|
|
1510
|
+
});
|
|
1511
|
+
}
|
|
1512
|
+
function printErrorInner(error, project, options) {
|
|
1513
|
+
const { showCodeFrame = true, type, printProperties = true } = options;
|
|
1514
|
+
const logger = options.logger;
|
|
1515
|
+
let e = error;
|
|
1516
|
+
if (isPrimitive(e)) {
|
|
1517
|
+
e = {
|
|
1518
|
+
message: String(error).split(/\n/g)[0],
|
|
1519
|
+
stack: String(error)
|
|
1520
|
+
};
|
|
1389
1521
|
}
|
|
1390
|
-
|
|
1391
|
-
|
|
1522
|
+
if (!e) {
|
|
1523
|
+
const error = new Error("unknown error");
|
|
1524
|
+
e = {
|
|
1525
|
+
message: e ?? error.message,
|
|
1526
|
+
stack: error.stack
|
|
1527
|
+
};
|
|
1392
1528
|
}
|
|
1393
|
-
|
|
1394
|
-
|
|
1529
|
+
if (!project) {
|
|
1530
|
+
printErrorMessage(e, logger);
|
|
1531
|
+
return;
|
|
1395
1532
|
}
|
|
1396
|
-
|
|
1397
|
-
|
|
1533
|
+
const stacks = options.parseErrorStacktrace(e);
|
|
1534
|
+
const nearest = error instanceof TypeCheckError ? error.stacks[0] : stacks.find((stack) => {
|
|
1535
|
+
try {
|
|
1536
|
+
return project.server && project.getModuleById(stack.file) && existsSync(stack.file);
|
|
1537
|
+
} catch {
|
|
1538
|
+
return false;
|
|
1539
|
+
}
|
|
1540
|
+
});
|
|
1541
|
+
if (type) {
|
|
1542
|
+
printErrorType(type, project.vitest);
|
|
1398
1543
|
}
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1544
|
+
printErrorMessage(e, logger);
|
|
1545
|
+
if (options.screenshotPaths?.length) {
|
|
1546
|
+
const length = options.screenshotPaths.length;
|
|
1547
|
+
logger.error(`\nFailure screenshot${length > 1 ? "s" : ""}:`);
|
|
1548
|
+
logger.error(options.screenshotPaths.map((p) => ` - ${c.dim(relative(process.cwd(), p))}`).join("\n"));
|
|
1549
|
+
if (!e.diff) {
|
|
1550
|
+
logger.error();
|
|
1551
|
+
}
|
|
1402
1552
|
}
|
|
1403
|
-
|
|
1404
|
-
|
|
1553
|
+
if (e.codeFrame) {
|
|
1554
|
+
logger.error(`${e.codeFrame}\n`);
|
|
1405
1555
|
}
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1556
|
+
if ("__vitest_rollup_error__" in e) {
|
|
1557
|
+
const err = e.__vitest_rollup_error__;
|
|
1558
|
+
logger.error([
|
|
1559
|
+
err.plugin && ` Plugin: ${c.magenta(err.plugin)}`,
|
|
1560
|
+
err.id && ` File: ${c.cyan(err.id)}${err.loc ? `:${err.loc.line}:${err.loc.column}` : ""}`,
|
|
1561
|
+
err.frame && c.yellow(err.frame.split(/\r?\n/g).map((l) => ` `.repeat(2) + l).join(`\n`))
|
|
1562
|
+
].filter(Boolean).join("\n"));
|
|
1409
1563
|
}
|
|
1410
|
-
|
|
1411
|
-
|
|
1564
|
+
if (e.diff) {
|
|
1565
|
+
logger.error(`\n${e.diff}\n`);
|
|
1412
1566
|
}
|
|
1413
|
-
|
|
1414
|
-
|
|
1567
|
+
if (e.frame) {
|
|
1568
|
+
logger.error(c.yellow(e.frame));
|
|
1569
|
+
} else {
|
|
1570
|
+
const errorProperties = printProperties ? getErrorProperties(e) : {};
|
|
1571
|
+
printStack(logger, project, stacks, nearest, errorProperties, (s) => {
|
|
1572
|
+
if (showCodeFrame && s === nearest && nearest) {
|
|
1573
|
+
const sourceCode = readFileSync(nearest.file, "utf-8");
|
|
1574
|
+
logger.error(generateCodeFrame(sourceCode.length > 1e5 ? sourceCode : logger.highlight(nearest.file, sourceCode), 4, s));
|
|
1575
|
+
}
|
|
1576
|
+
});
|
|
1415
1577
|
}
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1578
|
+
const testPath = e.VITEST_TEST_PATH;
|
|
1579
|
+
const testName = e.VITEST_TEST_NAME;
|
|
1580
|
+
const afterEnvTeardown = e.VITEST_AFTER_ENV_TEARDOWN;
|
|
1581
|
+
if (testPath) {
|
|
1582
|
+
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.`));
|
|
1419
1583
|
}
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
if (this.renderSucceed === undefined) {
|
|
1423
|
-
this.renderSucceed = !!this.renderSucceed;
|
|
1424
|
-
}
|
|
1425
|
-
if (this.renderSucceed !== true) {
|
|
1426
|
-
this.renderSucceed = paths.length <= 1;
|
|
1427
|
-
}
|
|
1428
|
-
}
|
|
1584
|
+
if (testName) {
|
|
1585
|
+
logger.error(c.red(`The latest test that might've caused the error is "${c.bold(testName)}". It might mean one of the following:` + "\n- The error was thrown, while Vitest was running this test." + "\n- If the error occurred after the test had been completed, this was the last documented test before it was thrown."));
|
|
1429
1586
|
}
|
|
1430
|
-
|
|
1431
|
-
|
|
1587
|
+
if (afterEnvTeardown) {
|
|
1588
|
+
logger.error(c.red("This error was caught after test environment was torn down. Make sure to cancel any running tasks before test finishes:" + "\n- cancel timeouts using clearTimeout and clearInterval" + "\n- wait for promises to resolve using the await keyword"));
|
|
1589
|
+
}
|
|
1590
|
+
if (typeof e.cause === "object" && e.cause && "name" in e.cause) {
|
|
1591
|
+
e.cause.name = `Caused by: ${e.cause.name}`;
|
|
1592
|
+
printErrorInner(e.cause, project, {
|
|
1593
|
+
showCodeFrame: false,
|
|
1594
|
+
logger: options.logger,
|
|
1595
|
+
parseErrorStacktrace: options.parseErrorStacktrace
|
|
1596
|
+
});
|
|
1432
1597
|
}
|
|
1598
|
+
handleImportOutsideModuleError(e.stack || e.stackStr || "", logger);
|
|
1599
|
+
return { nearest };
|
|
1433
1600
|
}
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1601
|
+
function printErrorType(type, ctx) {
|
|
1602
|
+
ctx.logger.error(`\n${errorBanner(type)}`);
|
|
1603
|
+
}
|
|
1604
|
+
const skipErrorProperties = new Set([
|
|
1605
|
+
"nameStr",
|
|
1606
|
+
"stack",
|
|
1607
|
+
"cause",
|
|
1608
|
+
"stacks",
|
|
1609
|
+
"stackStr",
|
|
1610
|
+
"type",
|
|
1611
|
+
"showDiff",
|
|
1612
|
+
"ok",
|
|
1613
|
+
"operator",
|
|
1614
|
+
"diff",
|
|
1615
|
+
"codeFrame",
|
|
1616
|
+
"actual",
|
|
1617
|
+
"expected",
|
|
1618
|
+
"diffOptions",
|
|
1619
|
+
"sourceURL",
|
|
1620
|
+
"column",
|
|
1621
|
+
"line",
|
|
1622
|
+
"fileName",
|
|
1623
|
+
"lineNumber",
|
|
1624
|
+
"columnNumber",
|
|
1625
|
+
"VITEST_TEST_NAME",
|
|
1626
|
+
"VITEST_TEST_PATH",
|
|
1627
|
+
"VITEST_AFTER_ENV_TEARDOWN",
|
|
1628
|
+
...Object.getOwnPropertyNames(Error.prototype),
|
|
1629
|
+
...Object.getOwnPropertyNames(Object.prototype)
|
|
1630
|
+
]);
|
|
1631
|
+
function getErrorProperties(e) {
|
|
1632
|
+
const errorObject = Object.create(null);
|
|
1633
|
+
if (e.name === "AssertionError") {
|
|
1634
|
+
return errorObject;
|
|
1448
1635
|
}
|
|
1449
|
-
|
|
1450
|
-
if (!
|
|
1451
|
-
|
|
1636
|
+
for (const key of Object.getOwnPropertyNames(e)) {
|
|
1637
|
+
if (!skipErrorProperties.has(key)) {
|
|
1638
|
+
errorObject[key] = e[key];
|
|
1452
1639
|
}
|
|
1453
1640
|
}
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1641
|
+
return errorObject;
|
|
1642
|
+
}
|
|
1643
|
+
const esmErrors = ["Cannot use import statement outside a module", "Unexpected token 'export'"];
|
|
1644
|
+
function handleImportOutsideModuleError(stack, logger) {
|
|
1645
|
+
if (!esmErrors.some((e) => stack.includes(e))) {
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
const path = normalize(stack.split("\n")[0].trim());
|
|
1649
|
+
let name = path.split("/node_modules/").pop() || "";
|
|
1650
|
+
if (name?.startsWith("@")) {
|
|
1651
|
+
name = name.split("/").slice(0, 2).join("/");
|
|
1652
|
+
} else {
|
|
1653
|
+
name = name.split("/")[0];
|
|
1458
1654
|
}
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
}
|
|
1464
|
-
this.tests.clear();
|
|
1465
|
-
this.renderer?.finish();
|
|
1466
|
-
super.onFinished(files, errors);
|
|
1655
|
+
if (name) {
|
|
1656
|
+
printModuleWarningForPackage(logger, path, name);
|
|
1657
|
+
} else {
|
|
1658
|
+
printModuleWarningForSourceCode(logger, path);
|
|
1467
1659
|
}
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1660
|
+
}
|
|
1661
|
+
function printModuleWarningForPackage(logger, path, name) {
|
|
1662
|
+
logger.error(c.yellow(`Module ${path} seems to be an ES Module but shipped in a CommonJS package. ` + `You might want to create an issue to the package ${c.bold(`"${name}"`)} asking ` + "them to ship the file in .mjs extension or add \"type\": \"module\" in their package.json." + "\n\n" + "As a temporary workaround you can try to inline the package by updating your config:" + "\n\n" + c.gray(c.dim("// vitest.config.js")) + "\n" + c.green(`export default {
|
|
1663
|
+
test: {
|
|
1664
|
+
server: {
|
|
1665
|
+
deps: {
|
|
1666
|
+
inline: [
|
|
1667
|
+
${c.yellow(c.bold(`"${name}"`))}
|
|
1668
|
+
]
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
}\n`)));
|
|
1673
|
+
}
|
|
1674
|
+
function printModuleWarningForSourceCode(logger, path) {
|
|
1675
|
+
logger.error(c.yellow(`Module ${path} seems to be an ES Module but shipped in a CommonJS package. ` + "To fix this issue, change the file extension to .mjs or add \"type\": \"module\" in your package.json."));
|
|
1676
|
+
}
|
|
1677
|
+
function printErrorMessage(error, logger) {
|
|
1678
|
+
const errorName = error.name || error.nameStr || "Unknown Error";
|
|
1679
|
+
if (!error.message) {
|
|
1680
|
+
logger.error(error);
|
|
1681
|
+
return;
|
|
1472
1682
|
}
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
}
|
|
1477
|
-
this.tests.set(test.id, test.result().state || "run");
|
|
1478
|
-
this.renderer?.schedule();
|
|
1683
|
+
if (error.message.length > 5e3) {
|
|
1684
|
+
logger.error(`${c.red(c.bold(errorName))}: ${error.message}`);
|
|
1685
|
+
} else {
|
|
1686
|
+
logger.error(c.red(`${c.bold(errorName)}: ${error.message}`));
|
|
1479
1687
|
}
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1688
|
+
}
|
|
1689
|
+
function printStack(logger, project, stack, highlight, errorProperties, onStack) {
|
|
1690
|
+
for (const frame of stack) {
|
|
1691
|
+
const color = frame === highlight ? c.cyan : c.gray;
|
|
1692
|
+
const path = relative(project.config.root, frame.file);
|
|
1693
|
+
logger.error(color(` ${c.dim(F_POINTER)} ${[frame.method, `${path}:${c.dim(`${frame.line}:${frame.column}`)}`].filter(Boolean).join(" ")}`));
|
|
1694
|
+
onStack?.(frame);
|
|
1485
1695
|
}
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
if (!this.isTTY) {
|
|
1489
|
-
return;
|
|
1490
|
-
}
|
|
1491
|
-
const columns = this.ctx.logger.getColumns();
|
|
1492
|
-
if (this.tests.size < columns) {
|
|
1493
|
-
return;
|
|
1494
|
-
}
|
|
1495
|
-
const finishedTests = Array.from(this.tests).filter((entry) => entry[1] !== "pending");
|
|
1496
|
-
if (finishedTests.length < columns) {
|
|
1497
|
-
return;
|
|
1498
|
-
}
|
|
1499
|
-
const states = [];
|
|
1500
|
-
let count = 0;
|
|
1501
|
-
for (const [id, state] of finishedTests) {
|
|
1502
|
-
if (count++ >= columns) {
|
|
1503
|
-
break;
|
|
1504
|
-
}
|
|
1505
|
-
this.tests.delete(id);
|
|
1506
|
-
states.push(state);
|
|
1507
|
-
}
|
|
1508
|
-
this.ctx.logger.log(formatTests(states));
|
|
1509
|
-
this.renderer?.schedule();
|
|
1696
|
+
if (stack.length) {
|
|
1697
|
+
logger.error();
|
|
1510
1698
|
}
|
|
1511
|
-
|
|
1512
|
-
|
|
1699
|
+
if (hasProperties(errorProperties)) {
|
|
1700
|
+
logger.error(c.red(c.dim(divider())));
|
|
1701
|
+
const propertiesString = inspect(errorProperties);
|
|
1702
|
+
logger.error(c.red(c.bold("Serialized Error:")), c.gray(propertiesString));
|
|
1513
1703
|
}
|
|
1514
1704
|
}
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
};
|
|
1519
|
-
const fail = {
|
|
1520
|
-
char: "x",
|
|
1521
|
-
color: c.red
|
|
1522
|
-
};
|
|
1523
|
-
const pending = {
|
|
1524
|
-
char: "*",
|
|
1525
|
-
color: c.yellow
|
|
1526
|
-
};
|
|
1527
|
-
const skip = {
|
|
1528
|
-
char: "-",
|
|
1529
|
-
color: (char) => c.dim(c.gray(char))
|
|
1530
|
-
};
|
|
1531
|
-
function getIcon(state) {
|
|
1532
|
-
switch (state) {
|
|
1533
|
-
case "passed": return pass;
|
|
1534
|
-
case "failed": return fail;
|
|
1535
|
-
case "skipped": return skip;
|
|
1536
|
-
default: return pending;
|
|
1705
|
+
function hasProperties(obj) {
|
|
1706
|
+
for (const _key in obj) {
|
|
1707
|
+
return true;
|
|
1537
1708
|
}
|
|
1709
|
+
return false;
|
|
1538
1710
|
}
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
let currentIcon = pending;
|
|
1711
|
+
function generateCodeFrame(source, indent = 0, loc, range = 2) {
|
|
1712
|
+
const start = typeof loc === "object" ? positionToOffset(source, loc.line, loc.column) : loc;
|
|
1713
|
+
const end = start;
|
|
1714
|
+
const lines = source.split(lineSplitRE);
|
|
1715
|
+
const nl = /\r\n/.test(source) ? 2 : 1;
|
|
1545
1716
|
let count = 0;
|
|
1546
|
-
let
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1717
|
+
let res = [];
|
|
1718
|
+
const columns = process.stdout?.columns || 80;
|
|
1719
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1720
|
+
count += lines[i].length + nl;
|
|
1721
|
+
if (count >= start) {
|
|
1722
|
+
for (let j = i - range; j <= i + range || end > count; j++) {
|
|
1723
|
+
if (j < 0 || j >= lines.length) {
|
|
1724
|
+
continue;
|
|
1725
|
+
}
|
|
1726
|
+
const lineLength = lines[j].length;
|
|
1727
|
+
if (stripVTControlCharacters(lines[j]).length > 200) {
|
|
1728
|
+
return "";
|
|
1729
|
+
}
|
|
1730
|
+
res.push(lineNo(j + 1) + truncateString(lines[j].replace(/\t/g, " "), columns - 5 - indent));
|
|
1731
|
+
if (j === i) {
|
|
1732
|
+
const pad = start - (count - lineLength) + (nl - 1);
|
|
1733
|
+
const length = Math.max(1, end > count ? lineLength - pad : end - start);
|
|
1734
|
+
res.push(lineNo() + " ".repeat(pad) + c.red("^".repeat(length)));
|
|
1735
|
+
} else if (j > i) {
|
|
1736
|
+
if (end > count) {
|
|
1737
|
+
const length = Math.max(1, Math.min(end - count, lineLength));
|
|
1738
|
+
res.push(lineNo() + c.red("^".repeat(length)));
|
|
1739
|
+
}
|
|
1740
|
+
count += lineLength + 1;
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
break;
|
|
1552
1744
|
}
|
|
1553
|
-
output += currentIcon.color(currentIcon.char.repeat(count));
|
|
1554
|
-
count = 1;
|
|
1555
|
-
currentIcon = icon;
|
|
1556
1745
|
}
|
|
1557
|
-
|
|
1558
|
-
|
|
1746
|
+
if (indent) {
|
|
1747
|
+
res = res.map((line) => " ".repeat(indent) + line);
|
|
1748
|
+
}
|
|
1749
|
+
return res.join("\n");
|
|
1750
|
+
}
|
|
1751
|
+
function lineNo(no = "") {
|
|
1752
|
+
return c.gray(`${String(no).padStart(3, " ")}| `);
|
|
1559
1753
|
}
|
|
1560
1754
|
|
|
1561
1755
|
class GithubActionsReporter {
|
|
@@ -1629,6 +1823,17 @@ function escapeProperty(s) {
|
|
|
1629
1823
|
return s.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A").replace(/:/g, "%3A").replace(/,/g, "%2C");
|
|
1630
1824
|
}
|
|
1631
1825
|
|
|
1826
|
+
class HangingProcessReporter {
|
|
1827
|
+
whyRunning;
|
|
1828
|
+
onInit() {
|
|
1829
|
+
const _require = createRequire(import.meta.url);
|
|
1830
|
+
this.whyRunning = _require("why-is-node-running");
|
|
1831
|
+
}
|
|
1832
|
+
onProcessTimeout() {
|
|
1833
|
+
this.whyRunning?.();
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1632
1837
|
const StatusMap = {
|
|
1633
1838
|
fail: "failed",
|
|
1634
1839
|
only: "pending",
|
|
@@ -2112,7 +2317,7 @@ class VerboseReporter extends DefaultReporter {
|
|
|
2112
2317
|
}
|
|
2113
2318
|
let title = ` ${getStateSymbol(test.task)} `;
|
|
2114
2319
|
if (test.project.name) {
|
|
2115
|
-
title += formatProjectName(test.project
|
|
2320
|
+
title += formatProjectName(test.project);
|
|
2116
2321
|
}
|
|
2117
2322
|
title += getFullName(test.task, c.dim(" > "));
|
|
2118
2323
|
title += this.getDurationPrefix(test.task);
|
|
@@ -2151,272 +2356,6 @@ function getIndentation(suite, level = 1) {
|
|
|
2151
2356
|
return level;
|
|
2152
2357
|
}
|
|
2153
2358
|
|
|
2154
|
-
function createBenchmarkJsonReport(files) {
|
|
2155
|
-
const report = { files: [] };
|
|
2156
|
-
for (const file of files) {
|
|
2157
|
-
const groups = [];
|
|
2158
|
-
for (const task of getTasks(file)) {
|
|
2159
|
-
if (task?.type === "suite") {
|
|
2160
|
-
const benchmarks = [];
|
|
2161
|
-
for (const t of task.tasks) {
|
|
2162
|
-
const benchmark = t.meta.benchmark && t.result?.benchmark;
|
|
2163
|
-
if (benchmark) {
|
|
2164
|
-
benchmarks.push({
|
|
2165
|
-
id: t.id,
|
|
2166
|
-
...benchmark,
|
|
2167
|
-
samples: []
|
|
2168
|
-
});
|
|
2169
|
-
}
|
|
2170
|
-
}
|
|
2171
|
-
if (benchmarks.length) {
|
|
2172
|
-
groups.push({
|
|
2173
|
-
fullName: getFullName(task, " > "),
|
|
2174
|
-
benchmarks
|
|
2175
|
-
});
|
|
2176
|
-
}
|
|
2177
|
-
}
|
|
2178
|
-
}
|
|
2179
|
-
report.files.push({
|
|
2180
|
-
filepath: file.filepath,
|
|
2181
|
-
groups
|
|
2182
|
-
});
|
|
2183
|
-
}
|
|
2184
|
-
return report;
|
|
2185
|
-
}
|
|
2186
|
-
function flattenFormattedBenchmarkReport(report) {
|
|
2187
|
-
const flat = {};
|
|
2188
|
-
for (const file of report.files) {
|
|
2189
|
-
for (const group of file.groups) {
|
|
2190
|
-
for (const t of group.benchmarks) {
|
|
2191
|
-
flat[t.id] = t;
|
|
2192
|
-
}
|
|
2193
|
-
}
|
|
2194
|
-
}
|
|
2195
|
-
return flat;
|
|
2196
|
-
}
|
|
2197
|
-
|
|
2198
|
-
const outputMap = new WeakMap();
|
|
2199
|
-
function formatNumber(number) {
|
|
2200
|
-
const res = String(number.toFixed(number < 100 ? 4 : 2)).split(".");
|
|
2201
|
-
return res[0].replace(/(?=(?:\d{3})+$)\B/g, ",") + (res[1] ? `.${res[1]}` : "");
|
|
2202
|
-
}
|
|
2203
|
-
const tableHead = [
|
|
2204
|
-
"name",
|
|
2205
|
-
"hz",
|
|
2206
|
-
"min",
|
|
2207
|
-
"max",
|
|
2208
|
-
"mean",
|
|
2209
|
-
"p75",
|
|
2210
|
-
"p99",
|
|
2211
|
-
"p995",
|
|
2212
|
-
"p999",
|
|
2213
|
-
"rme",
|
|
2214
|
-
"samples"
|
|
2215
|
-
];
|
|
2216
|
-
function renderBenchmarkItems(result) {
|
|
2217
|
-
return [
|
|
2218
|
-
result.name,
|
|
2219
|
-
formatNumber(result.hz || 0),
|
|
2220
|
-
formatNumber(result.min || 0),
|
|
2221
|
-
formatNumber(result.max || 0),
|
|
2222
|
-
formatNumber(result.mean || 0),
|
|
2223
|
-
formatNumber(result.p75 || 0),
|
|
2224
|
-
formatNumber(result.p99 || 0),
|
|
2225
|
-
formatNumber(result.p995 || 0),
|
|
2226
|
-
formatNumber(result.p999 || 0),
|
|
2227
|
-
`±${(result.rme || 0).toFixed(2)}%`,
|
|
2228
|
-
(result.sampleCount || 0).toString()
|
|
2229
|
-
];
|
|
2230
|
-
}
|
|
2231
|
-
function computeColumnWidths(results) {
|
|
2232
|
-
const rows = [tableHead, ...results.map((v) => renderBenchmarkItems(v))];
|
|
2233
|
-
return Array.from(tableHead, (_, i) => Math.max(...rows.map((row) => stripVTControlCharacters(row[i]).length)));
|
|
2234
|
-
}
|
|
2235
|
-
function padRow(row, widths) {
|
|
2236
|
-
return row.map((v, i) => i ? v.padStart(widths[i], " ") : v.padEnd(widths[i], " "));
|
|
2237
|
-
}
|
|
2238
|
-
function renderTableHead(widths) {
|
|
2239
|
-
return " ".repeat(3) + padRow(tableHead, widths).map(c.bold).join(" ");
|
|
2240
|
-
}
|
|
2241
|
-
function renderBenchmark(result, widths) {
|
|
2242
|
-
const padded = padRow(renderBenchmarkItems(result), widths);
|
|
2243
|
-
return [
|
|
2244
|
-
padded[0],
|
|
2245
|
-
c.blue(padded[1]),
|
|
2246
|
-
c.cyan(padded[2]),
|
|
2247
|
-
c.cyan(padded[3]),
|
|
2248
|
-
c.cyan(padded[4]),
|
|
2249
|
-
c.cyan(padded[5]),
|
|
2250
|
-
c.cyan(padded[6]),
|
|
2251
|
-
c.cyan(padded[7]),
|
|
2252
|
-
c.cyan(padded[8]),
|
|
2253
|
-
c.dim(padded[9]),
|
|
2254
|
-
c.dim(padded[10])
|
|
2255
|
-
].join(" ");
|
|
2256
|
-
}
|
|
2257
|
-
function renderTable(options) {
|
|
2258
|
-
const output = [];
|
|
2259
|
-
const benchMap = {};
|
|
2260
|
-
for (const task of options.tasks) {
|
|
2261
|
-
if (task.meta.benchmark && task.result?.benchmark) {
|
|
2262
|
-
benchMap[task.id] = {
|
|
2263
|
-
current: task.result.benchmark,
|
|
2264
|
-
baseline: options.compare?.[task.id]
|
|
2265
|
-
};
|
|
2266
|
-
}
|
|
2267
|
-
}
|
|
2268
|
-
const benchCount = Object.entries(benchMap).length;
|
|
2269
|
-
const columnWidths = computeColumnWidths(Object.values(benchMap).flatMap((v) => [v.current, v.baseline]).filter(notNullish));
|
|
2270
|
-
let idx = 0;
|
|
2271
|
-
const padding = " ".repeat(1 );
|
|
2272
|
-
for (const task of options.tasks) {
|
|
2273
|
-
const duration = task.result?.duration;
|
|
2274
|
-
const bench = benchMap[task.id];
|
|
2275
|
-
let prefix = "";
|
|
2276
|
-
if (idx === 0 && task.meta?.benchmark) {
|
|
2277
|
-
prefix += `${renderTableHead(columnWidths)}\n${padding}`;
|
|
2278
|
-
}
|
|
2279
|
-
prefix += ` ${getStateSymbol(task)} `;
|
|
2280
|
-
let suffix = "";
|
|
2281
|
-
if (task.type === "suite") {
|
|
2282
|
-
suffix += c.dim(` (${getTests(task).length})`);
|
|
2283
|
-
}
|
|
2284
|
-
if (task.mode === "skip" || task.mode === "todo") {
|
|
2285
|
-
suffix += c.dim(c.gray(" [skipped]"));
|
|
2286
|
-
}
|
|
2287
|
-
if (duration != null) {
|
|
2288
|
-
const color = duration > options.slowTestThreshold ? c.yellow : c.green;
|
|
2289
|
-
suffix += color(` ${Math.round(duration)}${c.dim("ms")}`);
|
|
2290
|
-
}
|
|
2291
|
-
if (options.showHeap && task.result?.heap != null) {
|
|
2292
|
-
suffix += c.magenta(` ${Math.floor(task.result.heap / 1024 / 1024)} MB heap used`);
|
|
2293
|
-
}
|
|
2294
|
-
if (bench) {
|
|
2295
|
-
let body = renderBenchmark(bench.current, columnWidths);
|
|
2296
|
-
if (options.compare && bench.baseline) {
|
|
2297
|
-
if (bench.current.hz) {
|
|
2298
|
-
const diff = bench.current.hz / bench.baseline.hz;
|
|
2299
|
-
const diffFixed = diff.toFixed(2);
|
|
2300
|
-
if (diffFixed === "1.0.0") {
|
|
2301
|
-
body += c.gray(` [${diffFixed}x]`);
|
|
2302
|
-
}
|
|
2303
|
-
if (diff > 1) {
|
|
2304
|
-
body += c.blue(` [${diffFixed}x] ⇑`);
|
|
2305
|
-
} else {
|
|
2306
|
-
body += c.red(` [${diffFixed}x] ⇓`);
|
|
2307
|
-
}
|
|
2308
|
-
}
|
|
2309
|
-
output.push(padding + prefix + body + suffix);
|
|
2310
|
-
const bodyBaseline = renderBenchmark(bench.baseline, columnWidths);
|
|
2311
|
-
output.push(`${padding} ${bodyBaseline} ${c.dim("(baseline)")}`);
|
|
2312
|
-
} else {
|
|
2313
|
-
if (bench.current.rank === 1 && benchCount > 1) {
|
|
2314
|
-
body += c.bold(c.green(" fastest"));
|
|
2315
|
-
}
|
|
2316
|
-
if (bench.current.rank === benchCount && benchCount > 2) {
|
|
2317
|
-
body += c.bold(c.gray(" slowest"));
|
|
2318
|
-
}
|
|
2319
|
-
output.push(padding + prefix + body + suffix);
|
|
2320
|
-
}
|
|
2321
|
-
} else {
|
|
2322
|
-
output.push(padding + prefix + task.name + suffix);
|
|
2323
|
-
}
|
|
2324
|
-
if (task.result?.state !== "pass" && outputMap.get(task) != null) {
|
|
2325
|
-
let data = outputMap.get(task);
|
|
2326
|
-
if (typeof data === "string") {
|
|
2327
|
-
data = stripVTControlCharacters(data.trim().split("\n").filter(Boolean).pop());
|
|
2328
|
-
if (data === "") {
|
|
2329
|
-
data = undefined;
|
|
2330
|
-
}
|
|
2331
|
-
}
|
|
2332
|
-
if (data != null) {
|
|
2333
|
-
const out = ` ${" ".repeat(options.level)}${F_RIGHT} ${data}`;
|
|
2334
|
-
output.push(c.gray(truncateString(out, options.columns)));
|
|
2335
|
-
}
|
|
2336
|
-
}
|
|
2337
|
-
idx++;
|
|
2338
|
-
}
|
|
2339
|
-
return output.filter(Boolean).join("\n");
|
|
2340
|
-
}
|
|
2341
|
-
|
|
2342
|
-
class BenchmarkReporter extends DefaultReporter {
|
|
2343
|
-
compare;
|
|
2344
|
-
async onInit(ctx) {
|
|
2345
|
-
super.onInit(ctx);
|
|
2346
|
-
if (this.ctx.config.benchmark?.compare) {
|
|
2347
|
-
const compareFile = pathe.resolve(this.ctx.config.root, this.ctx.config.benchmark?.compare);
|
|
2348
|
-
try {
|
|
2349
|
-
this.compare = flattenFormattedBenchmarkReport(JSON.parse(await fs.promises.readFile(compareFile, "utf-8")));
|
|
2350
|
-
} catch (e) {
|
|
2351
|
-
this.error(`Failed to read '${compareFile}'`, e);
|
|
2352
|
-
}
|
|
2353
|
-
}
|
|
2354
|
-
}
|
|
2355
|
-
onTaskUpdate(packs) {
|
|
2356
|
-
for (const pack of packs) {
|
|
2357
|
-
const task = this.ctx.state.idMap.get(pack[0]);
|
|
2358
|
-
if (task?.type === "suite" && task.result?.state !== "run") {
|
|
2359
|
-
task.tasks.filter((task) => task.result?.benchmark).sort((benchA, benchB) => benchA.result.benchmark.mean - benchB.result.benchmark.mean).forEach((bench, idx) => {
|
|
2360
|
-
bench.result.benchmark.rank = Number(idx) + 1;
|
|
2361
|
-
});
|
|
2362
|
-
}
|
|
2363
|
-
}
|
|
2364
|
-
}
|
|
2365
|
-
onTestSuiteResult(testSuite) {
|
|
2366
|
-
super.onTestSuiteResult(testSuite);
|
|
2367
|
-
this.printSuiteTable(testSuite);
|
|
2368
|
-
}
|
|
2369
|
-
printTestModule(testModule) {
|
|
2370
|
-
this.printSuiteTable(testModule);
|
|
2371
|
-
}
|
|
2372
|
-
printSuiteTable(testTask) {
|
|
2373
|
-
const state = testTask.state();
|
|
2374
|
-
if (state === "pending" || state === "queued") {
|
|
2375
|
-
return;
|
|
2376
|
-
}
|
|
2377
|
-
const benches = testTask.task.tasks.filter((t) => t.meta.benchmark);
|
|
2378
|
-
const duration = testTask.task.result?.duration || 0;
|
|
2379
|
-
if (benches.length > 0 && benches.every((t) => t.result?.state !== "run" && t.result?.state !== "queued")) {
|
|
2380
|
-
let title = `\n ${getStateSymbol(testTask.task)} ${formatProjectName(testTask.project.name)}${getFullName(testTask.task, c.dim(" > "))}`;
|
|
2381
|
-
if (duration != null && duration > this.ctx.config.slowTestThreshold) {
|
|
2382
|
-
title += c.yellow(` ${Math.round(duration)}${c.dim("ms")}`);
|
|
2383
|
-
}
|
|
2384
|
-
this.log(title);
|
|
2385
|
-
this.log(renderTable({
|
|
2386
|
-
tasks: benches,
|
|
2387
|
-
level: 1,
|
|
2388
|
-
columns: this.ctx.logger.getColumns(),
|
|
2389
|
-
compare: this.compare,
|
|
2390
|
-
showHeap: this.ctx.config.logHeapUsage,
|
|
2391
|
-
slowTestThreshold: this.ctx.config.slowTestThreshold
|
|
2392
|
-
}));
|
|
2393
|
-
}
|
|
2394
|
-
}
|
|
2395
|
-
async onFinished(files = this.ctx.state.getFiles(), errors = this.ctx.state.getUnhandledErrors()) {
|
|
2396
|
-
super.onFinished(files, errors);
|
|
2397
|
-
let outputFile = this.ctx.config.benchmark?.outputJson;
|
|
2398
|
-
if (outputFile) {
|
|
2399
|
-
outputFile = pathe.resolve(this.ctx.config.root, outputFile);
|
|
2400
|
-
const outputDirectory = pathe.dirname(outputFile);
|
|
2401
|
-
if (!fs.existsSync(outputDirectory)) {
|
|
2402
|
-
await fs.promises.mkdir(outputDirectory, { recursive: true });
|
|
2403
|
-
}
|
|
2404
|
-
const output = createBenchmarkJsonReport(files);
|
|
2405
|
-
await fs.promises.writeFile(outputFile, JSON.stringify(output, null, 2));
|
|
2406
|
-
this.log(`Benchmark report written to ${outputFile}`);
|
|
2407
|
-
}
|
|
2408
|
-
}
|
|
2409
|
-
}
|
|
2410
|
-
|
|
2411
|
-
class VerboseBenchmarkReporter extends BenchmarkReporter {
|
|
2412
|
-
verbose = true;
|
|
2413
|
-
}
|
|
2414
|
-
|
|
2415
|
-
const BenchmarkReportsMap = {
|
|
2416
|
-
default: BenchmarkReporter,
|
|
2417
|
-
verbose: VerboseBenchmarkReporter
|
|
2418
|
-
};
|
|
2419
|
-
|
|
2420
2359
|
const ReportersMap = {
|
|
2421
2360
|
"default": DefaultReporter,
|
|
2422
2361
|
"basic": BasicReporter,
|
|
@@ -2431,4 +2370,4 @@ const ReportersMap = {
|
|
|
2431
2370
|
"github-actions": GithubActionsReporter
|
|
2432
2371
|
};
|
|
2433
2372
|
|
|
2434
|
-
export { BasicReporter as B, DefaultReporter as D, GithubActionsReporter as G, HangingProcessReporter as H, JsonReporter as J, ReportersMap as R, TapFlatReporter as T,
|
|
2373
|
+
export { BasicReporter as B, DefaultReporter as D, F_RIGHT as F, GithubActionsReporter as G, HangingProcessReporter as H, JsonReporter as J, ReportersMap as R, TapFlatReporter as T, VerboseReporter as V, DotReporter as a, JUnitReporter as b, TapReporter as c, printError as d, errorBanner as e, formatProjectName as f, getStateSymbol as g, divider as h, generateCodeFrame as i, BlobReporter as j, parse as p, readBlobs as r, stringify as s, truncateString as t, utils as u, withLabel as w };
|