vitest 3.0.0-beta.1 → 3.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.d.ts +12 -9
- package/dist/browser.js +2 -2
- package/dist/chunks/{base.CkcgFVQd.js → base.CUgXReRN.js} +1 -1
- package/dist/chunks/{cac.CWCZimpS.js → cac.Xzv7eNWw.js} +18 -13
- package/dist/chunks/{cli-api.BKUOv0Nc.js → cli-api.CETCDGgZ.js} +915 -531
- package/dist/chunks/{coverage.BoMDb1ip.js → coverage.BWeNbfBa.js} +4 -4
- package/dist/chunks/{environment.CT0jpO-1.d.ts → environment.d8YfPkTm.d.ts} +1 -3
- package/dist/chunks/{globals.DJTzb7B3.js → globals.BFncSRNA.js} +2 -2
- package/dist/chunks/{index.DKe7vK-G.js → index.9ZEBV_TJ.js} +635 -606
- package/dist/chunks/{index.BqHViJW9.js → index.CkWmZCXU.js} +1 -1
- package/dist/chunks/{index.CkOJwybT.js → index.DoV7W5gc.js} +2 -2
- package/dist/chunks/{reporters.BZbwTvrM.d.ts → reporters.DTtxC3KQ.d.ts} +447 -374
- package/dist/chunks/{resolveConfig.3rGGWga5.js → resolveConfig.BA-_OKEx.js} +5529 -5532
- package/dist/chunks/{runBaseTests.C6huCAng.js → runBaseTests.D0dWpzZV.js} +11 -10
- package/dist/chunks/{setup-common.B5ClyS48.js → setup-common.Cp_bu5q3.js} +1 -1
- package/dist/chunks/types.BOjykUpq.d.ts +27 -0
- package/dist/chunks/{vi.CZKezqeD.js → vi.S4Fq8wSo.js} +2 -1
- package/dist/chunks/{vite.DIfmneq0.d.ts → vite.CXaetSK3.d.ts} +1 -1
- package/dist/chunks/{worker.umPNbBNk.d.ts → worker.ClntunZp.d.ts} +1 -1
- package/dist/chunks/{worker.CmzGeuVD.d.ts → worker.o1PBoDdo.d.ts} +3 -3
- package/dist/cli.js +1 -1
- package/dist/config.d.ts +6 -8
- package/dist/coverage.d.ts +4 -6
- package/dist/coverage.js +1 -1
- package/dist/environments.d.ts +2 -2
- package/dist/execute.d.ts +2 -2
- package/dist/index.d.ts +11 -14
- package/dist/index.js +2 -2
- package/dist/node.d.ts +22 -17
- package/dist/node.js +65 -31
- package/dist/reporters.d.ts +4 -6
- package/dist/reporters.js +1 -1
- package/dist/runners.d.ts +2 -2
- package/dist/runners.js +1 -1
- package/dist/suite.d.ts +1 -1
- package/dist/workers/forks.js +1 -1
- package/dist/workers/runVmTests.js +7 -7
- package/dist/workers/threads.js +1 -1
- package/dist/workers.d.ts +3 -3
- package/dist/workers.js +1 -1
- package/package.json +13 -13
|
@@ -20,351 +20,6 @@ import require$$0$1 from 'events';
|
|
|
20
20
|
import { createRequire } from 'node:module';
|
|
21
21
|
import { hostname } from 'node:os';
|
|
22
22
|
|
|
23
|
-
class ReportedTaskImplementation {
|
|
24
|
-
/**
|
|
25
|
-
* Task instance.
|
|
26
|
-
* @experimental Public runner task API is experimental and does not follow semver.
|
|
27
|
-
*/
|
|
28
|
-
task;
|
|
29
|
-
/**
|
|
30
|
-
* The project assosiacted with the test or suite.
|
|
31
|
-
*/
|
|
32
|
-
project;
|
|
33
|
-
/**
|
|
34
|
-
* Unique identifier.
|
|
35
|
-
* This ID is deterministic and will be the same for the same test across multiple runs.
|
|
36
|
-
* The ID is based on the project name, module url and test position.
|
|
37
|
-
*/
|
|
38
|
-
id;
|
|
39
|
-
/**
|
|
40
|
-
* Location in the module where the test or suite is defined.
|
|
41
|
-
*/
|
|
42
|
-
location;
|
|
43
|
-
constructor(task, project) {
|
|
44
|
-
this.task = task;
|
|
45
|
-
this.project = project;
|
|
46
|
-
this.id = task.id;
|
|
47
|
-
this.location = task.location;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Creates a new reported task instance and stores it in the project's state for future use.
|
|
51
|
-
*/
|
|
52
|
-
static register(task, project) {
|
|
53
|
-
const state = new this(task, project);
|
|
54
|
-
storeTask(project, task, state);
|
|
55
|
-
return state;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
class TestCase extends ReportedTaskImplementation {
|
|
59
|
-
#fullName;
|
|
60
|
-
type = "test";
|
|
61
|
-
/**
|
|
62
|
-
* Direct reference to the test module where the test or suite is defined.
|
|
63
|
-
*/
|
|
64
|
-
module;
|
|
65
|
-
/**
|
|
66
|
-
* Name of the test.
|
|
67
|
-
*/
|
|
68
|
-
name;
|
|
69
|
-
/**
|
|
70
|
-
* Options that the test was initiated with.
|
|
71
|
-
*/
|
|
72
|
-
options;
|
|
73
|
-
/**
|
|
74
|
-
* Parent suite. If the test was called directly inside the module, the parent will be the module itself.
|
|
75
|
-
*/
|
|
76
|
-
parent;
|
|
77
|
-
constructor(task, project) {
|
|
78
|
-
super(task, project);
|
|
79
|
-
this.name = task.name;
|
|
80
|
-
this.module = getReportedTask(project, task.file);
|
|
81
|
-
const suite = this.task.suite;
|
|
82
|
-
if (suite) {
|
|
83
|
-
this.parent = getReportedTask(project, suite);
|
|
84
|
-
} else {
|
|
85
|
-
this.parent = this.module;
|
|
86
|
-
}
|
|
87
|
-
this.options = buildOptions(task);
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Full name of the test including all parent suites separated with `>`.
|
|
91
|
-
*/
|
|
92
|
-
get fullName() {
|
|
93
|
-
if (this.#fullName === void 0) {
|
|
94
|
-
if (this.parent.type !== "module") {
|
|
95
|
-
this.#fullName = `${this.parent.fullName} > ${this.name}`;
|
|
96
|
-
} else {
|
|
97
|
-
this.#fullName = this.name;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return this.#fullName;
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Test results. Will be `undefined` if test is not finished yet or was just collected.
|
|
104
|
-
*/
|
|
105
|
-
result() {
|
|
106
|
-
const result = this.task.result;
|
|
107
|
-
if (!result || result.state === "run") {
|
|
108
|
-
return void 0;
|
|
109
|
-
}
|
|
110
|
-
const state = result.state === "fail" ? "failed" : result.state === "pass" ? "passed" : "skipped";
|
|
111
|
-
if (state === "skipped") {
|
|
112
|
-
return {
|
|
113
|
-
state,
|
|
114
|
-
note: result.note,
|
|
115
|
-
errors: void 0
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
if (state === "passed") {
|
|
119
|
-
return {
|
|
120
|
-
state,
|
|
121
|
-
errors: result.errors
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
return {
|
|
125
|
-
state,
|
|
126
|
-
errors: result.errors || []
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Checks if the test did not fail the suite.
|
|
131
|
-
* If the test is not finished yet or was skipped, it will return `true`.
|
|
132
|
-
*/
|
|
133
|
-
ok() {
|
|
134
|
-
const result = this.result();
|
|
135
|
-
return !result || result.state !== "failed";
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Custom metadata that was attached to the test during its execution.
|
|
139
|
-
*/
|
|
140
|
-
meta() {
|
|
141
|
-
return this.task.meta;
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* Useful information about the test like duration, memory usage, etc.
|
|
145
|
-
* Diagnostic is only available after the test has finished.
|
|
146
|
-
*/
|
|
147
|
-
diagnostic() {
|
|
148
|
-
const result = this.task.result;
|
|
149
|
-
if (!result || result.state === "run" || !result.startTime) {
|
|
150
|
-
return void 0;
|
|
151
|
-
}
|
|
152
|
-
const duration = result.duration || 0;
|
|
153
|
-
const slow = duration > this.project.globalConfig.slowTestThreshold;
|
|
154
|
-
return {
|
|
155
|
-
slow,
|
|
156
|
-
heap: result.heap,
|
|
157
|
-
duration,
|
|
158
|
-
startTime: result.startTime,
|
|
159
|
-
retryCount: result.retryCount ?? 0,
|
|
160
|
-
repeatCount: result.repeatCount ?? 0,
|
|
161
|
-
flaky: !!result.retryCount && result.state === "pass" && result.retryCount > 0
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
class TestCollection {
|
|
166
|
-
#task;
|
|
167
|
-
#project;
|
|
168
|
-
constructor(task, project) {
|
|
169
|
-
this.#task = task;
|
|
170
|
-
this.#project = project;
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Returns the test or suite at a specific index in the array.
|
|
174
|
-
*/
|
|
175
|
-
at(index) {
|
|
176
|
-
if (index < 0) {
|
|
177
|
-
index = this.size + index;
|
|
178
|
-
}
|
|
179
|
-
return getReportedTask(this.#project, this.#task.tasks[index]);
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* The number of tests and suites in the collection.
|
|
183
|
-
*/
|
|
184
|
-
get size() {
|
|
185
|
-
return this.#task.tasks.length;
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* Returns the collection in array form for easier manipulation.
|
|
189
|
-
*/
|
|
190
|
-
array() {
|
|
191
|
-
return Array.from(this);
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* Filters all tests that are part of this collection and its children.
|
|
195
|
-
*/
|
|
196
|
-
*allTests(state) {
|
|
197
|
-
for (const child of this) {
|
|
198
|
-
if (child.type === "suite") {
|
|
199
|
-
yield* child.children.allTests(state);
|
|
200
|
-
} else if (state) {
|
|
201
|
-
const testState = getTestState(child);
|
|
202
|
-
if (state === testState) {
|
|
203
|
-
yield child;
|
|
204
|
-
}
|
|
205
|
-
} else {
|
|
206
|
-
yield child;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Filters only the tests that are part of this collection.
|
|
212
|
-
*/
|
|
213
|
-
*tests(state) {
|
|
214
|
-
for (const child of this) {
|
|
215
|
-
if (child.type !== "test") {
|
|
216
|
-
continue;
|
|
217
|
-
}
|
|
218
|
-
if (state) {
|
|
219
|
-
const testState = getTestState(child);
|
|
220
|
-
if (state === testState) {
|
|
221
|
-
yield child;
|
|
222
|
-
}
|
|
223
|
-
} else {
|
|
224
|
-
yield child;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* Filters only the suites that are part of this collection.
|
|
230
|
-
*/
|
|
231
|
-
*suites() {
|
|
232
|
-
for (const child of this) {
|
|
233
|
-
if (child.type === "suite") {
|
|
234
|
-
yield child;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
/**
|
|
239
|
-
* Filters all suites that are part of this collection and its children.
|
|
240
|
-
*/
|
|
241
|
-
*allSuites() {
|
|
242
|
-
for (const child of this) {
|
|
243
|
-
if (child.type === "suite") {
|
|
244
|
-
yield child;
|
|
245
|
-
yield* child.children.allSuites();
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
*[Symbol.iterator]() {
|
|
250
|
-
for (const task of this.#task.tasks) {
|
|
251
|
-
yield getReportedTask(this.#project, task);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
class SuiteImplementation extends ReportedTaskImplementation {
|
|
256
|
-
/**
|
|
257
|
-
* Collection of suites and tests that are part of this suite.
|
|
258
|
-
*/
|
|
259
|
-
children;
|
|
260
|
-
constructor(task, project) {
|
|
261
|
-
super(task, project);
|
|
262
|
-
this.children = new TestCollection(task, project);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
class TestSuite extends SuiteImplementation {
|
|
266
|
-
#fullName;
|
|
267
|
-
type = "suite";
|
|
268
|
-
/**
|
|
269
|
-
* Name of the test or the suite.
|
|
270
|
-
*/
|
|
271
|
-
name;
|
|
272
|
-
/**
|
|
273
|
-
* Direct reference to the test module where the test or suite is defined.
|
|
274
|
-
*/
|
|
275
|
-
module;
|
|
276
|
-
/**
|
|
277
|
-
* Parent suite. If suite was called directly inside the module, the parent will be the module itself.
|
|
278
|
-
*/
|
|
279
|
-
parent;
|
|
280
|
-
/**
|
|
281
|
-
* Options that suite was initiated with.
|
|
282
|
-
*/
|
|
283
|
-
options;
|
|
284
|
-
constructor(task, project) {
|
|
285
|
-
super(task, project);
|
|
286
|
-
this.name = task.name;
|
|
287
|
-
this.module = getReportedTask(project, task.file);
|
|
288
|
-
const suite = this.task.suite;
|
|
289
|
-
if (suite) {
|
|
290
|
-
this.parent = getReportedTask(project, suite);
|
|
291
|
-
} else {
|
|
292
|
-
this.parent = this.module;
|
|
293
|
-
}
|
|
294
|
-
this.options = buildOptions(task);
|
|
295
|
-
}
|
|
296
|
-
/**
|
|
297
|
-
* Full name of the suite including all parent suites separated with `>`.
|
|
298
|
-
*/
|
|
299
|
-
get fullName() {
|
|
300
|
-
if (this.#fullName === void 0) {
|
|
301
|
-
if (this.parent.type !== "module") {
|
|
302
|
-
this.#fullName = `${this.parent.fullName} > ${this.name}`;
|
|
303
|
-
} else {
|
|
304
|
-
this.#fullName = this.name;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
return this.#fullName;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
class TestModule extends SuiteImplementation {
|
|
311
|
-
type = "module";
|
|
312
|
-
/**
|
|
313
|
-
* This is usually an absolute UNIX file path.
|
|
314
|
-
* It can be a virtual id if the file is not on the disk.
|
|
315
|
-
* This value corresponds to Vite's `ModuleGraph` id.
|
|
316
|
-
*/
|
|
317
|
-
moduleId;
|
|
318
|
-
constructor(task, project) {
|
|
319
|
-
super(task, project);
|
|
320
|
-
this.moduleId = task.filepath;
|
|
321
|
-
}
|
|
322
|
-
/**
|
|
323
|
-
* Useful information about the module like duration, memory usage, etc.
|
|
324
|
-
* If the module was not executed yet, all diagnostic values will return `0`.
|
|
325
|
-
*/
|
|
326
|
-
diagnostic() {
|
|
327
|
-
const setupDuration = this.task.setupDuration || 0;
|
|
328
|
-
const collectDuration = this.task.collectDuration || 0;
|
|
329
|
-
const prepareDuration = this.task.prepareDuration || 0;
|
|
330
|
-
const environmentSetupDuration = this.task.environmentLoad || 0;
|
|
331
|
-
const duration = this.task.result?.duration || 0;
|
|
332
|
-
return {
|
|
333
|
-
environmentSetupDuration,
|
|
334
|
-
prepareDuration,
|
|
335
|
-
collectDuration,
|
|
336
|
-
setupDuration,
|
|
337
|
-
duration
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
function buildOptions(task) {
|
|
342
|
-
return {
|
|
343
|
-
each: task.each,
|
|
344
|
-
concurrent: task.concurrent,
|
|
345
|
-
shuffle: task.shuffle,
|
|
346
|
-
retry: task.retry,
|
|
347
|
-
repeats: task.repeats,
|
|
348
|
-
mode: task.mode
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
|
-
function getTestState(test) {
|
|
352
|
-
const result = test.result();
|
|
353
|
-
return result ? result.state : "running";
|
|
354
|
-
}
|
|
355
|
-
function storeTask(project, runnerTask, reportedTask) {
|
|
356
|
-
project.vitest.state.reportedTasksMap.set(runnerTask, reportedTask);
|
|
357
|
-
}
|
|
358
|
-
function getReportedTask(project, runnerTask) {
|
|
359
|
-
const reportedTask = project.vitest.state.getReportedEntity(runnerTask);
|
|
360
|
-
if (!reportedTask) {
|
|
361
|
-
throw new Error(
|
|
362
|
-
`Task instance was not found for ${runnerTask.type} "${runnerTask.name}"`
|
|
363
|
-
);
|
|
364
|
-
}
|
|
365
|
-
return reportedTask;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
23
|
/// <reference types="../types/index.d.ts" />
|
|
369
24
|
|
|
370
25
|
// (c) 2020-present Andrea Giammarchi
|
|
@@ -3474,7 +3129,7 @@ class BaseReporter {
|
|
|
3474
3129
|
this.log(BADGE_PADDING + c.dim(" Project name: ") + c.blue(toArray(this.ctx.configOverride.project).join(", ")));
|
|
3475
3130
|
}
|
|
3476
3131
|
if (this.ctx.filenamePattern) {
|
|
3477
|
-
this.log(BADGE_PADDING + c.dim(" Filename pattern: ") + c.blue(this.ctx.filenamePattern));
|
|
3132
|
+
this.log(BADGE_PADDING + c.dim(" Filename pattern: ") + c.blue(this.ctx.filenamePattern.join(", ")));
|
|
3478
3133
|
}
|
|
3479
3134
|
if (this.ctx.configOverride.testNamePattern) {
|
|
3480
3135
|
this.log(BADGE_PADDING + c.dim(" Test name pattern: ") + c.blue(String(this.ctx.configOverride.testNamePattern)));
|
|
@@ -3505,7 +3160,7 @@ class BaseReporter {
|
|
|
3505
3160
|
if (log.browser) {
|
|
3506
3161
|
write("\n");
|
|
3507
3162
|
}
|
|
3508
|
-
const project =
|
|
3163
|
+
const project = task ? this.ctx.getProjectByName(task.file.projectName || "") : this.ctx.getRootProject();
|
|
3509
3164
|
const stack = log.browser ? project.browser?.parseStacktrace(log.origin) || [] : parseStacktrace(log.origin);
|
|
3510
3165
|
const highlight = task && stack.find((i) => i.file === task.file.filepath);
|
|
3511
3166
|
for (const frame of stack) {
|
|
@@ -3687,7 +3342,7 @@ ${errorBanner(`Failed Tests ${failedTests.length}`)}
|
|
|
3687
3342
|
}
|
|
3688
3343
|
const screenshotPaths = tasks2.map((t) => t.meta?.failScreenshotPath).filter((screenshot) => screenshot != null);
|
|
3689
3344
|
this.ctx.logger.printError(error, {
|
|
3690
|
-
project: this.ctx.
|
|
3345
|
+
project: this.ctx.getProjectByName(tasks2[0].file.projectName || ""),
|
|
3691
3346
|
verbose: this.verbose,
|
|
3692
3347
|
screenshotPaths,
|
|
3693
3348
|
task: tasks2[0]
|
|
@@ -4383,14 +4038,14 @@ class GithubActionsReporter {
|
|
|
4383
4038
|
const projectErrors = new Array();
|
|
4384
4039
|
for (const error of errors) {
|
|
4385
4040
|
projectErrors.push({
|
|
4386
|
-
project: this.ctx.
|
|
4041
|
+
project: this.ctx.getRootProject(),
|
|
4387
4042
|
title: "Unhandled error",
|
|
4388
4043
|
error
|
|
4389
4044
|
});
|
|
4390
4045
|
}
|
|
4391
4046
|
for (const file of files) {
|
|
4392
4047
|
const tasks = getTasks(file);
|
|
4393
|
-
const project = this.ctx.
|
|
4048
|
+
const project = this.ctx.getProjectByName(file.projectName || "");
|
|
4394
4049
|
for (const task of tasks) {
|
|
4395
4050
|
if (task.result?.state !== "fail") {
|
|
4396
4051
|
continue;
|
|
@@ -4581,293 +4236,667 @@ class JsonReporter {
|
|
|
4581
4236
|
if (!existsSync(outputDirectory)) {
|
|
4582
4237
|
await promises.mkdir(outputDirectory, { recursive: true });
|
|
4583
4238
|
}
|
|
4584
|
-
await promises.writeFile(reportFile, report, "utf-8");
|
|
4585
|
-
this.ctx.logger.log(`JSON report written to ${reportFile}`);
|
|
4239
|
+
await promises.writeFile(reportFile, report, "utf-8");
|
|
4240
|
+
this.ctx.logger.log(`JSON report written to ${reportFile}`);
|
|
4241
|
+
} else {
|
|
4242
|
+
this.ctx.logger.log(report);
|
|
4243
|
+
}
|
|
4244
|
+
}
|
|
4245
|
+
}
|
|
4246
|
+
|
|
4247
|
+
class IndentedLogger {
|
|
4248
|
+
constructor(baseLog) {
|
|
4249
|
+
this.baseLog = baseLog;
|
|
4250
|
+
}
|
|
4251
|
+
currentIndent = "";
|
|
4252
|
+
indent() {
|
|
4253
|
+
this.currentIndent += " ";
|
|
4254
|
+
}
|
|
4255
|
+
unindent() {
|
|
4256
|
+
this.currentIndent = this.currentIndent.substring(
|
|
4257
|
+
0,
|
|
4258
|
+
this.currentIndent.length - 4
|
|
4259
|
+
);
|
|
4260
|
+
}
|
|
4261
|
+
log(text) {
|
|
4262
|
+
return this.baseLog(this.currentIndent + text);
|
|
4263
|
+
}
|
|
4264
|
+
}
|
|
4265
|
+
|
|
4266
|
+
function flattenTasks$1(task, baseName = "") {
|
|
4267
|
+
const base = baseName ? `${baseName} > ` : "";
|
|
4268
|
+
if (task.type === "suite") {
|
|
4269
|
+
return task.tasks.flatMap(
|
|
4270
|
+
(child) => flattenTasks$1(child, `${base}${task.name}`)
|
|
4271
|
+
);
|
|
4272
|
+
} else {
|
|
4273
|
+
return [
|
|
4274
|
+
{
|
|
4275
|
+
...task,
|
|
4276
|
+
name: `${base}${task.name}`
|
|
4277
|
+
}
|
|
4278
|
+
];
|
|
4279
|
+
}
|
|
4280
|
+
}
|
|
4281
|
+
function removeInvalidXMLCharacters(value, removeDiscouragedChars) {
|
|
4282
|
+
let regex = /([\0-\x08\v\f\x0E-\x1F\uFFFD\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])/g;
|
|
4283
|
+
value = String(value || "").replace(regex, "");
|
|
4284
|
+
{
|
|
4285
|
+
regex = new RegExp(
|
|
4286
|
+
/* eslint-disable regexp/prefer-character-class, regexp/no-obscure-range, regexp/no-useless-non-capturing-group */
|
|
4287
|
+
"([\\x7F-\\x84]|[\\x86-\\x9F]|[\\uFDD0-\\uFDEF]|\\uD83F[\\uDFFE\\uDFFF]|(?:\\uD87F[\\uDFFE\\uDFFF])|\\uD8BF[\\uDFFE\\uDFFF]|\\uD8FF[\\uDFFE\\uDFFF]|(?:\\uD93F[\\uDFFE\\uDFFF])|\\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]))",
|
|
4288
|
+
"g"
|
|
4289
|
+
/* eslint-enable */
|
|
4290
|
+
);
|
|
4291
|
+
value = value.replace(regex, "");
|
|
4292
|
+
}
|
|
4293
|
+
return value;
|
|
4294
|
+
}
|
|
4295
|
+
function escapeXML(value) {
|
|
4296
|
+
return removeInvalidXMLCharacters(
|
|
4297
|
+
String(value).replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">"));
|
|
4298
|
+
}
|
|
4299
|
+
function executionTime(durationMS) {
|
|
4300
|
+
return (durationMS / 1e3).toLocaleString("en-US", {
|
|
4301
|
+
useGrouping: false,
|
|
4302
|
+
maximumFractionDigits: 10
|
|
4303
|
+
});
|
|
4304
|
+
}
|
|
4305
|
+
function getDuration(task) {
|
|
4306
|
+
const duration = task.result?.duration ?? 0;
|
|
4307
|
+
return executionTime(duration);
|
|
4308
|
+
}
|
|
4309
|
+
class JUnitReporter {
|
|
4310
|
+
ctx;
|
|
4311
|
+
reportFile;
|
|
4312
|
+
baseLog;
|
|
4313
|
+
logger;
|
|
4314
|
+
_timeStart = /* @__PURE__ */ new Date();
|
|
4315
|
+
fileFd;
|
|
4316
|
+
options;
|
|
4317
|
+
constructor(options) {
|
|
4318
|
+
this.options = { ...options };
|
|
4319
|
+
this.options.includeConsoleOutput ??= true;
|
|
4320
|
+
}
|
|
4321
|
+
async onInit(ctx) {
|
|
4322
|
+
this.ctx = ctx;
|
|
4323
|
+
const outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "junit");
|
|
4324
|
+
if (outputFile) {
|
|
4325
|
+
this.reportFile = resolve(this.ctx.config.root, outputFile);
|
|
4326
|
+
const outputDirectory = dirname(this.reportFile);
|
|
4327
|
+
if (!existsSync(outputDirectory)) {
|
|
4328
|
+
await promises.mkdir(outputDirectory, { recursive: true });
|
|
4329
|
+
}
|
|
4330
|
+
const fileFd = await promises.open(this.reportFile, "w+");
|
|
4331
|
+
this.fileFd = fileFd;
|
|
4332
|
+
this.baseLog = async (text) => {
|
|
4333
|
+
if (!this.fileFd) {
|
|
4334
|
+
this.fileFd = await promises.open(this.reportFile, "w+");
|
|
4335
|
+
}
|
|
4336
|
+
await promises.writeFile(this.fileFd, `${text}
|
|
4337
|
+
`);
|
|
4338
|
+
};
|
|
4586
4339
|
} else {
|
|
4587
|
-
this.ctx.logger.log(
|
|
4340
|
+
this.baseLog = async (text) => this.ctx.logger.log(text);
|
|
4588
4341
|
}
|
|
4342
|
+
this._timeStart = /* @__PURE__ */ new Date();
|
|
4343
|
+
this.logger = new IndentedLogger(this.baseLog);
|
|
4589
4344
|
}
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
this.currentIndent = this.currentIndent.substring(
|
|
4602
|
-
0,
|
|
4603
|
-
this.currentIndent.length - 4
|
|
4345
|
+
async writeElement(name, attrs, children) {
|
|
4346
|
+
const pairs = [];
|
|
4347
|
+
for (const key in attrs) {
|
|
4348
|
+
const attr = attrs[key];
|
|
4349
|
+
if (attr === void 0) {
|
|
4350
|
+
continue;
|
|
4351
|
+
}
|
|
4352
|
+
pairs.push(`${key}="${escapeXML(attr)}"`);
|
|
4353
|
+
}
|
|
4354
|
+
await this.logger.log(
|
|
4355
|
+
`<${name}${pairs.length ? ` ${pairs.join(" ")}` : ""}>`
|
|
4604
4356
|
);
|
|
4357
|
+
this.logger.indent();
|
|
4358
|
+
await children.call(this);
|
|
4359
|
+
this.logger.unindent();
|
|
4360
|
+
await this.logger.log(`</${name}>`);
|
|
4605
4361
|
}
|
|
4606
|
-
|
|
4607
|
-
|
|
4362
|
+
async writeLogs(task, type) {
|
|
4363
|
+
if (task.logs == null || task.logs.length === 0) {
|
|
4364
|
+
return;
|
|
4365
|
+
}
|
|
4366
|
+
const logType = type === "err" ? "stderr" : "stdout";
|
|
4367
|
+
const logs = task.logs.filter((log) => log.type === logType);
|
|
4368
|
+
if (logs.length === 0) {
|
|
4369
|
+
return;
|
|
4370
|
+
}
|
|
4371
|
+
await this.writeElement(`system-${type}`, {}, async () => {
|
|
4372
|
+
for (const log of logs) {
|
|
4373
|
+
await this.baseLog(escapeXML(log.content));
|
|
4374
|
+
}
|
|
4375
|
+
});
|
|
4608
4376
|
}
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4377
|
+
async writeTasks(tasks, filename) {
|
|
4378
|
+
for (const task of tasks) {
|
|
4379
|
+
let classname = filename;
|
|
4380
|
+
const templateVars = {
|
|
4381
|
+
filename: task.file.name,
|
|
4382
|
+
filepath: task.file.filepath
|
|
4383
|
+
};
|
|
4384
|
+
if (typeof this.options.classnameTemplate === "function") {
|
|
4385
|
+
classname = this.options.classnameTemplate(templateVars);
|
|
4386
|
+
} else if (typeof this.options.classnameTemplate === "string") {
|
|
4387
|
+
classname = this.options.classnameTemplate.replace(/\{filename\}/g, templateVars.filename).replace(/\{filepath\}/g, templateVars.filepath);
|
|
4388
|
+
} else if (typeof this.options.classname === "string") {
|
|
4389
|
+
classname = this.options.classname;
|
|
4390
|
+
}
|
|
4391
|
+
await this.writeElement(
|
|
4392
|
+
"testcase",
|
|
4393
|
+
{
|
|
4394
|
+
classname,
|
|
4395
|
+
file: this.options.addFileAttribute ? filename : void 0,
|
|
4396
|
+
name: task.name,
|
|
4397
|
+
time: getDuration(task)
|
|
4398
|
+
},
|
|
4399
|
+
async () => {
|
|
4400
|
+
if (this.options.includeConsoleOutput) {
|
|
4401
|
+
await this.writeLogs(task, "out");
|
|
4402
|
+
await this.writeLogs(task, "err");
|
|
4403
|
+
}
|
|
4404
|
+
if (task.mode === "skip" || task.mode === "todo") {
|
|
4405
|
+
await this.logger.log("<skipped/>");
|
|
4406
|
+
}
|
|
4407
|
+
if (task.result?.state === "fail") {
|
|
4408
|
+
const errors = task.result.errors || [];
|
|
4409
|
+
for (const error of errors) {
|
|
4410
|
+
await this.writeElement(
|
|
4411
|
+
"failure",
|
|
4412
|
+
{
|
|
4413
|
+
message: error?.message,
|
|
4414
|
+
type: error?.name ?? error?.nameStr
|
|
4415
|
+
},
|
|
4416
|
+
async () => {
|
|
4417
|
+
if (!error) {
|
|
4418
|
+
return;
|
|
4419
|
+
}
|
|
4420
|
+
const result = capturePrintError(
|
|
4421
|
+
error,
|
|
4422
|
+
this.ctx,
|
|
4423
|
+
{ project: this.ctx.getProjectByName(task.file.projectName || ""), task }
|
|
4424
|
+
);
|
|
4425
|
+
await this.baseLog(
|
|
4426
|
+
escapeXML(stripVTControlCharacters(result.output.trim()))
|
|
4427
|
+
);
|
|
4428
|
+
}
|
|
4429
|
+
);
|
|
4430
|
+
}
|
|
4431
|
+
}
|
|
4432
|
+
}
|
|
4433
|
+
);
|
|
4434
|
+
}
|
|
4435
|
+
}
|
|
4436
|
+
async onFinished(files = this.ctx.state.getFiles()) {
|
|
4437
|
+
await this.logger.log('<?xml version="1.0" encoding="UTF-8" ?>');
|
|
4438
|
+
const transformed = files.map((file) => {
|
|
4439
|
+
const tasks = file.tasks.flatMap((task) => flattenTasks$1(task));
|
|
4440
|
+
const stats2 = tasks.reduce(
|
|
4441
|
+
(stats3, task) => {
|
|
4442
|
+
return {
|
|
4443
|
+
passed: stats3.passed + Number(task.result?.state === "pass"),
|
|
4444
|
+
failures: stats3.failures + Number(task.result?.state === "fail"),
|
|
4445
|
+
skipped: stats3.skipped + Number(task.mode === "skip" || task.mode === "todo")
|
|
4446
|
+
};
|
|
4447
|
+
},
|
|
4448
|
+
{
|
|
4449
|
+
passed: 0,
|
|
4450
|
+
failures: 0,
|
|
4451
|
+
skipped: 0
|
|
4452
|
+
}
|
|
4453
|
+
);
|
|
4454
|
+
const suites = getSuites(file);
|
|
4455
|
+
for (const suite of suites) {
|
|
4456
|
+
if (suite.result?.errors) {
|
|
4457
|
+
tasks.push(suite);
|
|
4458
|
+
stats2.failures += 1;
|
|
4459
|
+
}
|
|
4460
|
+
}
|
|
4461
|
+
if (tasks.length === 0 && file.result?.state === "fail") {
|
|
4462
|
+
stats2.failures = 1;
|
|
4463
|
+
tasks.push({
|
|
4464
|
+
id: file.id,
|
|
4465
|
+
type: "test",
|
|
4466
|
+
name: file.name,
|
|
4467
|
+
mode: "run",
|
|
4468
|
+
result: file.result,
|
|
4469
|
+
meta: {},
|
|
4470
|
+
// NOTE: not used in JUnitReporter
|
|
4471
|
+
context: null,
|
|
4472
|
+
suite: null,
|
|
4473
|
+
file: null
|
|
4474
|
+
});
|
|
4475
|
+
}
|
|
4476
|
+
return {
|
|
4477
|
+
...file,
|
|
4478
|
+
tasks,
|
|
4479
|
+
stats: stats2
|
|
4480
|
+
};
|
|
4481
|
+
});
|
|
4482
|
+
const stats = transformed.reduce(
|
|
4483
|
+
(stats2, file) => {
|
|
4484
|
+
stats2.tests += file.tasks.length;
|
|
4485
|
+
stats2.failures += file.stats.failures;
|
|
4486
|
+
stats2.time += file.result?.duration || 0;
|
|
4487
|
+
return stats2;
|
|
4488
|
+
},
|
|
4619
4489
|
{
|
|
4620
|
-
|
|
4621
|
-
|
|
4490
|
+
name: this.options.suiteName || "vitest tests",
|
|
4491
|
+
tests: 0,
|
|
4492
|
+
failures: 0,
|
|
4493
|
+
errors: 0,
|
|
4494
|
+
// we cannot detect those
|
|
4495
|
+
time: 0
|
|
4622
4496
|
}
|
|
4623
|
-
|
|
4497
|
+
);
|
|
4498
|
+
await this.writeElement("testsuites", { ...stats, time: executionTime(stats.time) }, async () => {
|
|
4499
|
+
for (const file of transformed) {
|
|
4500
|
+
const filename = relative(this.ctx.config.root, file.filepath);
|
|
4501
|
+
await this.writeElement(
|
|
4502
|
+
"testsuite",
|
|
4503
|
+
{
|
|
4504
|
+
name: filename,
|
|
4505
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4506
|
+
hostname: hostname(),
|
|
4507
|
+
tests: file.tasks.length,
|
|
4508
|
+
failures: file.stats.failures,
|
|
4509
|
+
errors: 0,
|
|
4510
|
+
// An errored test is one that had an unanticipated problem. We cannot detect those.
|
|
4511
|
+
skipped: file.stats.skipped,
|
|
4512
|
+
time: getDuration(file)
|
|
4513
|
+
},
|
|
4514
|
+
async () => {
|
|
4515
|
+
await this.writeTasks(file.tasks, filename);
|
|
4516
|
+
}
|
|
4517
|
+
);
|
|
4518
|
+
}
|
|
4519
|
+
});
|
|
4520
|
+
if (this.reportFile) {
|
|
4521
|
+
this.ctx.logger.log(`JUNIT report written to ${this.reportFile}`);
|
|
4522
|
+
}
|
|
4523
|
+
await this.fileFd?.close();
|
|
4524
|
+
this.fileFd = void 0;
|
|
4624
4525
|
}
|
|
4625
4526
|
}
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4527
|
+
|
|
4528
|
+
class ReportedTaskImplementation {
|
|
4529
|
+
/**
|
|
4530
|
+
* Task instance.
|
|
4531
|
+
* @internal
|
|
4532
|
+
*/
|
|
4533
|
+
task;
|
|
4534
|
+
/**
|
|
4535
|
+
* The project assosiacted with the test or suite.
|
|
4536
|
+
*/
|
|
4537
|
+
project;
|
|
4538
|
+
/**
|
|
4539
|
+
* Unique identifier.
|
|
4540
|
+
* This ID is deterministic and will be the same for the same test across multiple runs.
|
|
4541
|
+
* The ID is based on the project name, module url and test order.
|
|
4542
|
+
*/
|
|
4543
|
+
id;
|
|
4544
|
+
/**
|
|
4545
|
+
* Location in the module where the test or suite is defined.
|
|
4546
|
+
*/
|
|
4547
|
+
location;
|
|
4548
|
+
/** @internal */
|
|
4549
|
+
constructor(task, project) {
|
|
4550
|
+
this.task = task;
|
|
4551
|
+
this.project = project;
|
|
4552
|
+
this.id = task.id;
|
|
4553
|
+
this.location = task.location;
|
|
4554
|
+
}
|
|
4555
|
+
/**
|
|
4556
|
+
* Checks if the test did not fail the suite.
|
|
4557
|
+
* If the test is not finished yet or was skipped, it will return `true`.
|
|
4558
|
+
*/
|
|
4559
|
+
ok() {
|
|
4560
|
+
const result = this.task.result;
|
|
4561
|
+
return !result || result.state !== "fail";
|
|
4562
|
+
}
|
|
4563
|
+
/**
|
|
4564
|
+
* Creates a new reported task instance and stores it in the project's state for future use.
|
|
4565
|
+
* @internal
|
|
4566
|
+
*/
|
|
4567
|
+
static register(task, project) {
|
|
4568
|
+
const state = new this(task, project);
|
|
4569
|
+
storeTask(project, task, state);
|
|
4570
|
+
return state;
|
|
4637
4571
|
}
|
|
4638
|
-
return value;
|
|
4639
|
-
}
|
|
4640
|
-
function escapeXML(value) {
|
|
4641
|
-
return removeInvalidXMLCharacters(
|
|
4642
|
-
String(value).replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">"));
|
|
4643
|
-
}
|
|
4644
|
-
function executionTime(durationMS) {
|
|
4645
|
-
return (durationMS / 1e3).toLocaleString("en-US", {
|
|
4646
|
-
useGrouping: false,
|
|
4647
|
-
maximumFractionDigits: 10
|
|
4648
|
-
});
|
|
4649
|
-
}
|
|
4650
|
-
function getDuration(task) {
|
|
4651
|
-
const duration = task.result?.duration ?? 0;
|
|
4652
|
-
return executionTime(duration);
|
|
4653
4572
|
}
|
|
4654
|
-
class
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4573
|
+
class TestCase extends ReportedTaskImplementation {
|
|
4574
|
+
#fullName;
|
|
4575
|
+
type = "test";
|
|
4576
|
+
/**
|
|
4577
|
+
* Direct reference to the test module where the test or suite is defined.
|
|
4578
|
+
*/
|
|
4579
|
+
module;
|
|
4580
|
+
/**
|
|
4581
|
+
* Name of the test.
|
|
4582
|
+
*/
|
|
4583
|
+
name;
|
|
4584
|
+
/**
|
|
4585
|
+
* Options that the test was initiated with.
|
|
4586
|
+
*/
|
|
4661
4587
|
options;
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
}
|
|
4675
|
-
const fileFd = await promises.open(this.reportFile, "w+");
|
|
4676
|
-
this.fileFd = fileFd;
|
|
4677
|
-
this.baseLog = async (text) => {
|
|
4678
|
-
if (!this.fileFd) {
|
|
4679
|
-
this.fileFd = await promises.open(this.reportFile, "w+");
|
|
4680
|
-
}
|
|
4681
|
-
await promises.writeFile(this.fileFd, `${text}
|
|
4682
|
-
`);
|
|
4683
|
-
};
|
|
4588
|
+
/**
|
|
4589
|
+
* Parent suite. If the test was called directly inside the module, the parent will be the module itself.
|
|
4590
|
+
*/
|
|
4591
|
+
parent;
|
|
4592
|
+
/** @internal */
|
|
4593
|
+
constructor(task, project) {
|
|
4594
|
+
super(task, project);
|
|
4595
|
+
this.name = task.name;
|
|
4596
|
+
this.module = getReportedTask(project, task.file);
|
|
4597
|
+
const suite = this.task.suite;
|
|
4598
|
+
if (suite) {
|
|
4599
|
+
this.parent = getReportedTask(project, suite);
|
|
4684
4600
|
} else {
|
|
4685
|
-
this.
|
|
4601
|
+
this.parent = this.module;
|
|
4686
4602
|
}
|
|
4687
|
-
this.
|
|
4688
|
-
this.logger = new IndentedLogger(this.baseLog);
|
|
4603
|
+
this.options = buildOptions(task);
|
|
4689
4604
|
}
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4605
|
+
/**
|
|
4606
|
+
* Full name of the test including all parent suites separated with `>`.
|
|
4607
|
+
*/
|
|
4608
|
+
get fullName() {
|
|
4609
|
+
if (this.#fullName === void 0) {
|
|
4610
|
+
if (this.parent.type !== "module") {
|
|
4611
|
+
this.#fullName = `${this.parent.fullName} > ${this.name}`;
|
|
4612
|
+
} else {
|
|
4613
|
+
this.#fullName = this.name;
|
|
4696
4614
|
}
|
|
4697
|
-
pairs.push(`${key}="${escapeXML(attr)}"`);
|
|
4698
4615
|
}
|
|
4699
|
-
|
|
4700
|
-
`<${name}${pairs.length ? ` ${pairs.join(" ")}` : ""}>`
|
|
4701
|
-
);
|
|
4702
|
-
this.logger.indent();
|
|
4703
|
-
await children.call(this);
|
|
4704
|
-
this.logger.unindent();
|
|
4705
|
-
await this.logger.log(`</${name}>`);
|
|
4616
|
+
return this.#fullName;
|
|
4706
4617
|
}
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4618
|
+
/**
|
|
4619
|
+
* Test results. Will be `undefined` if test is skipped, not finished yet or was just collected.
|
|
4620
|
+
*/
|
|
4621
|
+
result() {
|
|
4622
|
+
const result = this.task.result;
|
|
4623
|
+
if (!result || result.state === "run") {
|
|
4624
|
+
return void 0;
|
|
4710
4625
|
}
|
|
4711
|
-
const
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4626
|
+
const state = result.state === "fail" ? "failed" : result.state === "pass" ? "passed" : "skipped";
|
|
4627
|
+
if (state === "skipped") {
|
|
4628
|
+
return {
|
|
4629
|
+
state,
|
|
4630
|
+
note: result.note,
|
|
4631
|
+
errors: void 0
|
|
4632
|
+
};
|
|
4715
4633
|
}
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
});
|
|
4721
|
-
}
|
|
4722
|
-
async writeTasks(tasks, filename) {
|
|
4723
|
-
for (const task of tasks) {
|
|
4724
|
-
let classname = filename;
|
|
4725
|
-
const templateVars = {
|
|
4726
|
-
filename: task.file.name,
|
|
4727
|
-
filepath: task.file.filepath
|
|
4634
|
+
if (state === "passed") {
|
|
4635
|
+
return {
|
|
4636
|
+
state,
|
|
4637
|
+
errors: result.errors
|
|
4728
4638
|
};
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4639
|
+
}
|
|
4640
|
+
return {
|
|
4641
|
+
state,
|
|
4642
|
+
errors: result.errors || []
|
|
4643
|
+
};
|
|
4644
|
+
}
|
|
4645
|
+
/**
|
|
4646
|
+
* Checks if the test was skipped during collection or dynamically with `ctx.skip()`.
|
|
4647
|
+
*/
|
|
4648
|
+
skipped() {
|
|
4649
|
+
const mode = this.task.result?.state || this.task.mode;
|
|
4650
|
+
return mode === "skip" || mode === "todo";
|
|
4651
|
+
}
|
|
4652
|
+
/**
|
|
4653
|
+
* Custom metadata that was attached to the test during its execution.
|
|
4654
|
+
*/
|
|
4655
|
+
meta() {
|
|
4656
|
+
return this.task.meta;
|
|
4657
|
+
}
|
|
4658
|
+
/**
|
|
4659
|
+
* Useful information about the test like duration, memory usage, etc.
|
|
4660
|
+
* Diagnostic is only available after the test has finished.
|
|
4661
|
+
*/
|
|
4662
|
+
diagnostic() {
|
|
4663
|
+
const result = this.task.result;
|
|
4664
|
+
if (!result || result.state === "run" || !result.startTime) {
|
|
4665
|
+
return void 0;
|
|
4666
|
+
}
|
|
4667
|
+
const duration = result.duration || 0;
|
|
4668
|
+
const slow = duration > this.project.globalConfig.slowTestThreshold;
|
|
4669
|
+
return {
|
|
4670
|
+
slow,
|
|
4671
|
+
heap: result.heap,
|
|
4672
|
+
duration,
|
|
4673
|
+
startTime: result.startTime,
|
|
4674
|
+
retryCount: result.retryCount ?? 0,
|
|
4675
|
+
repeatCount: result.repeatCount ?? 0,
|
|
4676
|
+
flaky: !!result.retryCount && result.state === "pass" && result.retryCount > 0
|
|
4677
|
+
};
|
|
4678
|
+
}
|
|
4679
|
+
}
|
|
4680
|
+
class TestCollection {
|
|
4681
|
+
#task;
|
|
4682
|
+
#project;
|
|
4683
|
+
constructor(task, project) {
|
|
4684
|
+
this.#task = task;
|
|
4685
|
+
this.#project = project;
|
|
4686
|
+
}
|
|
4687
|
+
/**
|
|
4688
|
+
* Returns the test or suite at a specific index.
|
|
4689
|
+
*/
|
|
4690
|
+
at(index) {
|
|
4691
|
+
if (index < 0) {
|
|
4692
|
+
index = this.size + index;
|
|
4693
|
+
}
|
|
4694
|
+
return getReportedTask(this.#project, this.#task.tasks[index]);
|
|
4695
|
+
}
|
|
4696
|
+
/**
|
|
4697
|
+
* The number of tests and suites in the collection.
|
|
4698
|
+
*/
|
|
4699
|
+
get size() {
|
|
4700
|
+
return this.#task.tasks.length;
|
|
4701
|
+
}
|
|
4702
|
+
/**
|
|
4703
|
+
* Returns the collection in array form for easier manipulation.
|
|
4704
|
+
*/
|
|
4705
|
+
array() {
|
|
4706
|
+
return Array.from(this);
|
|
4707
|
+
}
|
|
4708
|
+
/**
|
|
4709
|
+
* Filters all tests that are part of this collection and its children.
|
|
4710
|
+
*/
|
|
4711
|
+
*allTests(state) {
|
|
4712
|
+
for (const child of this) {
|
|
4713
|
+
if (child.type === "suite") {
|
|
4714
|
+
yield* child.children.allTests(state);
|
|
4715
|
+
} else if (state) {
|
|
4716
|
+
const testState = getTestState(child);
|
|
4717
|
+
if (state === testState) {
|
|
4718
|
+
yield child;
|
|
4777
4719
|
}
|
|
4778
|
-
|
|
4720
|
+
} else {
|
|
4721
|
+
yield child;
|
|
4722
|
+
}
|
|
4779
4723
|
}
|
|
4780
4724
|
}
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
{
|
|
4794
|
-
passed: 0,
|
|
4795
|
-
failures: 0,
|
|
4796
|
-
skipped: 0
|
|
4797
|
-
}
|
|
4798
|
-
);
|
|
4799
|
-
const suites = getSuites(file);
|
|
4800
|
-
for (const suite of suites) {
|
|
4801
|
-
if (suite.result?.errors) {
|
|
4802
|
-
tasks.push(suite);
|
|
4803
|
-
stats2.failures += 1;
|
|
4725
|
+
/**
|
|
4726
|
+
* Filters only the tests that are part of this collection.
|
|
4727
|
+
*/
|
|
4728
|
+
*tests(state) {
|
|
4729
|
+
for (const child of this) {
|
|
4730
|
+
if (child.type !== "test") {
|
|
4731
|
+
continue;
|
|
4732
|
+
}
|
|
4733
|
+
if (state) {
|
|
4734
|
+
const testState = getTestState(child);
|
|
4735
|
+
if (state === testState) {
|
|
4736
|
+
yield child;
|
|
4804
4737
|
}
|
|
4738
|
+
} else {
|
|
4739
|
+
yield child;
|
|
4805
4740
|
}
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
// NOTE: not used in JUnitReporter
|
|
4816
|
-
context: null,
|
|
4817
|
-
suite: null,
|
|
4818
|
-
file: null
|
|
4819
|
-
});
|
|
4741
|
+
}
|
|
4742
|
+
}
|
|
4743
|
+
/**
|
|
4744
|
+
* Filters only the suites that are part of this collection.
|
|
4745
|
+
*/
|
|
4746
|
+
*suites() {
|
|
4747
|
+
for (const child of this) {
|
|
4748
|
+
if (child.type === "suite") {
|
|
4749
|
+
yield child;
|
|
4820
4750
|
}
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
const
|
|
4828
|
-
(
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
stats2.time += file.result?.duration || 0;
|
|
4832
|
-
return stats2;
|
|
4833
|
-
},
|
|
4834
|
-
{
|
|
4835
|
-
name: this.options.suiteName || "vitest tests",
|
|
4836
|
-
tests: 0,
|
|
4837
|
-
failures: 0,
|
|
4838
|
-
errors: 0,
|
|
4839
|
-
// we cannot detect those
|
|
4840
|
-
time: 0
|
|
4751
|
+
}
|
|
4752
|
+
}
|
|
4753
|
+
/**
|
|
4754
|
+
* Filters all suites that are part of this collection and its children.
|
|
4755
|
+
*/
|
|
4756
|
+
*allSuites() {
|
|
4757
|
+
for (const child of this) {
|
|
4758
|
+
if (child.type === "suite") {
|
|
4759
|
+
yield child;
|
|
4760
|
+
yield* child.children.allSuites();
|
|
4841
4761
|
}
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4762
|
+
}
|
|
4763
|
+
}
|
|
4764
|
+
*[Symbol.iterator]() {
|
|
4765
|
+
for (const task of this.#task.tasks) {
|
|
4766
|
+
yield getReportedTask(this.#project, task);
|
|
4767
|
+
}
|
|
4768
|
+
}
|
|
4769
|
+
}
|
|
4770
|
+
class SuiteImplementation extends ReportedTaskImplementation {
|
|
4771
|
+
/**
|
|
4772
|
+
* Collection of suites and tests that are part of this suite.
|
|
4773
|
+
*/
|
|
4774
|
+
children;
|
|
4775
|
+
/** @internal */
|
|
4776
|
+
constructor(task, project) {
|
|
4777
|
+
super(task, project);
|
|
4778
|
+
this.children = new TestCollection(task, project);
|
|
4779
|
+
}
|
|
4780
|
+
/**
|
|
4781
|
+
* Checks if the suite was skipped during collection.
|
|
4782
|
+
*/
|
|
4783
|
+
skipped() {
|
|
4784
|
+
const mode = this.task.mode;
|
|
4785
|
+
return mode === "skip" || mode === "todo";
|
|
4786
|
+
}
|
|
4787
|
+
/**
|
|
4788
|
+
* Errors that happened outside of the test run during collection, like syntax errors.
|
|
4789
|
+
*/
|
|
4790
|
+
errors() {
|
|
4791
|
+
return this.task.result?.errors || [];
|
|
4792
|
+
}
|
|
4793
|
+
}
|
|
4794
|
+
class TestSuite extends SuiteImplementation {
|
|
4795
|
+
#fullName;
|
|
4796
|
+
type = "suite";
|
|
4797
|
+
/**
|
|
4798
|
+
* Name of the test or the suite.
|
|
4799
|
+
*/
|
|
4800
|
+
name;
|
|
4801
|
+
/**
|
|
4802
|
+
* Direct reference to the test module where the test or suite is defined.
|
|
4803
|
+
*/
|
|
4804
|
+
module;
|
|
4805
|
+
/**
|
|
4806
|
+
* Parent suite. If suite was called directly inside the module, the parent will be the module itself.
|
|
4807
|
+
*/
|
|
4808
|
+
parent;
|
|
4809
|
+
/**
|
|
4810
|
+
* Options that suite was initiated with.
|
|
4811
|
+
*/
|
|
4812
|
+
options;
|
|
4813
|
+
/** @internal */
|
|
4814
|
+
constructor(task, project) {
|
|
4815
|
+
super(task, project);
|
|
4816
|
+
this.name = task.name;
|
|
4817
|
+
this.module = getReportedTask(project, task.file);
|
|
4818
|
+
const suite = this.task.suite;
|
|
4819
|
+
if (suite) {
|
|
4820
|
+
this.parent = getReportedTask(project, suite);
|
|
4821
|
+
} else {
|
|
4822
|
+
this.parent = this.module;
|
|
4823
|
+
}
|
|
4824
|
+
this.options = buildOptions(task);
|
|
4825
|
+
}
|
|
4826
|
+
/**
|
|
4827
|
+
* Full name of the suite including all parent suites separated with `>`.
|
|
4828
|
+
*/
|
|
4829
|
+
get fullName() {
|
|
4830
|
+
if (this.#fullName === void 0) {
|
|
4831
|
+
if (this.parent.type !== "module") {
|
|
4832
|
+
this.#fullName = `${this.parent.fullName} > ${this.name}`;
|
|
4833
|
+
} else {
|
|
4834
|
+
this.#fullName = this.name;
|
|
4863
4835
|
}
|
|
4864
|
-
});
|
|
4865
|
-
if (this.reportFile) {
|
|
4866
|
-
this.ctx.logger.log(`JUNIT report written to ${this.reportFile}`);
|
|
4867
4836
|
}
|
|
4868
|
-
|
|
4869
|
-
|
|
4837
|
+
return this.#fullName;
|
|
4838
|
+
}
|
|
4839
|
+
}
|
|
4840
|
+
class TestModule extends SuiteImplementation {
|
|
4841
|
+
type = "module";
|
|
4842
|
+
/**
|
|
4843
|
+
* This is usually an absolute UNIX file path.
|
|
4844
|
+
* It can be a virtual id if the file is not on the disk.
|
|
4845
|
+
* This value corresponds to Vite's `ModuleGraph` id.
|
|
4846
|
+
*/
|
|
4847
|
+
moduleId;
|
|
4848
|
+
/** @internal */
|
|
4849
|
+
constructor(task, project) {
|
|
4850
|
+
super(task, project);
|
|
4851
|
+
this.moduleId = task.filepath;
|
|
4852
|
+
}
|
|
4853
|
+
/**
|
|
4854
|
+
* Useful information about the module like duration, memory usage, etc.
|
|
4855
|
+
* If the module was not executed yet, all diagnostic values will return `0`.
|
|
4856
|
+
*/
|
|
4857
|
+
diagnostic() {
|
|
4858
|
+
const setupDuration = this.task.setupDuration || 0;
|
|
4859
|
+
const collectDuration = this.task.collectDuration || 0;
|
|
4860
|
+
const prepareDuration = this.task.prepareDuration || 0;
|
|
4861
|
+
const environmentSetupDuration = this.task.environmentLoad || 0;
|
|
4862
|
+
const duration = this.task.result?.duration || 0;
|
|
4863
|
+
return {
|
|
4864
|
+
environmentSetupDuration,
|
|
4865
|
+
prepareDuration,
|
|
4866
|
+
collectDuration,
|
|
4867
|
+
setupDuration,
|
|
4868
|
+
duration
|
|
4869
|
+
};
|
|
4870
|
+
}
|
|
4871
|
+
}
|
|
4872
|
+
function buildOptions(task) {
|
|
4873
|
+
return {
|
|
4874
|
+
each: task.each,
|
|
4875
|
+
concurrent: task.concurrent,
|
|
4876
|
+
shuffle: task.shuffle,
|
|
4877
|
+
retry: task.retry,
|
|
4878
|
+
repeats: task.repeats,
|
|
4879
|
+
mode: task.mode
|
|
4880
|
+
};
|
|
4881
|
+
}
|
|
4882
|
+
function getTestState(test) {
|
|
4883
|
+
if (test.skipped()) {
|
|
4884
|
+
return "skipped";
|
|
4885
|
+
}
|
|
4886
|
+
const result = test.result();
|
|
4887
|
+
return result ? result.state : "running";
|
|
4888
|
+
}
|
|
4889
|
+
function storeTask(project, runnerTask, reportedTask) {
|
|
4890
|
+
project.vitest.state.reportedTasksMap.set(runnerTask, reportedTask);
|
|
4891
|
+
}
|
|
4892
|
+
function getReportedTask(project, runnerTask) {
|
|
4893
|
+
const reportedTask = project.vitest.state.getReportedEntity(runnerTask);
|
|
4894
|
+
if (!reportedTask) {
|
|
4895
|
+
throw new Error(
|
|
4896
|
+
`Task instance was not found for ${runnerTask.type} "${runnerTask.name}"`
|
|
4897
|
+
);
|
|
4870
4898
|
}
|
|
4899
|
+
return reportedTask;
|
|
4871
4900
|
}
|
|
4872
4901
|
|
|
4873
4902
|
function yamlString(str) {
|
|
@@ -4918,7 +4947,7 @@ class TapReporter {
|
|
|
4918
4947
|
this.logger.log("}");
|
|
4919
4948
|
} else {
|
|
4920
4949
|
this.logger.log(`${ok} ${id} - ${tapString(task.name)}${comment}`);
|
|
4921
|
-
const project = this.ctx.
|
|
4950
|
+
const project = this.ctx.getProjectByName(task.file.projectName || "");
|
|
4922
4951
|
if (task.result?.state === "fail" && task.result.errors) {
|
|
4923
4952
|
this.logger.indent();
|
|
4924
4953
|
task.result.errors.forEach((error) => {
|