vitest 3.2.0-beta.1 → 3.2.0-beta.3
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 +5 -3
- package/dist/browser.js +3 -4
- package/dist/chunks/{base.SfTiRNZf.js → base.D4119yLM.js} +4 -3
- package/dist/chunks/{benchmark.BoF7jW0Q.js → benchmark.Cf_PACH1.js} +1 -1
- package/dist/chunks/{cac.TfX2-DVH.js → cac.DWaWHIIE.js} +21 -16
- package/dist/chunks/{cli-api.2970Nj9J.js → cli-api.CnmEXkxs.js} +292 -59
- package/dist/chunks/{config.d.UqE-KR0o.d.ts → config.d.D2ROskhv.d.ts} +2 -0
- package/dist/chunks/{console.K1NMVOSc.js → console.Cwr-MFPV.js} +3 -2
- package/dist/chunks/{constants.BZZyIeIE.js → constants.DnKduX2e.js} +1 -0
- package/dist/chunks/{coverage.z0LVMxgb.js → coverage.C73DaDgS.js} +241 -4226
- package/dist/chunks/{creator.CuL7xDWI.js → creator.C8WKy2eW.js} +26 -44
- package/dist/chunks/{date.CDOsz-HY.js → date.ByMsSlOr.js} +25 -0
- package/dist/chunks/{defaults.DSxsTG0h.js → defaults.DpVH7vbg.js} +1 -0
- package/dist/chunks/{environment.d.D8YDy2v5.d.ts → environment.d.cL3nLXbE.d.ts} +1 -0
- package/dist/chunks/{execute.BpmIjFTD.js → execute.B3q-2LPV.js} +28 -5
- package/dist/chunks/{global.d.BCOHQEpR.d.ts → global.d.BNLIi6yo.d.ts} +13 -11
- package/dist/chunks/{globals.Cg4NtV4P.js → globals.CI21aWXF.js} +7 -7
- package/dist/chunks/{index.DFXFpH3w.js → index.2jgTs_Q5.js} +19 -1
- package/dist/chunks/{index.CUacZlWG.js → index.Bter3jj9.js} +954 -954
- package/dist/chunks/{index.DbWBPwtH.js → index.CbT4iuwc.js} +7 -4
- package/dist/chunks/index.D3XRDfWc.js +213 -0
- package/dist/chunks/{index.BPc7M5ni.js → index.DNgLEKsQ.js} +5 -15
- package/dist/chunks/index.JOzufsrU.js +276 -0
- package/dist/chunks/{index.DBIGubLC.js → index.X0nbfr6-.js} +7 -7
- package/dist/chunks/{inspector.DbDkSkFn.js → inspector.BFsh5KO0.js} +3 -0
- package/dist/chunks/{node.3xsWotC9.js → node.Be-ntJnD.js} +1 -1
- package/dist/chunks/{reporters.d.DGm4k1Wx.d.ts → reporters.d.Bt4IGtsa.d.ts} +41 -6
- package/dist/chunks/{rpc.D9_013TY.js → rpc.BKExFSRG.js} +2 -1
- package/dist/chunks/{runBaseTests.CguliJB5.js → runBaseTests.B_M1TTsK.js} +19 -11
- package/dist/chunks/{setup-common.BP6KrF_Z.js → setup-common.CF-O-dZX.js} +2 -3
- package/dist/chunks/typechecker.BgzF-6iO.js +954 -0
- package/dist/chunks/{utils.CgTj3MsC.js → utils.BlI4TC7Y.js} +1 -0
- package/dist/chunks/{utils.BfxieIyZ.js → utils.DPCq3gzW.js} +3 -0
- package/dist/chunks/{vi.BFR5YIgu.js → vi.pkoYCV6A.js} +25 -2
- package/dist/chunks/{vite.d.DjP_ALCZ.d.ts → vite.d.B-Kx3KCF.d.ts} +3 -1
- package/dist/chunks/{vm.CuLHT1BG.js → vm.DPYem2so.js} +72 -4
- package/dist/chunks/{worker.d.CoCI7hzP.d.ts → worker.d.BKbBp2ga.d.ts} +2 -2
- package/dist/chunks/{worker.d.D5Xdi-Zr.d.ts → worker.d.Bl1O4kuf.d.ts} +1 -1
- package/dist/cli.js +21 -2
- package/dist/config.cjs +2 -0
- package/dist/config.d.ts +7 -6
- package/dist/config.js +2 -2
- package/dist/coverage.d.ts +4 -4
- package/dist/coverage.js +7 -10
- package/dist/environments.d.ts +6 -2
- package/dist/environments.js +1 -1
- package/dist/execute.d.ts +9 -3
- package/dist/execute.js +1 -1
- package/dist/index.d.ts +25 -35
- package/dist/index.js +5 -6
- package/dist/node.d.ts +18 -10
- package/dist/node.js +22 -22
- package/dist/reporters.d.ts +4 -4
- package/dist/reporters.js +14 -14
- package/dist/runners.d.ts +1 -1
- package/dist/runners.js +13 -5
- package/dist/snapshot.js +2 -2
- package/dist/suite.js +2 -2
- package/dist/worker.js +9 -5
- package/dist/workers/forks.js +6 -4
- package/dist/workers/runVmTests.js +14 -10
- package/dist/workers/threads.js +4 -4
- package/dist/workers/vmForks.js +6 -6
- package/dist/workers/vmThreads.js +6 -6
- package/dist/workers.d.ts +4 -4
- package/dist/workers.js +10 -10
- package/package.json +22 -26
- package/dist/chunks/index.Bw6JxgX8.js +0 -143
- package/dist/chunks/run-once.Dimr7O9f.js +0 -47
- package/dist/chunks/typechecker.DYQbn8uK.js +0 -956
- package/dist/chunks/utils.8gfOgtry.js +0 -207
- 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.8gfOgtry.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.BgzF-6iO.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;
|
|
180
|
+
function padSummaryTitle(str) {
|
|
181
|
+
return c.dim(`${str.padStart(11)} `);
|
|
295
182
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
if (
|
|
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);
|
|
183
|
+
function truncateString(text, maxLength) {
|
|
184
|
+
const plainText = stripVTControlCharacters(text);
|
|
185
|
+
if (plainText.length <= maxLength) {
|
|
186
|
+
return text;
|
|
312
187
|
}
|
|
188
|
+
return `${plainText.slice(0, maxLength - 1)}…`;
|
|
313
189
|
}
|
|
314
|
-
function
|
|
315
|
-
|
|
316
|
-
test: {
|
|
317
|
-
server: {
|
|
318
|
-
deps: {
|
|
319
|
-
inline: [
|
|
320
|
-
${c.yellow(c.bold(`"${name}"`))}
|
|
321
|
-
]
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}\n`)));
|
|
326
|
-
}
|
|
327
|
-
function printModuleWarningForSourceCode(logger, path) {
|
|
328
|
-
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."));
|
|
329
|
-
}
|
|
330
|
-
function printErrorMessage(error, logger) {
|
|
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
|
-
}
|
|
398
|
-
}
|
|
399
|
-
if (indent) {
|
|
400
|
-
res = res.map((line) => " ".repeat(indent) + line);
|
|
401
|
-
}
|
|
402
|
-
return res.join("\n");
|
|
403
|
-
}
|
|
404
|
-
function lineNo(no = "") {
|
|
405
|
-
return c.gray(`${String(no).padStart(3, " ")}| `);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
class BlobReporter {
|
|
409
|
-
start = 0;
|
|
410
|
-
ctx;
|
|
411
|
-
options;
|
|
412
|
-
constructor(options) {
|
|
413
|
-
this.options = options;
|
|
414
|
-
}
|
|
415
|
-
onInit(ctx) {
|
|
416
|
-
if (ctx.config.watch) {
|
|
417
|
-
throw new Error("Blob reporter is not supported in watch mode");
|
|
418
|
-
}
|
|
419
|
-
this.ctx = ctx;
|
|
420
|
-
this.start = performance.now();
|
|
421
|
-
}
|
|
422
|
-
async onFinished(files = [], errors = [], coverage) {
|
|
423
|
-
const executionTime = performance.now() - this.start;
|
|
424
|
-
let outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "blob");
|
|
425
|
-
if (!outputFile) {
|
|
426
|
-
const shard = this.ctx.config.shard;
|
|
427
|
-
outputFile = shard ? `.vitest-reports/blob-${shard.index}-${shard.count}.json` : ".vitest-reports/blob.json";
|
|
428
|
-
}
|
|
429
|
-
const modules = this.ctx.projects.map((project) => {
|
|
430
|
-
return [project.name, [...project.vite.moduleGraph.idToModuleMap.entries()].map((mod) => {
|
|
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
|
-
};
|
|
190
|
+
function capitalize(text) {
|
|
191
|
+
return `${text[0].toUpperCase()}${text.slice(1)}`;
|
|
526
192
|
}
|
|
527
193
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
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 {
|
|
@@ -604,6 +283,8 @@ class BaseReporter {
|
|
|
604
283
|
let testsCount = 0;
|
|
605
284
|
let failedCount = 0;
|
|
606
285
|
let skippedCount = 0;
|
|
286
|
+
// delaying logs to calculate the test stats first
|
|
287
|
+
// which minimizes the amount of for loops
|
|
607
288
|
const logs = [];
|
|
608
289
|
const originalLog = this.log.bind(this);
|
|
609
290
|
this.log = (msg) => logs.push(msg);
|
|
@@ -611,6 +292,7 @@ class BaseReporter {
|
|
|
611
292
|
for (const child of children) {
|
|
612
293
|
if (child.type === "suite") {
|
|
613
294
|
const suiteState = child.state();
|
|
295
|
+
// Skipped suites are hidden when --hideSkippedTests, print otherwise
|
|
614
296
|
if (!this.ctx.config.hideSkippedTests || suiteState !== "skipped") {
|
|
615
297
|
this.printTestSuite(child);
|
|
616
298
|
}
|
|
@@ -624,6 +306,7 @@ class BaseReporter {
|
|
|
624
306
|
skippedCount++;
|
|
625
307
|
}
|
|
626
308
|
if (this.ctx.config.hideSkippedTests && suiteState === "skipped") {
|
|
309
|
+
// Skipped suites are hidden when --hideSkippedTests
|
|
627
310
|
continue;
|
|
628
311
|
}
|
|
629
312
|
this.printTestCase(moduleState, child);
|
|
@@ -655,6 +338,7 @@ class BaseReporter {
|
|
|
655
338
|
}
|
|
656
339
|
if (testResult.state === "failed") {
|
|
657
340
|
this.log(c.red(` ${padding}${taskFail} ${this.getTestName(test.task, c.dim(" > "))}`) + suffix);
|
|
341
|
+
// print short errors, full errors will be at the end in summary
|
|
658
342
|
testResult.errors.forEach((error) => {
|
|
659
343
|
const message = this.formatShortError(error);
|
|
660
344
|
if (message) {
|
|
@@ -691,7 +375,9 @@ class BaseReporter {
|
|
|
691
375
|
}
|
|
692
376
|
return ` ${title} ${testModule.task.name} ${suffix}`;
|
|
693
377
|
}
|
|
694
|
-
printTestSuite(_suite) {
|
|
378
|
+
printTestSuite(_suite) {
|
|
379
|
+
// Suite name is included in getTestName by default
|
|
380
|
+
}
|
|
695
381
|
getTestName(test, separator) {
|
|
696
382
|
return getTestName(test, separator);
|
|
697
383
|
}
|
|
@@ -728,6 +414,7 @@ class BaseReporter {
|
|
|
728
414
|
onWatcherRerun(files, trigger) {
|
|
729
415
|
this.watchFilters = files;
|
|
730
416
|
this.failedUnwatchedFiles = this.ctx.state.getTestModules().filter((testModule) => !files.includes(testModule.task.filepath) && testModule.state() === "failed");
|
|
417
|
+
// Update re-run count for each file
|
|
731
418
|
files.forEach((filepath) => {
|
|
732
419
|
let reruns = this._filesInWatchMode.get(filepath) ?? 0;
|
|
733
420
|
this._filesInWatchMode.set(filepath, ++reruns);
|
|
@@ -770,6 +457,7 @@ class BaseReporter {
|
|
|
770
457
|
}
|
|
771
458
|
write(c.gray(log.type + c.dim(` | ${headerText}\n`)) + log.content);
|
|
772
459
|
if (log.origin) {
|
|
460
|
+
// browser logs don't have an extra end of line at the end like Node.js does
|
|
773
461
|
if (log.browser) {
|
|
774
462
|
write("\n");
|
|
775
463
|
}
|
|
@@ -841,6 +529,7 @@ class BaseReporter {
|
|
|
841
529
|
this.log(padSummaryTitle("Duration"), formatTime(collectTime + testsTime + setupTime));
|
|
842
530
|
} else {
|
|
843
531
|
const blobs = this.ctx.state.blobs;
|
|
532
|
+
// Execution time is either sum of all runs of `--merge-reports` or the current run's time
|
|
844
533
|
const executionTime = blobs?.executionTimes ? sum(blobs.executionTimes, (time) => time) : this.end - this.start;
|
|
845
534
|
const environmentTime = sum(files, (file) => file.environmentLoad);
|
|
846
535
|
const prepareTime = sum(files, (file) => file.prepareDuration);
|
|
@@ -906,6 +595,7 @@ class BaseReporter {
|
|
|
906
595
|
printTaskErrors(tasks, errorDivider) {
|
|
907
596
|
const errorsQueue = [];
|
|
908
597
|
for (const task of tasks) {
|
|
598
|
+
// Merge identical errors
|
|
909
599
|
task.result?.errors?.forEach((error) => {
|
|
910
600
|
let previous;
|
|
911
601
|
if (error?.stackStr) {
|
|
@@ -947,25 +637,255 @@ class BaseReporter {
|
|
|
947
637
|
}
|
|
948
638
|
}
|
|
949
639
|
}
|
|
950
|
-
function sum(items, cb) {
|
|
951
|
-
return items.reduce((total, next) => {
|
|
952
|
-
return total + Math.max(cb(next) || 0, 0);
|
|
953
|
-
}, 0);
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
class BasicReporter extends BaseReporter {
|
|
957
|
-
constructor() {
|
|
958
|
-
super();
|
|
959
|
-
this.isTTY = false;
|
|
640
|
+
function sum(items, cb) {
|
|
641
|
+
return items.reduce((total, next) => {
|
|
642
|
+
return total + Math.max(cb(next) || 0, 0);
|
|
643
|
+
}, 0);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
class BasicReporter extends BaseReporter {
|
|
647
|
+
constructor() {
|
|
648
|
+
super();
|
|
649
|
+
this.isTTY = false;
|
|
650
|
+
}
|
|
651
|
+
onInit(ctx) {
|
|
652
|
+
super.onInit(ctx);
|
|
653
|
+
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)}`);
|
|
654
|
+
}
|
|
655
|
+
reportSummary(files, errors) {
|
|
656
|
+
// non-tty mode doesn't add a new line
|
|
657
|
+
this.ctx.logger.log();
|
|
658
|
+
return super.reportSummary(files, errors);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/// <reference types="../types/index.d.ts" />
|
|
663
|
+
|
|
664
|
+
// (c) 2020-present Andrea Giammarchi
|
|
665
|
+
|
|
666
|
+
const {parse: $parse, stringify: $stringify} = JSON;
|
|
667
|
+
const {keys} = Object;
|
|
668
|
+
|
|
669
|
+
const Primitive = String; // it could be Number
|
|
670
|
+
const primitive = 'string'; // it could be 'number'
|
|
671
|
+
|
|
672
|
+
const ignore = {};
|
|
673
|
+
const object = 'object';
|
|
674
|
+
|
|
675
|
+
const noop = (_, value) => value;
|
|
676
|
+
|
|
677
|
+
const primitives = value => (
|
|
678
|
+
value instanceof Primitive ? Primitive(value) : value
|
|
679
|
+
);
|
|
680
|
+
|
|
681
|
+
const Primitives = (_, value) => (
|
|
682
|
+
typeof value === primitive ? new Primitive(value) : value
|
|
683
|
+
);
|
|
684
|
+
|
|
685
|
+
const revive = (input, parsed, output, $) => {
|
|
686
|
+
const lazy = [];
|
|
687
|
+
for (let ke = keys(output), {length} = ke, y = 0; y < length; y++) {
|
|
688
|
+
const k = ke[y];
|
|
689
|
+
const value = output[k];
|
|
690
|
+
if (value instanceof Primitive) {
|
|
691
|
+
const tmp = input[value];
|
|
692
|
+
if (typeof tmp === object && !parsed.has(tmp)) {
|
|
693
|
+
parsed.add(tmp);
|
|
694
|
+
output[k] = ignore;
|
|
695
|
+
lazy.push({k, a: [input, parsed, tmp, $]});
|
|
696
|
+
}
|
|
697
|
+
else
|
|
698
|
+
output[k] = $.call(output, k, tmp);
|
|
699
|
+
}
|
|
700
|
+
else if (output[k] !== ignore)
|
|
701
|
+
output[k] = $.call(output, k, value);
|
|
702
|
+
}
|
|
703
|
+
for (let {length} = lazy, i = 0; i < length; i++) {
|
|
704
|
+
const {k, a} = lazy[i];
|
|
705
|
+
output[k] = $.call(output, k, revive.apply(null, a));
|
|
706
|
+
}
|
|
707
|
+
return output;
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
const set = (known, input, value) => {
|
|
711
|
+
const index = Primitive(input.push(value) - 1);
|
|
712
|
+
known.set(value, index);
|
|
713
|
+
return index;
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* Converts a specialized flatted string into a JS value.
|
|
718
|
+
* @param {string} text
|
|
719
|
+
* @param {(this: any, key: string, value: any) => any} [reviver]
|
|
720
|
+
* @returns {any}
|
|
721
|
+
*/
|
|
722
|
+
const parse = (text, reviver) => {
|
|
723
|
+
const input = $parse(text, Primitives).map(primitives);
|
|
724
|
+
const value = input[0];
|
|
725
|
+
const $ = reviver || noop;
|
|
726
|
+
const tmp = typeof value === object && value ?
|
|
727
|
+
revive(input, new Set, value, $) :
|
|
728
|
+
value;
|
|
729
|
+
return $.call({'': tmp}, '', tmp);
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* Converts a JS value into a specialized flatted string.
|
|
734
|
+
* @param {any} value
|
|
735
|
+
* @param {((this: any, key: string, value: any) => any) | (string | number)[] | null | undefined} [replacer]
|
|
736
|
+
* @param {string | number | undefined} [space]
|
|
737
|
+
* @returns {string}
|
|
738
|
+
*/
|
|
739
|
+
const stringify = (value, replacer, space) => {
|
|
740
|
+
const $ = replacer && typeof replacer === object ?
|
|
741
|
+
(k, v) => (k === '' || -1 < replacer.indexOf(k) ? v : void 0) :
|
|
742
|
+
(replacer || noop);
|
|
743
|
+
const known = new Map;
|
|
744
|
+
const input = [];
|
|
745
|
+
const output = [];
|
|
746
|
+
let i = +set(known, input, $.call({'': value}, '', value));
|
|
747
|
+
let firstRun = !i;
|
|
748
|
+
while (i < input.length) {
|
|
749
|
+
firstRun = true;
|
|
750
|
+
output[i] = $stringify(input[i++], replace, space);
|
|
751
|
+
}
|
|
752
|
+
return '[' + output.join(',') + ']';
|
|
753
|
+
function replace(key, value) {
|
|
754
|
+
if (firstRun) {
|
|
755
|
+
firstRun = !firstRun;
|
|
756
|
+
return value;
|
|
757
|
+
}
|
|
758
|
+
const after = $.call(this, key, value);
|
|
759
|
+
switch (typeof after) {
|
|
760
|
+
case object:
|
|
761
|
+
if (after === null) return after;
|
|
762
|
+
case primitive:
|
|
763
|
+
return known.get(after) || set(known, input, after);
|
|
764
|
+
}
|
|
765
|
+
return after;
|
|
766
|
+
}
|
|
767
|
+
};
|
|
768
|
+
|
|
769
|
+
class BlobReporter {
|
|
770
|
+
start = 0;
|
|
771
|
+
ctx;
|
|
772
|
+
options;
|
|
773
|
+
constructor(options) {
|
|
774
|
+
this.options = options;
|
|
775
|
+
}
|
|
776
|
+
onInit(ctx) {
|
|
777
|
+
if (ctx.config.watch) {
|
|
778
|
+
throw new Error("Blob reporter is not supported in watch mode");
|
|
779
|
+
}
|
|
780
|
+
this.ctx = ctx;
|
|
781
|
+
this.start = performance.now();
|
|
782
|
+
}
|
|
783
|
+
async onFinished(files = [], errors = [], coverage) {
|
|
784
|
+
const executionTime = performance.now() - this.start;
|
|
785
|
+
let outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "blob");
|
|
786
|
+
if (!outputFile) {
|
|
787
|
+
const shard = this.ctx.config.shard;
|
|
788
|
+
outputFile = shard ? `.vitest-reports/blob-${shard.index}-${shard.count}.json` : ".vitest-reports/blob.json";
|
|
789
|
+
}
|
|
790
|
+
const modules = this.ctx.projects.map((project) => {
|
|
791
|
+
return [project.name, [...project.vite.moduleGraph.idToModuleMap.entries()].map((mod) => {
|
|
792
|
+
if (!mod[1].file) {
|
|
793
|
+
return null;
|
|
794
|
+
}
|
|
795
|
+
return [
|
|
796
|
+
mod[0],
|
|
797
|
+
mod[1].file,
|
|
798
|
+
mod[1].url
|
|
799
|
+
];
|
|
800
|
+
}).filter((x) => x != null)];
|
|
801
|
+
});
|
|
802
|
+
const report = [
|
|
803
|
+
this.ctx.version,
|
|
804
|
+
files,
|
|
805
|
+
errors,
|
|
806
|
+
modules,
|
|
807
|
+
coverage,
|
|
808
|
+
executionTime
|
|
809
|
+
];
|
|
810
|
+
const reportFile = resolve(this.ctx.config.root, outputFile);
|
|
811
|
+
await writeBlob(report, reportFile);
|
|
812
|
+
this.ctx.logger.log("blob report written to", reportFile);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
async function writeBlob(content, filename) {
|
|
816
|
+
const report = stringify(content);
|
|
817
|
+
const dir = dirname(filename);
|
|
818
|
+
if (!existsSync(dir)) {
|
|
819
|
+
await mkdir(dir, { recursive: true });
|
|
820
|
+
}
|
|
821
|
+
await writeFile(filename, report, "utf-8");
|
|
822
|
+
}
|
|
823
|
+
async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
|
|
824
|
+
// using process.cwd() because --merge-reports can only be used in CLI
|
|
825
|
+
const resolvedDir = resolve(process.cwd(), blobsDirectory);
|
|
826
|
+
const blobsFiles = await readdir(resolvedDir);
|
|
827
|
+
const promises = blobsFiles.map(async (filename) => {
|
|
828
|
+
const fullPath = resolve(resolvedDir, filename);
|
|
829
|
+
const stats = await stat(fullPath);
|
|
830
|
+
if (!stats.isFile()) {
|
|
831
|
+
throw new TypeError(`vitest.mergeReports() expects all paths in "${blobsDirectory}" to be files generated by the blob reporter, but "${filename}" is not a file`);
|
|
832
|
+
}
|
|
833
|
+
const content = await readFile(fullPath, "utf-8");
|
|
834
|
+
const [version, files, errors, moduleKeys, coverage, executionTime] = parse(content);
|
|
835
|
+
if (!version) {
|
|
836
|
+
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`);
|
|
837
|
+
}
|
|
838
|
+
return {
|
|
839
|
+
version,
|
|
840
|
+
files,
|
|
841
|
+
errors,
|
|
842
|
+
moduleKeys,
|
|
843
|
+
coverage,
|
|
844
|
+
file: filename,
|
|
845
|
+
executionTime
|
|
846
|
+
};
|
|
847
|
+
});
|
|
848
|
+
const blobs = await Promise.all(promises);
|
|
849
|
+
if (!blobs.length) {
|
|
850
|
+
throw new Error(`vitest.mergeReports() requires at least one blob file in "${blobsDirectory}" directory, but none were found`);
|
|
960
851
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
852
|
+
const versions = new Set(blobs.map((blob) => blob.version));
|
|
853
|
+
if (versions.size > 1) {
|
|
854
|
+
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")}`);
|
|
964
855
|
}
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
return super.reportSummary(files, errors);
|
|
856
|
+
if (!versions.has(currentVersion)) {
|
|
857
|
+
throw new Error(`the blobs in "${blobsDirectory}" were generated by a different version of Vitest. Expected v${currentVersion}, but received v${blobs[0].version}`);
|
|
968
858
|
}
|
|
859
|
+
// fake module graph - it is used to check if module is imported, but we don't use values inside
|
|
860
|
+
const projects = Object.fromEntries(projectsArray.map((p) => [p.name, p]));
|
|
861
|
+
blobs.forEach((blob) => {
|
|
862
|
+
blob.moduleKeys.forEach(([projectName, moduleIds]) => {
|
|
863
|
+
const project = projects[projectName];
|
|
864
|
+
if (!project) {
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
moduleIds.forEach(([moduleId, file, url]) => {
|
|
868
|
+
const moduleNode = project.vite.moduleGraph.createFileOnlyEntry(file);
|
|
869
|
+
moduleNode.url = url;
|
|
870
|
+
moduleNode.id = moduleId;
|
|
871
|
+
project.vite.moduleGraph.idToModuleMap.set(moduleId, moduleNode);
|
|
872
|
+
});
|
|
873
|
+
});
|
|
874
|
+
});
|
|
875
|
+
const files = blobs.flatMap((blob) => blob.files).sort((f1, f2) => {
|
|
876
|
+
const time1 = f1.result?.startTime || 0;
|
|
877
|
+
const time2 = f2.result?.startTime || 0;
|
|
878
|
+
return time1 - time2;
|
|
879
|
+
});
|
|
880
|
+
const errors = blobs.flatMap((blob) => blob.errors);
|
|
881
|
+
const coverages = blobs.map((blob) => blob.coverage);
|
|
882
|
+
const executionTimes = blobs.map((blob) => blob.executionTime);
|
|
883
|
+
return {
|
|
884
|
+
files,
|
|
885
|
+
errors,
|
|
886
|
+
coverages,
|
|
887
|
+
executionTimes
|
|
888
|
+
};
|
|
969
889
|
}
|
|
970
890
|
|
|
971
891
|
const DEFAULT_RENDER_INTERVAL_MS = 1e3;
|
|
@@ -997,6 +917,7 @@ class WindowRenderer {
|
|
|
997
917
|
error: options.logger.errorStream.write.bind(options.logger.errorStream)
|
|
998
918
|
};
|
|
999
919
|
this.cleanups.push(this.interceptStream(process.stdout, "output"), this.interceptStream(process.stderr, "error"));
|
|
920
|
+
// Write buffered content on unexpected exits, e.g. direct `process.exit()` calls
|
|
1000
921
|
this.options.logger.onTerminalCleanup(() => {
|
|
1001
922
|
this.flushBuffer();
|
|
1002
923
|
this.stop();
|
|
@@ -1037,6 +958,7 @@ class WindowRenderer {
|
|
|
1037
958
|
return this.render();
|
|
1038
959
|
}
|
|
1039
960
|
let current;
|
|
961
|
+
// Concatenate same types into a single render
|
|
1040
962
|
for (const next of this.buffer.splice(0)) {
|
|
1041
963
|
if (!current) {
|
|
1042
964
|
current = next;
|
|
@@ -1088,6 +1010,7 @@ class WindowRenderer {
|
|
|
1088
1010
|
}
|
|
1089
1011
|
interceptStream(stream, type) {
|
|
1090
1012
|
const original = stream.write;
|
|
1013
|
+
// @ts-expect-error -- not sure how 2 overloads should be typed
|
|
1091
1014
|
stream.write = (chunk, _, callback) => {
|
|
1092
1015
|
if (chunk) {
|
|
1093
1016
|
if (this.finished) {
|
|
@@ -1171,6 +1094,7 @@ class SummaryReporter {
|
|
|
1171
1094
|
clearInterval(this.durationInterval);
|
|
1172
1095
|
}
|
|
1173
1096
|
onTestModuleQueued(module) {
|
|
1097
|
+
// When new test module starts, take the place of previously finished test module, if any
|
|
1174
1098
|
if (this.finishedModules.size) {
|
|
1175
1099
|
const finished = this.finishedModules.keys().next().value;
|
|
1176
1100
|
this.removeTestModule(finished);
|
|
@@ -1217,6 +1141,7 @@ class SummaryReporter {
|
|
|
1217
1141
|
stats.hook.visible = false;
|
|
1218
1142
|
}
|
|
1219
1143
|
onTestCaseReady(test) {
|
|
1144
|
+
// Track slow running tests only on verbose mode
|
|
1220
1145
|
if (!this.options.verbose) {
|
|
1221
1146
|
return;
|
|
1222
1147
|
}
|
|
@@ -1270,16 +1195,22 @@ class SummaryReporter {
|
|
|
1270
1195
|
this.modules.skipped++;
|
|
1271
1196
|
}
|
|
1272
1197
|
const left = this.modules.total - this.modules.completed;
|
|
1198
|
+
// Keep finished tests visible in summary for a while if there are more tests left.
|
|
1199
|
+
// When a new test starts in onTestModuleQueued it will take this ones place.
|
|
1200
|
+
// This reduces flickering by making summary more stable.
|
|
1273
1201
|
if (left > this.maxParallelTests) {
|
|
1274
1202
|
this.finishedModules.set(module.id, setTimeout(() => {
|
|
1275
1203
|
this.removeTestModule(module.id);
|
|
1276
1204
|
}, FINISHED_TEST_CLEANUP_TIME_MS).unref());
|
|
1277
1205
|
} else {
|
|
1206
|
+
// Run is about to end as there are less tests left than whole run had parallel at max.
|
|
1207
|
+
// Remove finished test immediately.
|
|
1278
1208
|
this.removeTestModule(module.id);
|
|
1279
1209
|
}
|
|
1280
1210
|
this.renderer.schedule();
|
|
1281
1211
|
}
|
|
1282
1212
|
getHookStats({ entity }) {
|
|
1213
|
+
// Track slow running hooks only on verbose mode
|
|
1283
1214
|
if (!this.options.verbose) {
|
|
1284
1215
|
return;
|
|
1285
1216
|
}
|
|
@@ -1363,213 +1294,517 @@ function sortRunningModules(a, b) {
|
|
|
1363
1294
|
if ((a.projectName || "") < (b.projectName || "")) {
|
|
1364
1295
|
return -1;
|
|
1365
1296
|
}
|
|
1366
|
-
return a.filename.localeCompare(b.filename);
|
|
1297
|
+
return a.filename.localeCompare(b.filename);
|
|
1298
|
+
}
|
|
1299
|
+
function initializeStats(module) {
|
|
1300
|
+
return {
|
|
1301
|
+
total: 0,
|
|
1302
|
+
completed: 0,
|
|
1303
|
+
filename: module.task.name,
|
|
1304
|
+
projectName: module.project.name,
|
|
1305
|
+
projectColor: module.project.color,
|
|
1306
|
+
tests: new Map(),
|
|
1307
|
+
typecheck: !!module.task.meta.typecheck
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
class DefaultReporter extends BaseReporter {
|
|
1312
|
+
options;
|
|
1313
|
+
summary;
|
|
1314
|
+
constructor(options = {}) {
|
|
1315
|
+
super(options);
|
|
1316
|
+
this.options = {
|
|
1317
|
+
summary: true,
|
|
1318
|
+
...options
|
|
1319
|
+
};
|
|
1320
|
+
if (!this.isTTY) {
|
|
1321
|
+
this.options.summary = false;
|
|
1322
|
+
}
|
|
1323
|
+
if (this.options.summary) {
|
|
1324
|
+
this.summary = new SummaryReporter();
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
onTestRunStart(specifications) {
|
|
1328
|
+
if (this.isTTY) {
|
|
1329
|
+
if (this.renderSucceed === undefined) {
|
|
1330
|
+
this.renderSucceed = !!this.renderSucceed;
|
|
1331
|
+
}
|
|
1332
|
+
if (this.renderSucceed !== true) {
|
|
1333
|
+
this.renderSucceed = specifications.length <= 1;
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
this.summary?.onTestRunStart(specifications);
|
|
1337
|
+
}
|
|
1338
|
+
onTestModuleQueued(file) {
|
|
1339
|
+
this.summary?.onTestModuleQueued(file);
|
|
1340
|
+
}
|
|
1341
|
+
onTestModuleCollected(module) {
|
|
1342
|
+
this.summary?.onTestModuleCollected(module);
|
|
1343
|
+
}
|
|
1344
|
+
onTestModuleEnd(module) {
|
|
1345
|
+
super.onTestModuleEnd(module);
|
|
1346
|
+
this.summary?.onTestModuleEnd(module);
|
|
1347
|
+
}
|
|
1348
|
+
onTestCaseReady(test) {
|
|
1349
|
+
this.summary?.onTestCaseReady(test);
|
|
1350
|
+
}
|
|
1351
|
+
onTestCaseResult(test) {
|
|
1352
|
+
super.onTestCaseResult(test);
|
|
1353
|
+
this.summary?.onTestCaseResult(test);
|
|
1354
|
+
}
|
|
1355
|
+
onHookStart(hook) {
|
|
1356
|
+
this.summary?.onHookStart(hook);
|
|
1357
|
+
}
|
|
1358
|
+
onHookEnd(hook) {
|
|
1359
|
+
this.summary?.onHookEnd(hook);
|
|
1360
|
+
}
|
|
1361
|
+
onInit(ctx) {
|
|
1362
|
+
super.onInit(ctx);
|
|
1363
|
+
this.summary?.onInit(ctx, { verbose: this.verbose });
|
|
1364
|
+
}
|
|
1365
|
+
onTestRunEnd() {
|
|
1366
|
+
this.summary?.onTestRunEnd();
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
class DotReporter extends BaseReporter {
|
|
1371
|
+
renderer;
|
|
1372
|
+
tests = new Map();
|
|
1373
|
+
finishedTests = new Set();
|
|
1374
|
+
onInit(ctx) {
|
|
1375
|
+
super.onInit(ctx);
|
|
1376
|
+
if (this.isTTY) {
|
|
1377
|
+
this.renderer = new WindowRenderer({
|
|
1378
|
+
logger: ctx.logger,
|
|
1379
|
+
getWindow: () => this.createSummary()
|
|
1380
|
+
});
|
|
1381
|
+
this.ctx.onClose(() => this.renderer?.stop());
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
printTestModule(testModule) {
|
|
1385
|
+
if (!this.isTTY) {
|
|
1386
|
+
super.printTestModule(testModule);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
onWatcherRerun(files, trigger) {
|
|
1390
|
+
this.tests.clear();
|
|
1391
|
+
this.renderer?.start();
|
|
1392
|
+
super.onWatcherRerun(files, trigger);
|
|
1393
|
+
}
|
|
1394
|
+
onFinished(files, errors) {
|
|
1395
|
+
if (this.isTTY) {
|
|
1396
|
+
const finalLog = formatTests(Array.from(this.tests.values()));
|
|
1397
|
+
this.ctx.logger.log(finalLog);
|
|
1398
|
+
}
|
|
1399
|
+
this.tests.clear();
|
|
1400
|
+
this.renderer?.finish();
|
|
1401
|
+
super.onFinished(files, errors);
|
|
1402
|
+
}
|
|
1403
|
+
onTestModuleCollected(module) {
|
|
1404
|
+
for (const test of module.children.allTests()) {
|
|
1405
|
+
// Dot reporter marks pending tests as running
|
|
1406
|
+
this.onTestCaseReady(test);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
onTestCaseReady(test) {
|
|
1410
|
+
if (this.finishedTests.has(test.id)) {
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
this.tests.set(test.id, test.result().state || "run");
|
|
1414
|
+
this.renderer?.schedule();
|
|
1415
|
+
}
|
|
1416
|
+
onTestCaseResult(test) {
|
|
1417
|
+
super.onTestCaseResult(test);
|
|
1418
|
+
this.finishedTests.add(test.id);
|
|
1419
|
+
this.tests.set(test.id, test.result().state || "skipped");
|
|
1420
|
+
this.renderer?.schedule();
|
|
1421
|
+
}
|
|
1422
|
+
onTestModuleEnd(testModule) {
|
|
1423
|
+
super.onTestModuleEnd(testModule);
|
|
1424
|
+
if (!this.isTTY) {
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
const columns = this.ctx.logger.getColumns();
|
|
1428
|
+
if (this.tests.size < columns) {
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1431
|
+
const finishedTests = Array.from(this.tests).filter((entry) => entry[1] !== "pending");
|
|
1432
|
+
if (finishedTests.length < columns) {
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
// Remove finished tests from state and render them in static output
|
|
1436
|
+
const states = [];
|
|
1437
|
+
let count = 0;
|
|
1438
|
+
for (const [id, state] of finishedTests) {
|
|
1439
|
+
if (count++ >= columns) {
|
|
1440
|
+
break;
|
|
1441
|
+
}
|
|
1442
|
+
this.tests.delete(id);
|
|
1443
|
+
states.push(state);
|
|
1444
|
+
}
|
|
1445
|
+
this.ctx.logger.log(formatTests(states));
|
|
1446
|
+
this.renderer?.schedule();
|
|
1447
|
+
}
|
|
1448
|
+
createSummary() {
|
|
1449
|
+
return [formatTests(Array.from(this.tests.values())), ""];
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
// These are compared with reference equality in formatTests
|
|
1453
|
+
const pass = {
|
|
1454
|
+
char: "·",
|
|
1455
|
+
color: c.green
|
|
1456
|
+
};
|
|
1457
|
+
const fail = {
|
|
1458
|
+
char: "x",
|
|
1459
|
+
color: c.red
|
|
1460
|
+
};
|
|
1461
|
+
const pending = {
|
|
1462
|
+
char: "*",
|
|
1463
|
+
color: c.yellow
|
|
1464
|
+
};
|
|
1465
|
+
const skip = {
|
|
1466
|
+
char: "-",
|
|
1467
|
+
color: (char) => c.dim(c.gray(char))
|
|
1468
|
+
};
|
|
1469
|
+
function getIcon(state) {
|
|
1470
|
+
switch (state) {
|
|
1471
|
+
case "passed": return pass;
|
|
1472
|
+
case "failed": return fail;
|
|
1473
|
+
case "skipped": return skip;
|
|
1474
|
+
default: return pending;
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
/**
|
|
1478
|
+
* Format test states into string while keeping ANSI escapes at minimal.
|
|
1479
|
+
* Sibling icons with same color are merged into a single c.color() call.
|
|
1480
|
+
*/
|
|
1481
|
+
function formatTests(states) {
|
|
1482
|
+
let currentIcon = pending;
|
|
1483
|
+
let count = 0;
|
|
1484
|
+
let output = "";
|
|
1485
|
+
for (const state of states) {
|
|
1486
|
+
const icon = getIcon(state);
|
|
1487
|
+
if (currentIcon === icon) {
|
|
1488
|
+
count++;
|
|
1489
|
+
continue;
|
|
1490
|
+
}
|
|
1491
|
+
output += currentIcon.color(currentIcon.char.repeat(count));
|
|
1492
|
+
// Start tracking new group
|
|
1493
|
+
count = 1;
|
|
1494
|
+
currentIcon = icon;
|
|
1495
|
+
}
|
|
1496
|
+
output += currentIcon.color(currentIcon.char.repeat(count));
|
|
1497
|
+
return output;
|
|
1367
1498
|
}
|
|
1368
|
-
|
|
1499
|
+
|
|
1500
|
+
// use Logger with custom Console to capture entire error printing
|
|
1501
|
+
function capturePrintError(error, ctx, options) {
|
|
1502
|
+
let output = "";
|
|
1503
|
+
const writable = new Writable({ write(chunk, _encoding, callback) {
|
|
1504
|
+
output += String(chunk);
|
|
1505
|
+
callback();
|
|
1506
|
+
} });
|
|
1507
|
+
const console = new Console(writable);
|
|
1508
|
+
const logger = {
|
|
1509
|
+
error: console.error.bind(console),
|
|
1510
|
+
highlight: ctx.logger.highlight.bind(ctx.logger)
|
|
1511
|
+
};
|
|
1512
|
+
const result = printError(error, ctx, logger, {
|
|
1513
|
+
showCodeFrame: false,
|
|
1514
|
+
...options
|
|
1515
|
+
});
|
|
1369
1516
|
return {
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
filename: module.task.name,
|
|
1373
|
-
projectName: module.project.name,
|
|
1374
|
-
projectColor: module.project.color,
|
|
1375
|
-
tests: new Map(),
|
|
1376
|
-
typecheck: !!module.task.meta.typecheck
|
|
1517
|
+
nearest: result?.nearest,
|
|
1518
|
+
output
|
|
1377
1519
|
};
|
|
1378
1520
|
}
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1521
|
+
function printError(error, ctx, logger, options) {
|
|
1522
|
+
const project = options.project ?? ctx.coreWorkspaceProject ?? ctx.projects[0];
|
|
1523
|
+
return printErrorInner(error, project, {
|
|
1524
|
+
logger,
|
|
1525
|
+
type: options.type,
|
|
1526
|
+
showCodeFrame: options.showCodeFrame,
|
|
1527
|
+
screenshotPaths: options.screenshotPaths,
|
|
1528
|
+
printProperties: options.verbose,
|
|
1529
|
+
parseErrorStacktrace(error) {
|
|
1530
|
+
// browser stack trace needs to be processed differently,
|
|
1531
|
+
// so there is a separate method for that
|
|
1532
|
+
if (options.task?.file.pool === "browser" && project.browser) {
|
|
1533
|
+
return project.browser.parseErrorStacktrace(error, { ignoreStackEntries: options.fullStack ? [] : undefined });
|
|
1534
|
+
}
|
|
1535
|
+
// node.js stack trace already has correct source map locations
|
|
1536
|
+
return parseErrorStacktrace(error, {
|
|
1537
|
+
frameFilter: project.config.onStackTrace,
|
|
1538
|
+
ignoreStackEntries: options.fullStack ? [] : undefined
|
|
1539
|
+
});
|
|
1394
1540
|
}
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1543
|
+
function printErrorInner(error, project, options) {
|
|
1544
|
+
const { showCodeFrame = true, type, printProperties = true } = options;
|
|
1545
|
+
const logger = options.logger;
|
|
1546
|
+
let e = error;
|
|
1547
|
+
if (isPrimitive(e)) {
|
|
1548
|
+
e = {
|
|
1549
|
+
message: String(error).split(/\n/g)[0],
|
|
1550
|
+
stack: String(error)
|
|
1551
|
+
};
|
|
1395
1552
|
}
|
|
1396
|
-
|
|
1397
|
-
|
|
1553
|
+
if (!e) {
|
|
1554
|
+
const error = new Error("unknown error");
|
|
1555
|
+
e = {
|
|
1556
|
+
message: e ?? error.message,
|
|
1557
|
+
stack: error.stack
|
|
1558
|
+
};
|
|
1398
1559
|
}
|
|
1399
|
-
|
|
1400
|
-
|
|
1560
|
+
// Error may have occurred even before the configuration was resolved
|
|
1561
|
+
if (!project) {
|
|
1562
|
+
printErrorMessage(e, logger);
|
|
1563
|
+
return;
|
|
1401
1564
|
}
|
|
1402
|
-
|
|
1403
|
-
|
|
1565
|
+
const stacks = options.parseErrorStacktrace(e);
|
|
1566
|
+
const nearest = error instanceof TypeCheckError ? error.stacks[0] : stacks.find((stack) => {
|
|
1567
|
+
try {
|
|
1568
|
+
return project.server && project.getModuleById(stack.file) && existsSync(stack.file);
|
|
1569
|
+
} catch {
|
|
1570
|
+
return false;
|
|
1571
|
+
}
|
|
1572
|
+
});
|
|
1573
|
+
if (type) {
|
|
1574
|
+
printErrorType(type, project.vitest);
|
|
1404
1575
|
}
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1576
|
+
printErrorMessage(e, logger);
|
|
1577
|
+
if (options.screenshotPaths?.length) {
|
|
1578
|
+
const length = options.screenshotPaths.length;
|
|
1579
|
+
logger.error(`\nFailure screenshot${length > 1 ? "s" : ""}:`);
|
|
1580
|
+
logger.error(options.screenshotPaths.map((p) => ` - ${c.dim(relative(process.cwd(), p))}`).join("\n"));
|
|
1581
|
+
if (!e.diff) {
|
|
1582
|
+
logger.error();
|
|
1583
|
+
}
|
|
1408
1584
|
}
|
|
1409
|
-
|
|
1410
|
-
|
|
1585
|
+
if (e.codeFrame) {
|
|
1586
|
+
logger.error(`${e.codeFrame}\n`);
|
|
1411
1587
|
}
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1588
|
+
if ("__vitest_rollup_error__" in e) {
|
|
1589
|
+
// https://github.com/vitejs/vite/blob/95020ab49e12d143262859e095025cf02423c1d9/packages/vite/src/node/server/middlewares/error.ts#L25-L36
|
|
1590
|
+
const err = e.__vitest_rollup_error__;
|
|
1591
|
+
logger.error([
|
|
1592
|
+
err.plugin && ` Plugin: ${c.magenta(err.plugin)}`,
|
|
1593
|
+
err.id && ` File: ${c.cyan(err.id)}${err.loc ? `:${err.loc.line}:${err.loc.column}` : ""}`,
|
|
1594
|
+
err.frame && c.yellow(err.frame.split(/\r?\n/g).map((l) => ` `.repeat(2) + l).join(`\n`))
|
|
1595
|
+
].filter(Boolean).join("\n"));
|
|
1415
1596
|
}
|
|
1416
|
-
|
|
1417
|
-
|
|
1597
|
+
// E.g. AssertionError from assert does not set showDiff but has both actual and expected properties
|
|
1598
|
+
if (e.diff) {
|
|
1599
|
+
logger.error(`\n${e.diff}\n`);
|
|
1418
1600
|
}
|
|
1419
|
-
|
|
1420
|
-
|
|
1601
|
+
// if the error provide the frame
|
|
1602
|
+
if (e.frame) {
|
|
1603
|
+
logger.error(c.yellow(e.frame));
|
|
1604
|
+
} else {
|
|
1605
|
+
const errorProperties = printProperties ? getErrorProperties(e) : {};
|
|
1606
|
+
printStack(logger, project, stacks, nearest, errorProperties, (s) => {
|
|
1607
|
+
if (showCodeFrame && s === nearest && nearest) {
|
|
1608
|
+
const sourceCode = readFileSync(nearest.file, "utf-8");
|
|
1609
|
+
logger.error(generateCodeFrame(sourceCode.length > 1e5 ? sourceCode : logger.highlight(nearest.file, sourceCode), 4, s));
|
|
1610
|
+
}
|
|
1611
|
+
});
|
|
1421
1612
|
}
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1613
|
+
const testPath = e.VITEST_TEST_PATH;
|
|
1614
|
+
const testName = e.VITEST_TEST_NAME;
|
|
1615
|
+
const afterEnvTeardown = e.VITEST_AFTER_ENV_TEARDOWN;
|
|
1616
|
+
// testName has testPath inside
|
|
1617
|
+
if (testPath) {
|
|
1618
|
+
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.`));
|
|
1425
1619
|
}
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
if (this.renderSucceed === undefined) {
|
|
1429
|
-
this.renderSucceed = !!this.renderSucceed;
|
|
1430
|
-
}
|
|
1431
|
-
if (this.renderSucceed !== true) {
|
|
1432
|
-
this.renderSucceed = paths.length <= 1;
|
|
1433
|
-
}
|
|
1434
|
-
}
|
|
1620
|
+
if (testName) {
|
|
1621
|
+
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."));
|
|
1435
1622
|
}
|
|
1436
|
-
|
|
1437
|
-
|
|
1623
|
+
if (afterEnvTeardown) {
|
|
1624
|
+
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"));
|
|
1625
|
+
}
|
|
1626
|
+
if (typeof e.cause === "object" && e.cause && "name" in e.cause) {
|
|
1627
|
+
e.cause.name = `Caused by: ${e.cause.name}`;
|
|
1628
|
+
printErrorInner(e.cause, project, {
|
|
1629
|
+
showCodeFrame: false,
|
|
1630
|
+
logger: options.logger,
|
|
1631
|
+
parseErrorStacktrace: options.parseErrorStacktrace
|
|
1632
|
+
});
|
|
1438
1633
|
}
|
|
1634
|
+
handleImportOutsideModuleError(e.stack || e.stackStr || "", logger);
|
|
1635
|
+
return { nearest };
|
|
1439
1636
|
}
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1637
|
+
function printErrorType(type, ctx) {
|
|
1638
|
+
ctx.logger.error(`\n${errorBanner(type)}`);
|
|
1639
|
+
}
|
|
1640
|
+
const skipErrorProperties = new Set([
|
|
1641
|
+
"nameStr",
|
|
1642
|
+
"cause",
|
|
1643
|
+
"stacks",
|
|
1644
|
+
"stackStr",
|
|
1645
|
+
"type",
|
|
1646
|
+
"showDiff",
|
|
1647
|
+
"ok",
|
|
1648
|
+
"operator",
|
|
1649
|
+
"diff",
|
|
1650
|
+
"codeFrame",
|
|
1651
|
+
"actual",
|
|
1652
|
+
"expected",
|
|
1653
|
+
"diffOptions",
|
|
1654
|
+
"sourceURL",
|
|
1655
|
+
"column",
|
|
1656
|
+
"line",
|
|
1657
|
+
"fileName",
|
|
1658
|
+
"lineNumber",
|
|
1659
|
+
"columnNumber",
|
|
1660
|
+
"VITEST_TEST_NAME",
|
|
1661
|
+
"VITEST_TEST_PATH",
|
|
1662
|
+
"VITEST_AFTER_ENV_TEARDOWN",
|
|
1663
|
+
...Object.getOwnPropertyNames(Error.prototype),
|
|
1664
|
+
...Object.getOwnPropertyNames(Object.prototype)
|
|
1665
|
+
]);
|
|
1666
|
+
function getErrorProperties(e) {
|
|
1667
|
+
const errorObject = Object.create(null);
|
|
1668
|
+
if (e.name === "AssertionError") {
|
|
1669
|
+
return errorObject;
|
|
1454
1670
|
}
|
|
1455
|
-
|
|
1456
|
-
if
|
|
1457
|
-
|
|
1671
|
+
for (const key of Object.getOwnPropertyNames(e)) {
|
|
1672
|
+
// print the original stack if it was ever changed manually by the user
|
|
1673
|
+
if (key === "stack" && e[key] != null && typeof e[key] !== "string") {
|
|
1674
|
+
errorObject[key] = e[key];
|
|
1675
|
+
} else if (key !== "stack" && !skipErrorProperties.has(key)) {
|
|
1676
|
+
errorObject[key] = e[key];
|
|
1458
1677
|
}
|
|
1459
1678
|
}
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1679
|
+
return errorObject;
|
|
1680
|
+
}
|
|
1681
|
+
const esmErrors = ["Cannot use import statement outside a module", "Unexpected token 'export'"];
|
|
1682
|
+
function handleImportOutsideModuleError(stack, logger) {
|
|
1683
|
+
if (!esmErrors.some((e) => stack.includes(e))) {
|
|
1684
|
+
return;
|
|
1464
1685
|
}
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
this.renderer?.finish();
|
|
1472
|
-
super.onFinished(files, errors);
|
|
1686
|
+
const path = normalize(stack.split("\n")[0].trim());
|
|
1687
|
+
let name = path.split("/node_modules/").pop() || "";
|
|
1688
|
+
if (name?.startsWith("@")) {
|
|
1689
|
+
name = name.split("/").slice(0, 2).join("/");
|
|
1690
|
+
} else {
|
|
1691
|
+
name = name.split("/")[0];
|
|
1473
1692
|
}
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1693
|
+
if (name) {
|
|
1694
|
+
printModuleWarningForPackage(logger, path, name);
|
|
1695
|
+
} else {
|
|
1696
|
+
printModuleWarningForSourceCode(logger, path);
|
|
1478
1697
|
}
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1698
|
+
}
|
|
1699
|
+
function printModuleWarningForPackage(logger, path, name) {
|
|
1700
|
+
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 {
|
|
1701
|
+
test: {
|
|
1702
|
+
server: {
|
|
1703
|
+
deps: {
|
|
1704
|
+
inline: [
|
|
1705
|
+
${c.yellow(c.bold(`"${name}"`))}
|
|
1706
|
+
]
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
}\n`)));
|
|
1711
|
+
}
|
|
1712
|
+
function printModuleWarningForSourceCode(logger, path) {
|
|
1713
|
+
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."));
|
|
1714
|
+
}
|
|
1715
|
+
function printErrorMessage(error, logger) {
|
|
1716
|
+
const errorName = error.name || error.nameStr || "Unknown Error";
|
|
1717
|
+
if (!error.message) {
|
|
1718
|
+
logger.error(error);
|
|
1719
|
+
return;
|
|
1485
1720
|
}
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1721
|
+
if (error.message.length > 5e3) {
|
|
1722
|
+
// Protect against infinite stack trace in tinyrainbow
|
|
1723
|
+
logger.error(`${c.red(c.bold(errorName))}: ${error.message}`);
|
|
1724
|
+
} else {
|
|
1725
|
+
logger.error(c.red(`${c.bold(errorName)}: ${error.message}`));
|
|
1491
1726
|
}
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
return;
|
|
1500
|
-
}
|
|
1501
|
-
const finishedTests = Array.from(this.tests).filter((entry) => entry[1] !== "pending");
|
|
1502
|
-
if (finishedTests.length < columns) {
|
|
1503
|
-
return;
|
|
1504
|
-
}
|
|
1505
|
-
const states = [];
|
|
1506
|
-
let count = 0;
|
|
1507
|
-
for (const [id, state] of finishedTests) {
|
|
1508
|
-
if (count++ >= columns) {
|
|
1509
|
-
break;
|
|
1510
|
-
}
|
|
1511
|
-
this.tests.delete(id);
|
|
1512
|
-
states.push(state);
|
|
1513
|
-
}
|
|
1514
|
-
this.ctx.logger.log(formatTests(states));
|
|
1515
|
-
this.renderer?.schedule();
|
|
1727
|
+
}
|
|
1728
|
+
function printStack(logger, project, stack, highlight, errorProperties, onStack) {
|
|
1729
|
+
for (const frame of stack) {
|
|
1730
|
+
const color = frame === highlight ? c.cyan : c.gray;
|
|
1731
|
+
const path = relative(project.config.root, frame.file);
|
|
1732
|
+
logger.error(color(` ${c.dim(F_POINTER)} ${[frame.method, `${path}:${c.dim(`${frame.line}:${frame.column}`)}`].filter(Boolean).join(" ")}`));
|
|
1733
|
+
onStack?.(frame);
|
|
1516
1734
|
}
|
|
1517
|
-
|
|
1518
|
-
|
|
1735
|
+
if (stack.length) {
|
|
1736
|
+
logger.error();
|
|
1737
|
+
}
|
|
1738
|
+
if (hasProperties(errorProperties)) {
|
|
1739
|
+
logger.error(c.red(c.dim(divider())));
|
|
1740
|
+
const propertiesString = inspect(errorProperties);
|
|
1741
|
+
logger.error(c.red(c.bold("Serialized Error:")), c.gray(propertiesString));
|
|
1519
1742
|
}
|
|
1520
1743
|
}
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
const fail = {
|
|
1526
|
-
char: "x",
|
|
1527
|
-
color: c.red
|
|
1528
|
-
};
|
|
1529
|
-
const pending = {
|
|
1530
|
-
char: "*",
|
|
1531
|
-
color: c.yellow
|
|
1532
|
-
};
|
|
1533
|
-
const skip = {
|
|
1534
|
-
char: "-",
|
|
1535
|
-
color: (char) => c.dim(c.gray(char))
|
|
1536
|
-
};
|
|
1537
|
-
function getIcon(state) {
|
|
1538
|
-
switch (state) {
|
|
1539
|
-
case "passed": return pass;
|
|
1540
|
-
case "failed": return fail;
|
|
1541
|
-
case "skipped": return skip;
|
|
1542
|
-
default: return pending;
|
|
1744
|
+
function hasProperties(obj) {
|
|
1745
|
+
// eslint-disable-next-line no-unreachable-loop
|
|
1746
|
+
for (const _key in obj) {
|
|
1747
|
+
return true;
|
|
1543
1748
|
}
|
|
1749
|
+
return false;
|
|
1544
1750
|
}
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
let currentIcon = pending;
|
|
1751
|
+
function generateCodeFrame(source, indent = 0, loc, range = 2) {
|
|
1752
|
+
const start = typeof loc === "object" ? positionToOffset(source, loc.line, loc.column) : loc;
|
|
1753
|
+
const end = start;
|
|
1754
|
+
const lines = source.split(lineSplitRE);
|
|
1755
|
+
const nl = /\r\n/.test(source) ? 2 : 1;
|
|
1551
1756
|
let count = 0;
|
|
1552
|
-
let
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1757
|
+
let res = [];
|
|
1758
|
+
const columns = process.stdout?.columns || 80;
|
|
1759
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1760
|
+
count += lines[i].length + nl;
|
|
1761
|
+
if (count >= start) {
|
|
1762
|
+
for (let j = i - range; j <= i + range || end > count; j++) {
|
|
1763
|
+
if (j < 0 || j >= lines.length) {
|
|
1764
|
+
continue;
|
|
1765
|
+
}
|
|
1766
|
+
const lineLength = lines[j].length;
|
|
1767
|
+
// too long, maybe it's a minified file, skip for codeframe
|
|
1768
|
+
if (stripVTControlCharacters(lines[j]).length > 200) {
|
|
1769
|
+
return "";
|
|
1770
|
+
}
|
|
1771
|
+
res.push(lineNo(j + 1) + truncateString(lines[j].replace(/\t/g, " "), columns - 5 - indent));
|
|
1772
|
+
if (j === i) {
|
|
1773
|
+
// push underline
|
|
1774
|
+
const pad = start - (count - lineLength) + (nl - 1);
|
|
1775
|
+
const length = Math.max(1, end > count ? lineLength - pad : end - start);
|
|
1776
|
+
res.push(lineNo() + " ".repeat(pad) + c.red("^".repeat(length)));
|
|
1777
|
+
} else if (j > i) {
|
|
1778
|
+
if (end > count) {
|
|
1779
|
+
const length = Math.max(1, Math.min(end - count, lineLength));
|
|
1780
|
+
res.push(lineNo() + c.red("^".repeat(length)));
|
|
1781
|
+
}
|
|
1782
|
+
count += lineLength + 1;
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
break;
|
|
1558
1786
|
}
|
|
1559
|
-
output += currentIcon.color(currentIcon.char.repeat(count));
|
|
1560
|
-
count = 1;
|
|
1561
|
-
currentIcon = icon;
|
|
1562
1787
|
}
|
|
1563
|
-
|
|
1564
|
-
|
|
1788
|
+
if (indent) {
|
|
1789
|
+
res = res.map((line) => " ".repeat(indent) + line);
|
|
1790
|
+
}
|
|
1791
|
+
return res.join("\n");
|
|
1792
|
+
}
|
|
1793
|
+
function lineNo(no = "") {
|
|
1794
|
+
return c.gray(`${String(no).padStart(3, " ")}| `);
|
|
1565
1795
|
}
|
|
1566
1796
|
|
|
1567
1797
|
class GithubActionsReporter {
|
|
1568
1798
|
ctx = undefined;
|
|
1799
|
+
options;
|
|
1800
|
+
constructor(options = {}) {
|
|
1801
|
+
this.options = options;
|
|
1802
|
+
}
|
|
1569
1803
|
onInit(ctx) {
|
|
1570
1804
|
this.ctx = ctx;
|
|
1571
1805
|
}
|
|
1572
1806
|
onFinished(files = [], errors = []) {
|
|
1807
|
+
// collect all errors and associate them with projects
|
|
1573
1808
|
const projectErrors = new Array();
|
|
1574
1809
|
for (const error of errors) {
|
|
1575
1810
|
projectErrors.push({
|
|
@@ -1596,6 +1831,8 @@ class GithubActionsReporter {
|
|
|
1596
1831
|
}
|
|
1597
1832
|
}
|
|
1598
1833
|
}
|
|
1834
|
+
const onWritePath = this.options.onWritePath ?? defaultOnWritePath;
|
|
1835
|
+
// format errors via `printError`
|
|
1599
1836
|
for (const { project, title, error, file } of projectErrors) {
|
|
1600
1837
|
const result = capturePrintError(error, this.ctx, {
|
|
1601
1838
|
project,
|
|
@@ -1608,7 +1845,7 @@ class GithubActionsReporter {
|
|
|
1608
1845
|
const formatted = formatMessage({
|
|
1609
1846
|
command: "error",
|
|
1610
1847
|
properties: {
|
|
1611
|
-
file: stack.file,
|
|
1848
|
+
file: onWritePath(stack.file),
|
|
1612
1849
|
title,
|
|
1613
1850
|
line: String(stack.line),
|
|
1614
1851
|
column: String(stack.column)
|
|
@@ -1619,6 +1856,12 @@ class GithubActionsReporter {
|
|
|
1619
1856
|
}
|
|
1620
1857
|
}
|
|
1621
1858
|
}
|
|
1859
|
+
function defaultOnWritePath(path) {
|
|
1860
|
+
return path;
|
|
1861
|
+
}
|
|
1862
|
+
// workflow command formatting based on
|
|
1863
|
+
// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-error-message
|
|
1864
|
+
// https://github.com/actions/toolkit/blob/f1d9b4b985e6f0f728b4b766db73498403fd5ca3/packages/core/src/command.ts#L80-L85
|
|
1622
1865
|
function formatMessage({ command, properties, message }) {
|
|
1623
1866
|
let result = `::${command}`;
|
|
1624
1867
|
Object.entries(properties).forEach(([k, v], i) => {
|
|
@@ -1635,6 +1878,17 @@ function escapeProperty(s) {
|
|
|
1635
1878
|
return s.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A").replace(/:/g, "%3A").replace(/,/g, "%2C");
|
|
1636
1879
|
}
|
|
1637
1880
|
|
|
1881
|
+
class HangingProcessReporter {
|
|
1882
|
+
whyRunning;
|
|
1883
|
+
onInit() {
|
|
1884
|
+
const _require = createRequire(import.meta.url);
|
|
1885
|
+
this.whyRunning = _require("why-is-node-running");
|
|
1886
|
+
}
|
|
1887
|
+
onProcessTimeout() {
|
|
1888
|
+
this.whyRunning?.();
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1638
1892
|
const StatusMap = {
|
|
1639
1893
|
fail: "failed",
|
|
1640
1894
|
only: "pending",
|
|
@@ -1777,16 +2031,18 @@ function flattenTasks$1(task, baseName = "") {
|
|
|
1777
2031
|
}];
|
|
1778
2032
|
}
|
|
1779
2033
|
}
|
|
2034
|
+
// https://gist.github.com/john-doherty/b9195065884cdbfd2017a4756e6409cc
|
|
1780
2035
|
function removeInvalidXMLCharacters(value, removeDiscouragedChars) {
|
|
1781
2036
|
let regex = /([\0-\x08\v\f\x0E-\x1F\uFFFD\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])/g;
|
|
1782
2037
|
value = String(value || "").replace(regex, "");
|
|
1783
2038
|
{
|
|
2039
|
+
// remove everything discouraged by XML 1.0 specifications
|
|
1784
2040
|
regex = new RegExp(
|
|
1785
2041
|
/* eslint-disable regexp/prefer-character-class, regexp/no-obscure-range, regexp/no-useless-non-capturing-group */
|
|
1786
2042
|
"([\\x7F-\\x84]|[\\x86-\\x9F]|[\\uFDD0-\\uFDEF]|\\uD83F[\\uDFFE\\uDFFF]|(?:\\uD87F[\\uDF" + "FE\\uDFFF])|\\uD8BF[\\uDFFE\\uDFFF]|\\uD8FF[\\uDFFE\\uDFFF]|(?:\\uD93F[\\uDFFE\\uD" + "FFF])|\\uD97F[\\uDFFE\\uDFFF]|\\uD9BF[\\uDFFE\\uDFFF]|\\uD9FF[\\uDFFE\\uDFFF]" + "|\\uDA3F[\\uDFFE\\uDFFF]|\\uDA7F[\\uDFFE\\uDFFF]|\\uDABF[\\uDFFE\\uDFFF]|(?:\\" + "uDAFF[\\uDFFE\\uDFFF])|\\uDB3F[\\uDFFE\\uDFFF]|\\uDB7F[\\uDFFE\\uDFFF]|(?:\\uDBBF" + "[\\uDFFE\\uDFFF])|\\uDBFF[\\uDFFE\\uDFFF](?:[\\0-\\t\\v\\f\\x0E-\\u2027\\u202A-\\uD7FF\\" + "uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|" + "(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF]))",
|
|
1787
2043
|
"g"
|
|
1788
2044
|
/* eslint-enable */
|
|
1789
|
-
);
|
|
2045
|
+
);
|
|
1790
2046
|
value = value.replace(regex, "");
|
|
1791
2047
|
}
|
|
1792
2048
|
return value;
|
|
@@ -1932,6 +2188,7 @@ class JUnitReporter {
|
|
|
1932
2188
|
failures: 0,
|
|
1933
2189
|
skipped: 0
|
|
1934
2190
|
});
|
|
2191
|
+
// inject failed suites to surface errors during beforeAll/afterAll
|
|
1935
2192
|
const suites = getSuites(file);
|
|
1936
2193
|
for (const suite of suites) {
|
|
1937
2194
|
if (suite.result?.errors) {
|
|
@@ -1939,6 +2196,7 @@ class JUnitReporter {
|
|
|
1939
2196
|
stats.failures += 1;
|
|
1940
2197
|
}
|
|
1941
2198
|
}
|
|
2199
|
+
// If there are no tests, but the file failed to load, we still want to report it as a failure
|
|
1942
2200
|
if (tasks.length === 0 && file.result?.state === "fail") {
|
|
1943
2201
|
stats.failures = 1;
|
|
1944
2202
|
tasks.push({
|
|
@@ -2029,6 +2287,7 @@ class TapReporter {
|
|
|
2029
2287
|
this.logger.log(`name: ${yamlString(String(errorName))}`);
|
|
2030
2288
|
this.logger.log(`message: ${yamlString(String(error.message))}`);
|
|
2031
2289
|
if (stack) {
|
|
2290
|
+
// For compatibility with tap-mocha-reporter
|
|
2032
2291
|
this.logger.log(`stack: ${yamlString(`${stack.file}:${stack.line}:${stack.column}`)}`);
|
|
2033
2292
|
}
|
|
2034
2293
|
}
|
|
@@ -2103,12 +2362,18 @@ class VerboseReporter extends DefaultReporter {
|
|
|
2103
2362
|
verbose = true;
|
|
2104
2363
|
renderSucceed = true;
|
|
2105
2364
|
printTestModule(module) {
|
|
2365
|
+
// still print the test module in TTY,
|
|
2366
|
+
// but don't print it in the CLI because we
|
|
2367
|
+
// print all the tests when they finish
|
|
2368
|
+
// instead of printing them when the test file finishes
|
|
2106
2369
|
if (this.isTTY) {
|
|
2107
2370
|
return super.printTestModule(module);
|
|
2108
2371
|
}
|
|
2109
2372
|
}
|
|
2110
2373
|
onTestCaseResult(test) {
|
|
2111
2374
|
super.onTestCaseResult(test);
|
|
2375
|
+
// don't print tests in TTY as they go, only print them
|
|
2376
|
+
// in the CLI when they finish
|
|
2112
2377
|
if (this.isTTY) {
|
|
2113
2378
|
return;
|
|
2114
2379
|
}
|
|
@@ -2147,6 +2412,7 @@ class VerboseReporter extends DefaultReporter {
|
|
|
2147
2412
|
return " ".repeat(getIndentation(test));
|
|
2148
2413
|
}
|
|
2149
2414
|
formatShortError() {
|
|
2415
|
+
// Short errors are not shown in tree-view
|
|
2150
2416
|
return "";
|
|
2151
2417
|
}
|
|
2152
2418
|
}
|
|
@@ -2157,272 +2423,6 @@ function getIndentation(suite, level = 1) {
|
|
|
2157
2423
|
return level;
|
|
2158
2424
|
}
|
|
2159
2425
|
|
|
2160
|
-
function createBenchmarkJsonReport(files) {
|
|
2161
|
-
const report = { files: [] };
|
|
2162
|
-
for (const file of files) {
|
|
2163
|
-
const groups = [];
|
|
2164
|
-
for (const task of getTasks(file)) {
|
|
2165
|
-
if (task?.type === "suite") {
|
|
2166
|
-
const benchmarks = [];
|
|
2167
|
-
for (const t of task.tasks) {
|
|
2168
|
-
const benchmark = t.meta.benchmark && t.result?.benchmark;
|
|
2169
|
-
if (benchmark) {
|
|
2170
|
-
benchmarks.push({
|
|
2171
|
-
id: t.id,
|
|
2172
|
-
...benchmark,
|
|
2173
|
-
samples: []
|
|
2174
|
-
});
|
|
2175
|
-
}
|
|
2176
|
-
}
|
|
2177
|
-
if (benchmarks.length) {
|
|
2178
|
-
groups.push({
|
|
2179
|
-
fullName: getFullName(task, " > "),
|
|
2180
|
-
benchmarks
|
|
2181
|
-
});
|
|
2182
|
-
}
|
|
2183
|
-
}
|
|
2184
|
-
}
|
|
2185
|
-
report.files.push({
|
|
2186
|
-
filepath: file.filepath,
|
|
2187
|
-
groups
|
|
2188
|
-
});
|
|
2189
|
-
}
|
|
2190
|
-
return report;
|
|
2191
|
-
}
|
|
2192
|
-
function flattenFormattedBenchmarkReport(report) {
|
|
2193
|
-
const flat = {};
|
|
2194
|
-
for (const file of report.files) {
|
|
2195
|
-
for (const group of file.groups) {
|
|
2196
|
-
for (const t of group.benchmarks) {
|
|
2197
|
-
flat[t.id] = t;
|
|
2198
|
-
}
|
|
2199
|
-
}
|
|
2200
|
-
}
|
|
2201
|
-
return flat;
|
|
2202
|
-
}
|
|
2203
|
-
|
|
2204
|
-
const outputMap = new WeakMap();
|
|
2205
|
-
function formatNumber(number) {
|
|
2206
|
-
const res = String(number.toFixed(number < 100 ? 4 : 2)).split(".");
|
|
2207
|
-
return res[0].replace(/(?=(?:\d{3})+$)\B/g, ",") + (res[1] ? `.${res[1]}` : "");
|
|
2208
|
-
}
|
|
2209
|
-
const tableHead = [
|
|
2210
|
-
"name",
|
|
2211
|
-
"hz",
|
|
2212
|
-
"min",
|
|
2213
|
-
"max",
|
|
2214
|
-
"mean",
|
|
2215
|
-
"p75",
|
|
2216
|
-
"p99",
|
|
2217
|
-
"p995",
|
|
2218
|
-
"p999",
|
|
2219
|
-
"rme",
|
|
2220
|
-
"samples"
|
|
2221
|
-
];
|
|
2222
|
-
function renderBenchmarkItems(result) {
|
|
2223
|
-
return [
|
|
2224
|
-
result.name,
|
|
2225
|
-
formatNumber(result.hz || 0),
|
|
2226
|
-
formatNumber(result.min || 0),
|
|
2227
|
-
formatNumber(result.max || 0),
|
|
2228
|
-
formatNumber(result.mean || 0),
|
|
2229
|
-
formatNumber(result.p75 || 0),
|
|
2230
|
-
formatNumber(result.p99 || 0),
|
|
2231
|
-
formatNumber(result.p995 || 0),
|
|
2232
|
-
formatNumber(result.p999 || 0),
|
|
2233
|
-
`±${(result.rme || 0).toFixed(2)}%`,
|
|
2234
|
-
(result.sampleCount || 0).toString()
|
|
2235
|
-
];
|
|
2236
|
-
}
|
|
2237
|
-
function computeColumnWidths(results) {
|
|
2238
|
-
const rows = [tableHead, ...results.map((v) => renderBenchmarkItems(v))];
|
|
2239
|
-
return Array.from(tableHead, (_, i) => Math.max(...rows.map((row) => stripVTControlCharacters(row[i]).length)));
|
|
2240
|
-
}
|
|
2241
|
-
function padRow(row, widths) {
|
|
2242
|
-
return row.map((v, i) => i ? v.padStart(widths[i], " ") : v.padEnd(widths[i], " "));
|
|
2243
|
-
}
|
|
2244
|
-
function renderTableHead(widths) {
|
|
2245
|
-
return " ".repeat(3) + padRow(tableHead, widths).map(c.bold).join(" ");
|
|
2246
|
-
}
|
|
2247
|
-
function renderBenchmark(result, widths) {
|
|
2248
|
-
const padded = padRow(renderBenchmarkItems(result), widths);
|
|
2249
|
-
return [
|
|
2250
|
-
padded[0],
|
|
2251
|
-
c.blue(padded[1]),
|
|
2252
|
-
c.cyan(padded[2]),
|
|
2253
|
-
c.cyan(padded[3]),
|
|
2254
|
-
c.cyan(padded[4]),
|
|
2255
|
-
c.cyan(padded[5]),
|
|
2256
|
-
c.cyan(padded[6]),
|
|
2257
|
-
c.cyan(padded[7]),
|
|
2258
|
-
c.cyan(padded[8]),
|
|
2259
|
-
c.dim(padded[9]),
|
|
2260
|
-
c.dim(padded[10])
|
|
2261
|
-
].join(" ");
|
|
2262
|
-
}
|
|
2263
|
-
function renderTable(options) {
|
|
2264
|
-
const output = [];
|
|
2265
|
-
const benchMap = {};
|
|
2266
|
-
for (const task of options.tasks) {
|
|
2267
|
-
if (task.meta.benchmark && task.result?.benchmark) {
|
|
2268
|
-
benchMap[task.id] = {
|
|
2269
|
-
current: task.result.benchmark,
|
|
2270
|
-
baseline: options.compare?.[task.id]
|
|
2271
|
-
};
|
|
2272
|
-
}
|
|
2273
|
-
}
|
|
2274
|
-
const benchCount = Object.entries(benchMap).length;
|
|
2275
|
-
const columnWidths = computeColumnWidths(Object.values(benchMap).flatMap((v) => [v.current, v.baseline]).filter(notNullish));
|
|
2276
|
-
let idx = 0;
|
|
2277
|
-
const padding = " ".repeat(1 );
|
|
2278
|
-
for (const task of options.tasks) {
|
|
2279
|
-
const duration = task.result?.duration;
|
|
2280
|
-
const bench = benchMap[task.id];
|
|
2281
|
-
let prefix = "";
|
|
2282
|
-
if (idx === 0 && task.meta?.benchmark) {
|
|
2283
|
-
prefix += `${renderTableHead(columnWidths)}\n${padding}`;
|
|
2284
|
-
}
|
|
2285
|
-
prefix += ` ${getStateSymbol(task)} `;
|
|
2286
|
-
let suffix = "";
|
|
2287
|
-
if (task.type === "suite") {
|
|
2288
|
-
suffix += c.dim(` (${getTests(task).length})`);
|
|
2289
|
-
}
|
|
2290
|
-
if (task.mode === "skip" || task.mode === "todo") {
|
|
2291
|
-
suffix += c.dim(c.gray(" [skipped]"));
|
|
2292
|
-
}
|
|
2293
|
-
if (duration != null) {
|
|
2294
|
-
const color = duration > options.slowTestThreshold ? c.yellow : c.green;
|
|
2295
|
-
suffix += color(` ${Math.round(duration)}${c.dim("ms")}`);
|
|
2296
|
-
}
|
|
2297
|
-
if (options.showHeap && task.result?.heap != null) {
|
|
2298
|
-
suffix += c.magenta(` ${Math.floor(task.result.heap / 1024 / 1024)} MB heap used`);
|
|
2299
|
-
}
|
|
2300
|
-
if (bench) {
|
|
2301
|
-
let body = renderBenchmark(bench.current, columnWidths);
|
|
2302
|
-
if (options.compare && bench.baseline) {
|
|
2303
|
-
if (bench.current.hz) {
|
|
2304
|
-
const diff = bench.current.hz / bench.baseline.hz;
|
|
2305
|
-
const diffFixed = diff.toFixed(2);
|
|
2306
|
-
if (diffFixed === "1.0.0") {
|
|
2307
|
-
body += c.gray(` [${diffFixed}x]`);
|
|
2308
|
-
}
|
|
2309
|
-
if (diff > 1) {
|
|
2310
|
-
body += c.blue(` [${diffFixed}x] ⇑`);
|
|
2311
|
-
} else {
|
|
2312
|
-
body += c.red(` [${diffFixed}x] ⇓`);
|
|
2313
|
-
}
|
|
2314
|
-
}
|
|
2315
|
-
output.push(padding + prefix + body + suffix);
|
|
2316
|
-
const bodyBaseline = renderBenchmark(bench.baseline, columnWidths);
|
|
2317
|
-
output.push(`${padding} ${bodyBaseline} ${c.dim("(baseline)")}`);
|
|
2318
|
-
} else {
|
|
2319
|
-
if (bench.current.rank === 1 && benchCount > 1) {
|
|
2320
|
-
body += c.bold(c.green(" fastest"));
|
|
2321
|
-
}
|
|
2322
|
-
if (bench.current.rank === benchCount && benchCount > 2) {
|
|
2323
|
-
body += c.bold(c.gray(" slowest"));
|
|
2324
|
-
}
|
|
2325
|
-
output.push(padding + prefix + body + suffix);
|
|
2326
|
-
}
|
|
2327
|
-
} else {
|
|
2328
|
-
output.push(padding + prefix + task.name + suffix);
|
|
2329
|
-
}
|
|
2330
|
-
if (task.result?.state !== "pass" && outputMap.get(task) != null) {
|
|
2331
|
-
let data = outputMap.get(task);
|
|
2332
|
-
if (typeof data === "string") {
|
|
2333
|
-
data = stripVTControlCharacters(data.trim().split("\n").filter(Boolean).pop());
|
|
2334
|
-
if (data === "") {
|
|
2335
|
-
data = undefined;
|
|
2336
|
-
}
|
|
2337
|
-
}
|
|
2338
|
-
if (data != null) {
|
|
2339
|
-
const out = ` ${" ".repeat(options.level)}${F_RIGHT} ${data}`;
|
|
2340
|
-
output.push(c.gray(truncateString(out, options.columns)));
|
|
2341
|
-
}
|
|
2342
|
-
}
|
|
2343
|
-
idx++;
|
|
2344
|
-
}
|
|
2345
|
-
return output.filter(Boolean).join("\n");
|
|
2346
|
-
}
|
|
2347
|
-
|
|
2348
|
-
class BenchmarkReporter extends DefaultReporter {
|
|
2349
|
-
compare;
|
|
2350
|
-
async onInit(ctx) {
|
|
2351
|
-
super.onInit(ctx);
|
|
2352
|
-
if (this.ctx.config.benchmark?.compare) {
|
|
2353
|
-
const compareFile = pathe.resolve(this.ctx.config.root, this.ctx.config.benchmark?.compare);
|
|
2354
|
-
try {
|
|
2355
|
-
this.compare = flattenFormattedBenchmarkReport(JSON.parse(await fs.promises.readFile(compareFile, "utf-8")));
|
|
2356
|
-
} catch (e) {
|
|
2357
|
-
this.error(`Failed to read '${compareFile}'`, e);
|
|
2358
|
-
}
|
|
2359
|
-
}
|
|
2360
|
-
}
|
|
2361
|
-
onTaskUpdate(packs) {
|
|
2362
|
-
for (const pack of packs) {
|
|
2363
|
-
const task = this.ctx.state.idMap.get(pack[0]);
|
|
2364
|
-
if (task?.type === "suite" && task.result?.state !== "run") {
|
|
2365
|
-
task.tasks.filter((task) => task.result?.benchmark).sort((benchA, benchB) => benchA.result.benchmark.mean - benchB.result.benchmark.mean).forEach((bench, idx) => {
|
|
2366
|
-
bench.result.benchmark.rank = Number(idx) + 1;
|
|
2367
|
-
});
|
|
2368
|
-
}
|
|
2369
|
-
}
|
|
2370
|
-
}
|
|
2371
|
-
onTestSuiteResult(testSuite) {
|
|
2372
|
-
super.onTestSuiteResult(testSuite);
|
|
2373
|
-
this.printSuiteTable(testSuite);
|
|
2374
|
-
}
|
|
2375
|
-
printTestModule(testModule) {
|
|
2376
|
-
this.printSuiteTable(testModule);
|
|
2377
|
-
}
|
|
2378
|
-
printSuiteTable(testTask) {
|
|
2379
|
-
const state = testTask.state();
|
|
2380
|
-
if (state === "pending" || state === "queued") {
|
|
2381
|
-
return;
|
|
2382
|
-
}
|
|
2383
|
-
const benches = testTask.task.tasks.filter((t) => t.meta.benchmark);
|
|
2384
|
-
const duration = testTask.task.result?.duration || 0;
|
|
2385
|
-
if (benches.length > 0 && benches.every((t) => t.result?.state !== "run" && t.result?.state !== "queued")) {
|
|
2386
|
-
let title = `\n ${getStateSymbol(testTask.task)} ${formatProjectName(testTask.project)}${getFullName(testTask.task, c.dim(" > "))}`;
|
|
2387
|
-
if (duration != null && duration > this.ctx.config.slowTestThreshold) {
|
|
2388
|
-
title += c.yellow(` ${Math.round(duration)}${c.dim("ms")}`);
|
|
2389
|
-
}
|
|
2390
|
-
this.log(title);
|
|
2391
|
-
this.log(renderTable({
|
|
2392
|
-
tasks: benches,
|
|
2393
|
-
level: 1,
|
|
2394
|
-
columns: this.ctx.logger.getColumns(),
|
|
2395
|
-
compare: this.compare,
|
|
2396
|
-
showHeap: this.ctx.config.logHeapUsage,
|
|
2397
|
-
slowTestThreshold: this.ctx.config.slowTestThreshold
|
|
2398
|
-
}));
|
|
2399
|
-
}
|
|
2400
|
-
}
|
|
2401
|
-
async onFinished(files = this.ctx.state.getFiles(), errors = this.ctx.state.getUnhandledErrors()) {
|
|
2402
|
-
super.onFinished(files, errors);
|
|
2403
|
-
let outputFile = this.ctx.config.benchmark?.outputJson;
|
|
2404
|
-
if (outputFile) {
|
|
2405
|
-
outputFile = pathe.resolve(this.ctx.config.root, outputFile);
|
|
2406
|
-
const outputDirectory = pathe.dirname(outputFile);
|
|
2407
|
-
if (!fs.existsSync(outputDirectory)) {
|
|
2408
|
-
await fs.promises.mkdir(outputDirectory, { recursive: true });
|
|
2409
|
-
}
|
|
2410
|
-
const output = createBenchmarkJsonReport(files);
|
|
2411
|
-
await fs.promises.writeFile(outputFile, JSON.stringify(output, null, 2));
|
|
2412
|
-
this.log(`Benchmark report written to ${outputFile}`);
|
|
2413
|
-
}
|
|
2414
|
-
}
|
|
2415
|
-
}
|
|
2416
|
-
|
|
2417
|
-
class VerboseBenchmarkReporter extends BenchmarkReporter {
|
|
2418
|
-
verbose = true;
|
|
2419
|
-
}
|
|
2420
|
-
|
|
2421
|
-
const BenchmarkReportsMap = {
|
|
2422
|
-
default: BenchmarkReporter,
|
|
2423
|
-
verbose: VerboseBenchmarkReporter
|
|
2424
|
-
};
|
|
2425
|
-
|
|
2426
2426
|
const ReportersMap = {
|
|
2427
2427
|
"default": DefaultReporter,
|
|
2428
2428
|
"basic": BasicReporter,
|
|
@@ -2437,4 +2437,4 @@ const ReportersMap = {
|
|
|
2437
2437
|
"github-actions": GithubActionsReporter
|
|
2438
2438
|
};
|
|
2439
2439
|
|
|
2440
|
-
export { BasicReporter as B, DefaultReporter as D, GithubActionsReporter as G, HangingProcessReporter as H, JsonReporter as J, ReportersMap as R, TapFlatReporter as T,
|
|
2440
|
+
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 };
|