@vitest/runner 4.1.0-beta.3 → 4.1.0-beta.5
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/chunk-tasks.js +62 -35
- package/dist/index.d.ts +5 -4
- package/dist/index.js +618 -398
- package/dist/{tasks.d-WWG4yDf6.d.ts → tasks.d-D2GKpdwQ.d.ts} +403 -64
- package/dist/types.d.ts +2 -2
- package/dist/utils.d.ts +10 -6
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { processError } from '@vitest/utils/error';
|
|
|
2
2
|
import { isObject, filterOutComments, createDefer, assertTypes, toArray, isNegativeNaN, unique, objectAttr, shuffle } from '@vitest/utils/helpers';
|
|
3
3
|
import { getSafeTimers } from '@vitest/utils/timers';
|
|
4
4
|
import { format, formatRegExp, objDisplay } from '@vitest/utils/display';
|
|
5
|
-
import { a as createChainable, v as validateTags, e as createTaskName,
|
|
5
|
+
import { w as getChainableContext, a as createChainable, v as validateTags, e as createTaskName, x as createNoTagsError, f as findTestFileStackTrace, d as createTagsFilter, b as createFileTask, c as calculateSuiteHash, u as someTasksAreOnly, q as interpretTaskModes, s as limitConcurrency, t as partitionSuiteChildren, p as hasTests, o as hasFailed } from './chunk-tasks.js';
|
|
6
6
|
import '@vitest/utils/source-map';
|
|
7
7
|
import 'pathe';
|
|
8
8
|
|
|
@@ -24,6 +24,9 @@ class TestRunAbortError extends Error {
|
|
|
24
24
|
this.reason = reason;
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
|
+
class FixtureDependencyError extends Error {
|
|
28
|
+
name = "FixtureDependencyError";
|
|
29
|
+
}
|
|
27
30
|
class AroundHookSetupError extends Error {
|
|
28
31
|
name = "AroundHookSetupError";
|
|
29
32
|
}
|
|
@@ -47,7 +50,7 @@ function getFn(key) {
|
|
|
47
50
|
function setTestFixture(key, fixture) {
|
|
48
51
|
testFixtureMap.set(key, fixture);
|
|
49
52
|
}
|
|
50
|
-
function
|
|
53
|
+
function getTestFixtures(key) {
|
|
51
54
|
return testFixtureMap.get(key);
|
|
52
55
|
}
|
|
53
56
|
function setHooks(key, hooks) {
|
|
@@ -57,85 +60,182 @@ function getHooks(key) {
|
|
|
57
60
|
return hooksMap.get(key);
|
|
58
61
|
}
|
|
59
62
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
// will reference the original fixture instead of the scope
|
|
75
|
-
fixture.deps = (_fixture$deps = fixture.deps) === null || _fixture$deps === void 0 ? void 0 : _fixture$deps.map((dep) => newFixtures[dep.prop]);
|
|
76
|
-
}
|
|
77
|
-
return Object.values(newFixtures);
|
|
78
|
-
}
|
|
79
|
-
function mergeContextFixtures(fixtures, context, runner) {
|
|
80
|
-
const fixtureOptionKeys = [
|
|
63
|
+
class TestFixtures {
|
|
64
|
+
_suiteContexts;
|
|
65
|
+
_overrides = new WeakMap();
|
|
66
|
+
_registrations;
|
|
67
|
+
static _definitions = [];
|
|
68
|
+
static _builtinFixtures = [
|
|
69
|
+
"task",
|
|
70
|
+
"signal",
|
|
71
|
+
"onTestFailed",
|
|
72
|
+
"onTestFinished",
|
|
73
|
+
"skip",
|
|
74
|
+
"annotate"
|
|
75
|
+
];
|
|
76
|
+
static _fixtureOptionKeys = [
|
|
81
77
|
"auto",
|
|
82
78
|
"injected",
|
|
83
79
|
"scope"
|
|
84
80
|
];
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
81
|
+
static _fixtureScopes = [
|
|
82
|
+
"test",
|
|
83
|
+
"file",
|
|
84
|
+
"worker"
|
|
85
|
+
];
|
|
86
|
+
static _workerContextSuite = { type: "worker" };
|
|
87
|
+
static clearDefinitions() {
|
|
88
|
+
TestFixtures._definitions.length = 0;
|
|
89
|
+
}
|
|
90
|
+
static getWorkerContexts() {
|
|
91
|
+
return TestFixtures._definitions.map((f) => f.getWorkerContext());
|
|
92
|
+
}
|
|
93
|
+
static getFileContexts(file) {
|
|
94
|
+
return TestFixtures._definitions.map((f) => f.getFileContext(file));
|
|
95
|
+
}
|
|
96
|
+
constructor(registrations) {
|
|
97
|
+
this._registrations = registrations ?? new Map();
|
|
98
|
+
this._suiteContexts = new WeakMap();
|
|
99
|
+
TestFixtures._definitions.push(this);
|
|
100
|
+
}
|
|
101
|
+
extend(runner, userFixtures) {
|
|
102
|
+
const { suite } = getCurrentSuite();
|
|
103
|
+
const isTopLevel = !suite || suite.file === suite;
|
|
104
|
+
const registrations = this.parseUserFixtures(runner, userFixtures, isTopLevel);
|
|
105
|
+
return new TestFixtures(registrations);
|
|
106
|
+
}
|
|
107
|
+
get(suite) {
|
|
108
|
+
let currentSuite = suite;
|
|
109
|
+
while (currentSuite) {
|
|
110
|
+
const overrides = this._overrides.get(currentSuite);
|
|
111
|
+
// return the closest override
|
|
112
|
+
if (overrides) {
|
|
113
|
+
return overrides;
|
|
113
114
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
var _fixture$deps2;
|
|
117
|
-
(_fixture$deps2 = fixture.deps) === null || _fixture$deps2 === void 0 ? void 0 : _fixture$deps2.forEach((dep) => {
|
|
118
|
-
if (!dep.isFn) {
|
|
119
|
-
// non fn fixtures are always resolved and available to anyone
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
// worker scope can only import from worker scope
|
|
123
|
-
if (fixture.scope === "worker" && dep.scope === "worker") {
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
// file scope an import from file and worker scopes
|
|
127
|
-
if (fixture.scope === "file" && dep.scope !== "test") {
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
throw new SyntaxError(`cannot use the ${dep.scope} fixture "${dep.prop}" inside the ${fixture.scope} fixture "${fixture.prop}"`);
|
|
131
|
-
});
|
|
115
|
+
if (currentSuite === currentSuite.file) {
|
|
116
|
+
break;
|
|
132
117
|
}
|
|
118
|
+
currentSuite = currentSuite.suite || currentSuite.file;
|
|
119
|
+
}
|
|
120
|
+
return this._registrations;
|
|
121
|
+
}
|
|
122
|
+
override(runner, userFixtures) {
|
|
123
|
+
const { suite: currentSuite, file } = getCurrentSuite();
|
|
124
|
+
const suite = currentSuite || file;
|
|
125
|
+
const isTopLevel = !currentSuite || currentSuite.file === currentSuite;
|
|
126
|
+
// Create a copy of the closest parent's registrations to avoid modifying them
|
|
127
|
+
// For chained calls, this.get(suite) returns this suite's overrides; for first call, returns parent's
|
|
128
|
+
const suiteRegistrations = new Map(this.get(suite));
|
|
129
|
+
const registrations = this.parseUserFixtures(runner, userFixtures, isTopLevel, suiteRegistrations);
|
|
130
|
+
// If defined in top-level, just override all registrations
|
|
131
|
+
// We don't support overriding suite-level fixtures anyway (it will throw an error)
|
|
132
|
+
if (isTopLevel) {
|
|
133
|
+
this._registrations = registrations;
|
|
134
|
+
} else {
|
|
135
|
+
this._overrides.set(suite, registrations);
|
|
133
136
|
}
|
|
134
|
-
}
|
|
135
|
-
|
|
137
|
+
}
|
|
138
|
+
getFileContext(file) {
|
|
139
|
+
if (!this._suiteContexts.has(file)) {
|
|
140
|
+
this._suiteContexts.set(file, Object.create(null));
|
|
141
|
+
}
|
|
142
|
+
return this._suiteContexts.get(file);
|
|
143
|
+
}
|
|
144
|
+
getWorkerContext() {
|
|
145
|
+
if (!this._suiteContexts.has(TestFixtures._workerContextSuite)) {
|
|
146
|
+
this._suiteContexts.set(TestFixtures._workerContextSuite, Object.create(null));
|
|
147
|
+
}
|
|
148
|
+
return this._suiteContexts.get(TestFixtures._workerContextSuite);
|
|
149
|
+
}
|
|
150
|
+
parseUserFixtures(runner, userFixtures, supportNonTest, registrations = new Map(this._registrations)) {
|
|
151
|
+
const errors = [];
|
|
152
|
+
Object.entries(userFixtures).forEach(([name, fn]) => {
|
|
153
|
+
let options;
|
|
154
|
+
let value;
|
|
155
|
+
let _options;
|
|
156
|
+
if (Array.isArray(fn) && fn.length >= 2 && isObject(fn[1]) && Object.keys(fn[1]).some((key) => TestFixtures._fixtureOptionKeys.includes(key))) {
|
|
157
|
+
_options = fn[1];
|
|
158
|
+
options = {
|
|
159
|
+
auto: _options.auto ?? false,
|
|
160
|
+
scope: _options.scope ?? "test",
|
|
161
|
+
injected: _options.injected ?? false
|
|
162
|
+
};
|
|
163
|
+
value = options.injected ? runner.injectValue?.(name) ?? fn[0] : fn[0];
|
|
164
|
+
} else {
|
|
165
|
+
value = fn;
|
|
166
|
+
}
|
|
167
|
+
const parent = registrations.get(name);
|
|
168
|
+
if (parent && options) {
|
|
169
|
+
if (parent.scope !== options.scope) {
|
|
170
|
+
errors.push(new FixtureDependencyError(`The "${name}" fixture was already registered with a "${options.scope}" scope.`));
|
|
171
|
+
}
|
|
172
|
+
if (parent.auto !== options.auto) {
|
|
173
|
+
errors.push(new FixtureDependencyError(`The "${name}" fixture was already registered as { auto: ${options.auto} }.`));
|
|
174
|
+
}
|
|
175
|
+
} else if (parent) {
|
|
176
|
+
options = {
|
|
177
|
+
auto: parent.auto,
|
|
178
|
+
scope: parent.scope,
|
|
179
|
+
injected: parent.injected
|
|
180
|
+
};
|
|
181
|
+
} else if (!options) {
|
|
182
|
+
options = {
|
|
183
|
+
auto: false,
|
|
184
|
+
injected: false,
|
|
185
|
+
scope: "test"
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
if (options.scope && !TestFixtures._fixtureScopes.includes(options.scope)) {
|
|
189
|
+
errors.push(new FixtureDependencyError(`The "${name}" fixture has unknown scope "${options.scope}".`));
|
|
190
|
+
}
|
|
191
|
+
if (!supportNonTest && options.scope !== "test") {
|
|
192
|
+
errors.push(new FixtureDependencyError(`The "${name}" fixture cannot be defined with a ${options.scope} scope${!_options?.scope && parent?.scope ? " (inherited from the base fixture)" : ""} inside the describe block. Define it at the top level of the file instead.`));
|
|
193
|
+
}
|
|
194
|
+
const deps = isFixtureFunction(value) ? getUsedProps(value) : new Set();
|
|
195
|
+
const item = {
|
|
196
|
+
name,
|
|
197
|
+
value,
|
|
198
|
+
auto: options.auto ?? false,
|
|
199
|
+
injected: options.injected ?? false,
|
|
200
|
+
scope: options.scope ?? "test",
|
|
201
|
+
deps,
|
|
202
|
+
parent
|
|
203
|
+
};
|
|
204
|
+
registrations.set(name, item);
|
|
205
|
+
if (item.scope === "worker" && (runner.pool === "vmThreads" || runner.pool === "vmForks")) {
|
|
206
|
+
item.scope = "file";
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
// validate fixture dependency scopes
|
|
210
|
+
for (const fixture of registrations.values()) {
|
|
211
|
+
for (const depName of fixture.deps) {
|
|
212
|
+
if (TestFixtures._builtinFixtures.includes(depName)) {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
const dep = registrations.get(depName);
|
|
216
|
+
if (!dep) {
|
|
217
|
+
errors.push(new FixtureDependencyError(`The "${fixture.name}" fixture depends on unknown fixture "${depName}".`));
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (depName === fixture.name && !fixture.parent) {
|
|
221
|
+
errors.push(new FixtureDependencyError(`The "${fixture.name}" fixture depends on itself, but does not have a base implementation.`));
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (TestFixtures._fixtureScopes.indexOf(fixture.scope) > TestFixtures._fixtureScopes.indexOf(dep.scope)) {
|
|
225
|
+
errors.push(new FixtureDependencyError(`The ${fixture.scope} "${fixture.name}" fixture cannot depend on a ${dep.scope} fixture "${dep.name}".`));
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (errors.length === 1) {
|
|
231
|
+
throw errors[0];
|
|
232
|
+
} else if (errors.length > 1) {
|
|
233
|
+
throw new AggregateError(errors, "Cannot resolve user fixtures. See errors for more information.");
|
|
234
|
+
}
|
|
235
|
+
return registrations;
|
|
236
|
+
}
|
|
136
237
|
}
|
|
137
|
-
const
|
|
138
|
-
const cleanupFnArrayMap = new Map();
|
|
238
|
+
const cleanupFnArrayMap = new WeakMap();
|
|
139
239
|
async function callFixtureCleanup(context) {
|
|
140
240
|
const cleanupFnArray = cleanupFnArrayMap.get(context) ?? [];
|
|
141
241
|
for (const cleanup of cleanupFnArray.reverse()) {
|
|
@@ -148,8 +248,7 @@ async function callFixtureCleanup(context) {
|
|
|
148
248
|
* This can be used as a checkpoint to later clean up only fixtures added after this point.
|
|
149
249
|
*/
|
|
150
250
|
function getFixtureCleanupCount(context) {
|
|
151
|
-
|
|
152
|
-
return ((_cleanupFnArrayMap$ge = cleanupFnArrayMap.get(context)) === null || _cleanupFnArrayMap$ge === void 0 ? void 0 : _cleanupFnArrayMap$ge.length) ?? 0;
|
|
251
|
+
return cleanupFnArrayMap.get(context)?.length ?? 0;
|
|
153
252
|
}
|
|
154
253
|
/**
|
|
155
254
|
* Cleans up only fixtures that were added after the given checkpoint index.
|
|
@@ -170,96 +269,126 @@ async function callFixtureCleanupFrom(context, fromIndex) {
|
|
|
170
269
|
// Remove cleaned up items from the array, keeping items before checkpoint
|
|
171
270
|
cleanupFnArray.length = fromIndex;
|
|
172
271
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
272
|
+
const contextHasFixturesCache = new WeakMap();
|
|
273
|
+
function withFixtures(fn, options) {
|
|
274
|
+
const collector = getCurrentSuite();
|
|
275
|
+
const suite = collector.suite || collector.file;
|
|
276
|
+
return async (hookContext) => {
|
|
277
|
+
const context = hookContext || options?.context;
|
|
176
278
|
if (!context) {
|
|
279
|
+
if (options?.suiteHook) {
|
|
280
|
+
validateSuiteHook(fn, options.suiteHook, options.stackTraceError);
|
|
281
|
+
}
|
|
177
282
|
return fn({});
|
|
178
283
|
}
|
|
179
|
-
const fixtures =
|
|
180
|
-
if (!
|
|
284
|
+
const fixtures = options?.fixtures || getTestFixtures(context);
|
|
285
|
+
if (!fixtures) {
|
|
181
286
|
return fn(context);
|
|
182
287
|
}
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
if (!usedProps.length && !hasAutoFixture) {
|
|
288
|
+
const registrations = fixtures.get(suite);
|
|
289
|
+
if (!registrations.size) {
|
|
186
290
|
return fn(context);
|
|
187
291
|
}
|
|
188
|
-
|
|
189
|
-
|
|
292
|
+
const usedFixtures = [];
|
|
293
|
+
const usedProps = getUsedProps(fn);
|
|
294
|
+
for (const fixture of registrations.values()) {
|
|
295
|
+
if (fixture.auto || usedProps.has(fixture.name)) {
|
|
296
|
+
usedFixtures.push(fixture);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (!usedFixtures.length) {
|
|
300
|
+
return fn(context);
|
|
190
301
|
}
|
|
191
|
-
const fixtureValueMap = fixtureValueMaps.get(context);
|
|
192
302
|
if (!cleanupFnArrayMap.has(context)) {
|
|
193
303
|
cleanupFnArrayMap.set(context, []);
|
|
194
304
|
}
|
|
195
305
|
const cleanupFnArray = cleanupFnArrayMap.get(context);
|
|
196
|
-
const
|
|
197
|
-
const pendingFixtures = resolveDeps(usedFixtures);
|
|
306
|
+
const pendingFixtures = resolveDeps(usedFixtures, registrations);
|
|
198
307
|
if (!pendingFixtures.length) {
|
|
199
308
|
return fn(context);
|
|
200
309
|
}
|
|
201
|
-
|
|
202
|
-
|
|
310
|
+
// Check if suite-level hook is trying to access test-scoped fixtures
|
|
311
|
+
// Suite hooks (beforeAll/afterAll/aroundAll) can only access file/worker scoped fixtures
|
|
312
|
+
if (options?.suiteHook) {
|
|
313
|
+
const testScopedFixtures = pendingFixtures.filter((f) => f.scope === "test");
|
|
314
|
+
if (testScopedFixtures.length > 0) {
|
|
315
|
+
const fixtureNames = testScopedFixtures.map((f) => `"${f.name}"`).join(", ");
|
|
316
|
+
const alternativeHook = {
|
|
317
|
+
aroundAll: "aroundEach",
|
|
318
|
+
beforeAll: "beforeEach",
|
|
319
|
+
afterAll: "afterEach"
|
|
320
|
+
};
|
|
321
|
+
const error = new FixtureDependencyError(`Test-scoped fixtures cannot be used inside ${options.suiteHook} hook. ` + `The following fixtures are test-scoped: ${fixtureNames}. ` + `Use { scope: 'file' } or { scope: 'worker' } fixtures instead, or move the logic to ${alternativeHook[options.suiteHook]} hook.`);
|
|
322
|
+
// Use stack trace from hook registration for better error location
|
|
323
|
+
if (options.stackTraceError?.stack) {
|
|
324
|
+
error.stack = error.message + options.stackTraceError.stack.replace(options.stackTraceError.message, "");
|
|
325
|
+
}
|
|
326
|
+
throw error;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (!contextHasFixturesCache.has(context)) {
|
|
330
|
+
contextHasFixturesCache.set(context, new WeakSet());
|
|
331
|
+
}
|
|
332
|
+
const cachedFixtures = contextHasFixturesCache.get(context);
|
|
333
|
+
for (const fixture of pendingFixtures) {
|
|
334
|
+
if (fixture.scope === "test") {
|
|
203
335
|
// fixture could be already initialized during "before" hook
|
|
204
|
-
|
|
336
|
+
// we can't check "fixture.name" in context because context may
|
|
337
|
+
// access the parent fixture ({ a: ({ a }) => {} })
|
|
338
|
+
if (cachedFixtures.has(fixture)) {
|
|
205
339
|
continue;
|
|
206
340
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
341
|
+
cachedFixtures.add(fixture);
|
|
342
|
+
const resolvedValue = await resolveTestFixtureValue(fixture, context, cleanupFnArray);
|
|
343
|
+
context[fixture.name] = resolvedValue;
|
|
344
|
+
cleanupFnArray.push(() => {
|
|
345
|
+
cachedFixtures.delete(fixture);
|
|
346
|
+
});
|
|
347
|
+
} else {
|
|
348
|
+
const resolvedValue = await resolveScopeFixtureValue(fixtures, suite, fixture);
|
|
349
|
+
context[fixture.name] = resolvedValue;
|
|
215
350
|
}
|
|
216
351
|
}
|
|
217
|
-
return
|
|
352
|
+
return fn(context);
|
|
218
353
|
};
|
|
219
354
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (!fixture.isFn) {
|
|
226
|
-
var _fixture$prop;
|
|
227
|
-
fileContext[_fixture$prop = fixture.prop] ?? (fileContext[_fixture$prop] = fixture.value);
|
|
228
|
-
if (workerContext) {
|
|
229
|
-
var _fixture$prop2;
|
|
230
|
-
workerContext[_fixture$prop2 = fixture.prop] ?? (workerContext[_fixture$prop2] = fixture.value);
|
|
231
|
-
}
|
|
355
|
+
function isFixtureFunction(value) {
|
|
356
|
+
return typeof value === "function";
|
|
357
|
+
}
|
|
358
|
+
function resolveTestFixtureValue(fixture, context, cleanupFnArray) {
|
|
359
|
+
if (!isFixtureFunction(fixture.value)) {
|
|
232
360
|
return fixture.value;
|
|
233
361
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
362
|
+
return resolveFixtureFunction(fixture.value, context, cleanupFnArray);
|
|
363
|
+
}
|
|
364
|
+
const scopedFixturePromiseCache = new WeakMap();
|
|
365
|
+
async function resolveScopeFixtureValue(fixtures, suite, fixture) {
|
|
366
|
+
const workerContext = fixtures.getWorkerContext();
|
|
367
|
+
const fileContext = fixtures.getFileContext(suite.file);
|
|
368
|
+
const fixtureContext = fixture.scope === "worker" ? workerContext : fileContext;
|
|
369
|
+
if (!isFixtureFunction(fixture.value)) {
|
|
370
|
+
fixtureContext[fixture.name] = fixture.value;
|
|
371
|
+
return fixture.value;
|
|
240
372
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
if (!workerContext) {
|
|
244
|
-
throw new TypeError("[@vitest/runner] The worker context is not available in the current test runner. Please, provide the `getWorkerContext` method when initiating the runner.");
|
|
245
|
-
}
|
|
246
|
-
fixtureContext = workerContext;
|
|
247
|
-
} else {
|
|
248
|
-
fixtureContext = fileContext;
|
|
373
|
+
if (fixture.name in fixtureContext) {
|
|
374
|
+
return fixtureContext[fixture.name];
|
|
249
375
|
}
|
|
250
|
-
if (fixture
|
|
251
|
-
return
|
|
376
|
+
if (scopedFixturePromiseCache.has(fixture)) {
|
|
377
|
+
return scopedFixturePromiseCache.get(fixture);
|
|
252
378
|
}
|
|
253
379
|
if (!cleanupFnArrayMap.has(fixtureContext)) {
|
|
254
380
|
cleanupFnArrayMap.set(fixtureContext, []);
|
|
255
381
|
}
|
|
256
382
|
const cleanupFnFileArray = cleanupFnArrayMap.get(fixtureContext);
|
|
257
|
-
const promise = resolveFixtureFunction(fixture.value,
|
|
258
|
-
|
|
259
|
-
|
|
383
|
+
const promise = resolveFixtureFunction(fixture.value, fixture.scope === "file" ? {
|
|
384
|
+
...workerContext,
|
|
385
|
+
...fileContext
|
|
386
|
+
} : fixtureContext, cleanupFnFileArray).then((value) => {
|
|
387
|
+
fixtureContext[fixture.name] = value;
|
|
388
|
+
scopedFixturePromiseCache.delete(fixture);
|
|
260
389
|
return value;
|
|
261
390
|
});
|
|
262
|
-
|
|
391
|
+
scopedFixturePromiseCache.set(fixture, promise);
|
|
263
392
|
return promise;
|
|
264
393
|
}
|
|
265
394
|
async function resolveFixtureFunction(fixtureFn, context, cleanupFnArray) {
|
|
@@ -290,27 +419,50 @@ async function resolveFixtureFunction(fixtureFn, context, cleanupFnArray) {
|
|
|
290
419
|
});
|
|
291
420
|
return useFnArgPromise;
|
|
292
421
|
}
|
|
293
|
-
function resolveDeps(
|
|
294
|
-
|
|
422
|
+
function resolveDeps(usedFixtures, registrations, depSet = new Set(), pendingFixtures = []) {
|
|
423
|
+
usedFixtures.forEach((fixture) => {
|
|
295
424
|
if (pendingFixtures.includes(fixture)) {
|
|
296
425
|
return;
|
|
297
426
|
}
|
|
298
|
-
if (!fixture.
|
|
427
|
+
if (!isFixtureFunction(fixture.value) || !fixture.deps) {
|
|
299
428
|
pendingFixtures.push(fixture);
|
|
300
429
|
return;
|
|
301
430
|
}
|
|
302
431
|
if (depSet.has(fixture)) {
|
|
303
|
-
|
|
432
|
+
if (fixture.parent) {
|
|
433
|
+
fixture = fixture.parent;
|
|
434
|
+
} else {
|
|
435
|
+
throw new Error(`Circular fixture dependency detected: ${fixture.name} <- ${[...depSet].reverse().map((d) => d.name).join(" <- ")}`);
|
|
436
|
+
}
|
|
304
437
|
}
|
|
305
438
|
depSet.add(fixture);
|
|
306
|
-
resolveDeps(fixture.deps, depSet, pendingFixtures);
|
|
439
|
+
resolveDeps([...fixture.deps].map((n) => n === fixture.name ? fixture.parent : registrations.get(n)).filter((n) => !!n), registrations, depSet, pendingFixtures);
|
|
307
440
|
pendingFixtures.push(fixture);
|
|
308
441
|
depSet.clear();
|
|
309
442
|
});
|
|
310
443
|
return pendingFixtures;
|
|
311
444
|
}
|
|
445
|
+
function validateSuiteHook(fn, hook, error) {
|
|
446
|
+
const usedProps = getUsedProps(fn);
|
|
447
|
+
if (usedProps.size) {
|
|
448
|
+
console.warn(`The ${hook} hook uses fixtures "${[...usedProps].join("\", \"")}", but has no access to context. Did you forget to call it as "test.${hook}()" instead of "${hook}()"? This will throw an error in a future major. See https://vitest.dev/guide/test-context#suite-level-hooks`);
|
|
449
|
+
if (error) {
|
|
450
|
+
const processor = globalThis.__vitest_worker__?.onFilterStackTrace || ((s) => s || "");
|
|
451
|
+
const stack = processor(error.stack || "");
|
|
452
|
+
console.warn(stack);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
const kPropsSymbol = Symbol("$vitest:fixture-props");
|
|
457
|
+
function configureProps(fn, options) {
|
|
458
|
+
Object.defineProperty(fn, kPropsSymbol, {
|
|
459
|
+
value: options,
|
|
460
|
+
enumerable: false
|
|
461
|
+
});
|
|
462
|
+
}
|
|
312
463
|
function getUsedProps(fn) {
|
|
313
|
-
|
|
464
|
+
const { index: fixturesIndex = 0, original: implementation = fn } = kPropsSymbol in fn ? fn[kPropsSymbol] : {};
|
|
465
|
+
let fnString = filterOutComments(implementation.toString());
|
|
314
466
|
// match lowered async function and strip it off
|
|
315
467
|
// example code on esbuild-try https://esbuild.github.io/try/#YgAwLjI0LjAALS1zdXBwb3J0ZWQ6YXN5bmMtYXdhaXQ9ZmFsc2UAZQBlbnRyeS50cwBjb25zdCBvID0gewogIGYxOiBhc3luYyAoKSA9PiB7fSwKICBmMjogYXN5bmMgKGEpID0+IHt9LAogIGYzOiBhc3luYyAoYSwgYikgPT4ge30sCiAgZjQ6IGFzeW5jIGZ1bmN0aW9uKGEpIHt9LAogIGY1OiBhc3luYyBmdW5jdGlvbiBmZihhKSB7fSwKICBhc3luYyBmNihhKSB7fSwKCiAgZzE6IGFzeW5jICgpID0+IHt9LAogIGcyOiBhc3luYyAoeyBhIH0pID0+IHt9LAogIGczOiBhc3luYyAoeyBhIH0sIGIpID0+IHt9LAogIGc0OiBhc3luYyBmdW5jdGlvbiAoeyBhIH0pIHt9LAogIGc1OiBhc3luYyBmdW5jdGlvbiBnZyh7IGEgfSkge30sCiAgYXN5bmMgZzYoeyBhIH0pIHt9LAoKICBoMTogYXN5bmMgKCkgPT4ge30sCiAgLy8gY29tbWVudCBiZXR3ZWVuCiAgaDI6IGFzeW5jIChhKSA9PiB7fSwKfQ
|
|
316
468
|
// __async(this, null, function*
|
|
@@ -321,23 +473,20 @@ function getUsedProps(fn) {
|
|
|
321
473
|
}
|
|
322
474
|
const match = fnString.match(/[^(]*\(([^)]*)/);
|
|
323
475
|
if (!match) {
|
|
324
|
-
return
|
|
476
|
+
return new Set();
|
|
325
477
|
}
|
|
326
478
|
const args = splitByComma(match[1]);
|
|
327
479
|
if (!args.length) {
|
|
328
|
-
return
|
|
480
|
+
return new Set();
|
|
329
481
|
}
|
|
330
|
-
|
|
331
|
-
if (
|
|
332
|
-
|
|
333
|
-
if (!first) {
|
|
334
|
-
return [];
|
|
335
|
-
}
|
|
482
|
+
const fixturesArgument = args[fixturesIndex];
|
|
483
|
+
if (!fixturesArgument) {
|
|
484
|
+
return new Set();
|
|
336
485
|
}
|
|
337
|
-
if (!(
|
|
338
|
-
throw new Error(`The first argument inside a fixture must use object destructuring pattern, e.g. ({ test } => {}). Instead, received "${
|
|
486
|
+
if (!(fixturesArgument[0] === "{" && fixturesArgument.endsWith("}"))) {
|
|
487
|
+
throw new Error(`The first argument inside a fixture must use object destructuring pattern, e.g. ({ test } => {}). Instead, received "${fixturesArgument}".`);
|
|
339
488
|
}
|
|
340
|
-
const _first =
|
|
489
|
+
const _first = fixturesArgument.slice(1, -1).replace(/\s/g, "");
|
|
341
490
|
const props = splitByComma(_first).map((prop) => {
|
|
342
491
|
return prop.replace(/:.*|=.*/g, "");
|
|
343
492
|
});
|
|
@@ -345,7 +494,7 @@ function getUsedProps(fn) {
|
|
|
345
494
|
if (last && last.startsWith("...")) {
|
|
346
495
|
throw new Error(`Rest parameters are not supported in fixtures, received "${last}".`);
|
|
347
496
|
}
|
|
348
|
-
return props;
|
|
497
|
+
return new Set(props);
|
|
349
498
|
}
|
|
350
499
|
function splitByComma(s) {
|
|
351
500
|
const result = [];
|
|
@@ -427,7 +576,8 @@ function getBeforeHookCleanupCallback(hook, result, context) {
|
|
|
427
576
|
function beforeAll(fn, timeout = getDefaultHookTimeout()) {
|
|
428
577
|
assertTypes(fn, "\"beforeAll\" callback", ["function"]);
|
|
429
578
|
const stackTraceError = new Error("STACK_TRACE_ERROR");
|
|
430
|
-
|
|
579
|
+
const context = getChainableContext(this);
|
|
580
|
+
return getCurrentSuite().on("beforeAll", Object.assign(withTimeout(withSuiteFixtures("beforeAll", fn, context, stackTraceError), timeout, true, stackTraceError), {
|
|
431
581
|
[CLEANUP_TIMEOUT_KEY]: timeout,
|
|
432
582
|
[CLEANUP_STACK_TRACE_KEY]: stackTraceError
|
|
433
583
|
}));
|
|
@@ -451,7 +601,9 @@ function beforeAll(fn, timeout = getDefaultHookTimeout()) {
|
|
|
451
601
|
*/
|
|
452
602
|
function afterAll(fn, timeout) {
|
|
453
603
|
assertTypes(fn, "\"afterAll\" callback", ["function"]);
|
|
454
|
-
|
|
604
|
+
const context = getChainableContext(this);
|
|
605
|
+
const stackTraceError = new Error("STACK_TRACE_ERROR");
|
|
606
|
+
return getCurrentSuite().on("afterAll", withTimeout(withSuiteFixtures("afterAll", fn, context, stackTraceError), timeout ?? getDefaultHookTimeout(), true, stackTraceError));
|
|
455
607
|
}
|
|
456
608
|
/**
|
|
457
609
|
* Registers a callback function to be executed before each test within the current suite.
|
|
@@ -473,8 +625,7 @@ function afterAll(fn, timeout) {
|
|
|
473
625
|
function beforeEach(fn, timeout = getDefaultHookTimeout()) {
|
|
474
626
|
assertTypes(fn, "\"beforeEach\" callback", ["function"]);
|
|
475
627
|
const stackTraceError = new Error("STACK_TRACE_ERROR");
|
|
476
|
-
|
|
477
|
-
return getCurrentSuite().on("beforeEach", Object.assign(withTimeout(withFixtures(runner, fn), timeout ?? getDefaultHookTimeout(), true, stackTraceError, abortIfTimeout), {
|
|
628
|
+
return getCurrentSuite().on("beforeEach", Object.assign(withTimeout(withFixtures(fn), timeout ?? getDefaultHookTimeout(), true, stackTraceError, abortIfTimeout), {
|
|
478
629
|
[CLEANUP_TIMEOUT_KEY]: timeout,
|
|
479
630
|
[CLEANUP_STACK_TRACE_KEY]: stackTraceError
|
|
480
631
|
}));
|
|
@@ -498,8 +649,7 @@ function beforeEach(fn, timeout = getDefaultHookTimeout()) {
|
|
|
498
649
|
*/
|
|
499
650
|
function afterEach(fn, timeout) {
|
|
500
651
|
assertTypes(fn, "\"afterEach\" callback", ["function"]);
|
|
501
|
-
|
|
502
|
-
return getCurrentSuite().on("afterEach", withTimeout(withFixtures(runner, fn), timeout ?? getDefaultHookTimeout(), true, new Error("STACK_TRACE_ERROR"), abortIfTimeout));
|
|
652
|
+
return getCurrentSuite().on("afterEach", withTimeout(withFixtures(fn), timeout ?? getDefaultHookTimeout(), true, new Error("STACK_TRACE_ERROR"), abortIfTimeout));
|
|
503
653
|
}
|
|
504
654
|
/**
|
|
505
655
|
* Registers a callback function to be executed when a test fails within the current suite.
|
|
@@ -520,7 +670,7 @@ function afterEach(fn, timeout) {
|
|
|
520
670
|
* ```
|
|
521
671
|
*/
|
|
522
672
|
const onTestFailed = createTestHook("onTestFailed", (test, handler, timeout) => {
|
|
523
|
-
test.onFailed
|
|
673
|
+
test.onFailed ||= [];
|
|
524
674
|
test.onFailed.push(withTimeout(handler, timeout ?? getDefaultHookTimeout(), true, new Error("STACK_TRACE_ERROR"), abortIfTimeout));
|
|
525
675
|
});
|
|
526
676
|
/**
|
|
@@ -547,7 +697,7 @@ const onTestFailed = createTestHook("onTestFailed", (test, handler, timeout) =>
|
|
|
547
697
|
* ```
|
|
548
698
|
*/
|
|
549
699
|
const onTestFinished = createTestHook("onTestFinished", (test, handler, timeout) => {
|
|
550
|
-
test.onFinished
|
|
700
|
+
test.onFinished ||= [];
|
|
551
701
|
test.onFinished.push(withTimeout(handler, timeout ?? getDefaultHookTimeout(), true, new Error("STACK_TRACE_ERROR"), abortIfTimeout));
|
|
552
702
|
});
|
|
553
703
|
/**
|
|
@@ -559,9 +709,6 @@ const onTestFinished = createTestHook("onTestFinished", (test, handler, timeout)
|
|
|
559
709
|
* **Note:** When multiple `aroundAll` hooks are registered, they are nested inside each other.
|
|
560
710
|
* The first registered hook is the outermost wrapper.
|
|
561
711
|
*
|
|
562
|
-
* **Note:** Unlike `aroundEach`, the `aroundAll` hook does not receive test context or support fixtures,
|
|
563
|
-
* as it runs at the suite level before any individual test context is created.
|
|
564
|
-
*
|
|
565
712
|
* @param {Function} fn - The callback function that wraps the suite. Must call `runSuite()` to run the tests.
|
|
566
713
|
* @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
|
|
567
714
|
* @returns {void}
|
|
@@ -574,9 +721,9 @@ const onTestFinished = createTestHook("onTestFinished", (test, handler, timeout)
|
|
|
574
721
|
* ```
|
|
575
722
|
* @example
|
|
576
723
|
* ```ts
|
|
577
|
-
* // Example of using aroundAll with
|
|
578
|
-
* aroundAll(async (runSuite) => {
|
|
579
|
-
* await
|
|
724
|
+
* // Example of using aroundAll with fixtures
|
|
725
|
+
* aroundAll(async (runSuite, { db }) => {
|
|
726
|
+
* await db.transaction(() => runSuite());
|
|
580
727
|
* });
|
|
581
728
|
* ```
|
|
582
729
|
*/
|
|
@@ -584,7 +731,8 @@ function aroundAll(fn, timeout) {
|
|
|
584
731
|
assertTypes(fn, "\"aroundAll\" callback", ["function"]);
|
|
585
732
|
const stackTraceError = new Error("STACK_TRACE_ERROR");
|
|
586
733
|
const resolvedTimeout = timeout ?? getDefaultHookTimeout();
|
|
587
|
-
|
|
734
|
+
const context = getChainableContext(this);
|
|
735
|
+
return getCurrentSuite().on("aroundAll", Object.assign(withSuiteFixtures("aroundAll", fn, context, stackTraceError, 1), {
|
|
588
736
|
[AROUND_TIMEOUT_KEY]: resolvedTimeout,
|
|
589
737
|
[AROUND_STACK_TRACE_KEY]: stackTraceError
|
|
590
738
|
}));
|
|
@@ -619,37 +767,39 @@ function aroundEach(fn, timeout) {
|
|
|
619
767
|
assertTypes(fn, "\"aroundEach\" callback", ["function"]);
|
|
620
768
|
const stackTraceError = new Error("STACK_TRACE_ERROR");
|
|
621
769
|
const resolvedTimeout = timeout ?? getDefaultHookTimeout();
|
|
622
|
-
const
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
770
|
+
const wrapper = (runTest, context, suite) => {
|
|
771
|
+
const innerFn = (ctx) => fn(runTest, ctx, suite);
|
|
772
|
+
configureProps(innerFn, {
|
|
773
|
+
index: 1,
|
|
774
|
+
original: fn
|
|
775
|
+
});
|
|
776
|
+
const fixtureResolver = withFixtures(innerFn);
|
|
777
|
+
return fixtureResolver(context);
|
|
778
|
+
};
|
|
779
|
+
return getCurrentSuite().on("aroundEach", Object.assign(wrapper, {
|
|
629
780
|
[AROUND_TIMEOUT_KEY]: resolvedTimeout,
|
|
630
781
|
[AROUND_STACK_TRACE_KEY]: stackTraceError
|
|
631
782
|
}));
|
|
632
783
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
return
|
|
784
|
+
function withSuiteFixtures(suiteHook, fn, context, stackTraceError, contextIndex = 0) {
|
|
785
|
+
return (...args) => {
|
|
786
|
+
const suite = args.at(-1);
|
|
787
|
+
const prefix = args.slice(0, -1);
|
|
788
|
+
const wrapper = (ctx) => fn(...prefix, ctx, suite);
|
|
789
|
+
configureProps(wrapper, {
|
|
790
|
+
index: contextIndex,
|
|
791
|
+
original: fn
|
|
792
|
+
});
|
|
793
|
+
const fixtures = context?.getFixtures();
|
|
794
|
+
const fileContext = fixtures?.getFileContext(suite.file);
|
|
795
|
+
const fixtured = withFixtures(wrapper, {
|
|
796
|
+
suiteHook,
|
|
797
|
+
fixtures,
|
|
798
|
+
context: fileContext,
|
|
799
|
+
stackTraceError
|
|
800
|
+
});
|
|
801
|
+
return fixtured();
|
|
651
802
|
};
|
|
652
|
-
return wrapper;
|
|
653
803
|
}
|
|
654
804
|
function getAroundHookTimeout(hook) {
|
|
655
805
|
return AROUND_TIMEOUT_KEY in hook && typeof hook[AROUND_TIMEOUT_KEY] === "number" ? hook[AROUND_TIMEOUT_KEY] : getDefaultHookTimeout();
|
|
@@ -875,19 +1025,17 @@ function parseArguments(optionsOrFn, timeoutOrTest) {
|
|
|
875
1025
|
};
|
|
876
1026
|
}
|
|
877
1027
|
// implementations
|
|
878
|
-
function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions
|
|
1028
|
+
function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions) {
|
|
879
1029
|
const tasks = [];
|
|
880
1030
|
let suite;
|
|
881
1031
|
initSuite(true);
|
|
882
1032
|
const task = function(name = "", options = {}) {
|
|
883
|
-
|
|
884
|
-
const
|
|
885
|
-
const
|
|
886
|
-
const parentTags = (parentTask === null || parentTask === void 0 ? void 0 : parentTask.tags) || [];
|
|
1033
|
+
const currentSuite = collectorContext.currentSuite?.suite;
|
|
1034
|
+
const parentTask = currentSuite ?? collectorContext.currentSuite?.file;
|
|
1035
|
+
const parentTags = parentTask?.tags || [];
|
|
887
1036
|
const testTags = unique([...parentTags, ...toArray(options.tags)]);
|
|
888
1037
|
const tagsOptions = testTags.map((tag) => {
|
|
889
|
-
|
|
890
|
-
const tagDefinition = (_runner$config$tags = runner.config.tags) === null || _runner$config$tags === void 0 ? void 0 : _runner$config$tags.find((t) => t.name === tag);
|
|
1038
|
+
const tagDefinition = runner.config.tags?.find((t) => t.name === tag);
|
|
891
1039
|
if (!tagDefinition && runner.config.strictTags) {
|
|
892
1040
|
throw createNoTagsError(runner.config.tags, tag);
|
|
893
1041
|
}
|
|
@@ -906,7 +1054,7 @@ function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions
|
|
|
906
1054
|
...options
|
|
907
1055
|
};
|
|
908
1056
|
const timeout = options.timeout ?? runner.config.testTimeout;
|
|
909
|
-
const parentMeta = currentSuite
|
|
1057
|
+
const parentMeta = currentSuite?.meta;
|
|
910
1058
|
const tagMeta = tagsOptions.meta;
|
|
911
1059
|
const testMeta = Object.create(null);
|
|
912
1060
|
if (tagMeta) {
|
|
@@ -921,14 +1069,14 @@ function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions
|
|
|
921
1069
|
const task = {
|
|
922
1070
|
id: "",
|
|
923
1071
|
name,
|
|
924
|
-
fullName: createTaskName([
|
|
925
|
-
fullTestName: createTaskName([currentSuite
|
|
1072
|
+
fullName: createTaskName([currentSuite?.fullName ?? collectorContext.currentSuite?.file?.fullName, name]),
|
|
1073
|
+
fullTestName: createTaskName([currentSuite?.fullTestName, name]),
|
|
926
1074
|
suite: currentSuite,
|
|
927
1075
|
each: options.each,
|
|
928
1076
|
fails: options.fails,
|
|
929
1077
|
context: undefined,
|
|
930
1078
|
type: "test",
|
|
931
|
-
file:
|
|
1079
|
+
file: currentSuite?.file ?? collectorContext.currentSuite?.file,
|
|
932
1080
|
timeout,
|
|
933
1081
|
retry: options.retry ?? runner.config.retry,
|
|
934
1082
|
repeats: options.repeats,
|
|
@@ -945,21 +1093,20 @@ function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions
|
|
|
945
1093
|
if (options.concurrent || !options.sequential && runner.config.sequence.concurrent) {
|
|
946
1094
|
task.concurrent = true;
|
|
947
1095
|
}
|
|
948
|
-
task.shuffle = suiteOptions
|
|
1096
|
+
task.shuffle = suiteOptions?.shuffle;
|
|
949
1097
|
const context = createTestContext(task, runner);
|
|
950
1098
|
// create test context
|
|
951
1099
|
Object.defineProperty(task, "context", {
|
|
952
1100
|
value: context,
|
|
953
1101
|
enumerable: false
|
|
954
1102
|
});
|
|
955
|
-
setTestFixture(context, options.fixtures);
|
|
956
|
-
// custom can be called from any place, let's assume the limit is 15 stacks
|
|
1103
|
+
setTestFixture(context, options.fixtures ?? new TestFixtures());
|
|
957
1104
|
const limit = Error.stackTraceLimit;
|
|
958
|
-
Error.stackTraceLimit =
|
|
1105
|
+
Error.stackTraceLimit = 10;
|
|
959
1106
|
const stackTraceError = new Error("STACK_TRACE_ERROR");
|
|
960
1107
|
Error.stackTraceLimit = limit;
|
|
961
1108
|
if (handler) {
|
|
962
|
-
setFn(task, withTimeout(withAwaitAsyncAssertions(withFixtures(
|
|
1109
|
+
setFn(task, withTimeout(withCancel(withAwaitAsyncAssertions(withFixtures(handler, { context }), task), task.context.signal), timeout, false, stackTraceError, (_, error) => abortIfTimeout([context], error)));
|
|
963
1110
|
}
|
|
964
1111
|
if (runner.config.includeTaskLocation) {
|
|
965
1112
|
const error = stackTraceError.stack;
|
|
@@ -981,11 +1128,11 @@ function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions
|
|
|
981
1128
|
options = Object.assign({}, suiteOptions, options);
|
|
982
1129
|
}
|
|
983
1130
|
// inherit concurrent / sequential from suite
|
|
984
|
-
const concurrent = this.concurrent ?? (!this.sequential &&
|
|
1131
|
+
const concurrent = this.concurrent ?? (!this.sequential && options?.concurrent);
|
|
985
1132
|
if (options.concurrent != null && concurrent != null) {
|
|
986
1133
|
options.concurrent = concurrent;
|
|
987
1134
|
}
|
|
988
|
-
const sequential = this.sequential ?? (!this.concurrent &&
|
|
1135
|
+
const sequential = this.sequential ?? (!this.concurrent && options?.sequential);
|
|
989
1136
|
if (options.sequential != null && sequential != null) {
|
|
990
1137
|
options.sequential = sequential;
|
|
991
1138
|
}
|
|
@@ -996,7 +1143,6 @@ function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions
|
|
|
996
1143
|
});
|
|
997
1144
|
test.type = "test";
|
|
998
1145
|
});
|
|
999
|
-
let collectorFixtures = parentCollectorFixtures;
|
|
1000
1146
|
const collector = {
|
|
1001
1147
|
type: "collector",
|
|
1002
1148
|
name,
|
|
@@ -1004,48 +1150,39 @@ function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions
|
|
|
1004
1150
|
suite,
|
|
1005
1151
|
options: suiteOptions,
|
|
1006
1152
|
test,
|
|
1153
|
+
file: suite.file,
|
|
1007
1154
|
tasks,
|
|
1008
1155
|
collect,
|
|
1009
1156
|
task,
|
|
1010
1157
|
clear,
|
|
1011
|
-
on: addHook
|
|
1012
|
-
fixtures() {
|
|
1013
|
-
return collectorFixtures;
|
|
1014
|
-
},
|
|
1015
|
-
scoped(fixtures) {
|
|
1016
|
-
const parsed = mergeContextFixtures(fixtures, { fixtures: collectorFixtures }, runner);
|
|
1017
|
-
if (parsed.fixtures) {
|
|
1018
|
-
collectorFixtures = parsed.fixtures;
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1158
|
+
on: addHook
|
|
1021
1159
|
};
|
|
1022
1160
|
function addHook(name, ...fn) {
|
|
1023
1161
|
getHooks(suite)[name].push(...fn);
|
|
1024
1162
|
}
|
|
1025
1163
|
function initSuite(includeLocation) {
|
|
1026
|
-
var _collectorContext$cur5, _collectorContext$cur6, _collectorContext$cur7, _collectorContext$cur8;
|
|
1027
1164
|
if (typeof suiteOptions === "number") {
|
|
1028
1165
|
suiteOptions = { timeout: suiteOptions };
|
|
1029
1166
|
}
|
|
1030
|
-
const currentSuite =
|
|
1031
|
-
const parentTask = currentSuite ??
|
|
1032
|
-
const suiteTags = toArray(suiteOptions
|
|
1167
|
+
const currentSuite = collectorContext.currentSuite?.suite;
|
|
1168
|
+
const parentTask = currentSuite ?? collectorContext.currentSuite?.file;
|
|
1169
|
+
const suiteTags = toArray(suiteOptions?.tags);
|
|
1033
1170
|
validateTags(runner.config, suiteTags);
|
|
1034
1171
|
suite = {
|
|
1035
1172
|
id: "",
|
|
1036
1173
|
type: "suite",
|
|
1037
1174
|
name,
|
|
1038
|
-
fullName: createTaskName([
|
|
1039
|
-
fullTestName: createTaskName([currentSuite
|
|
1175
|
+
fullName: createTaskName([currentSuite?.fullName ?? collectorContext.currentSuite?.file?.fullName, name]),
|
|
1176
|
+
fullTestName: createTaskName([currentSuite?.fullTestName, name]),
|
|
1040
1177
|
suite: currentSuite,
|
|
1041
1178
|
mode,
|
|
1042
1179
|
each,
|
|
1043
|
-
file:
|
|
1044
|
-
shuffle: suiteOptions
|
|
1180
|
+
file: currentSuite?.file ?? collectorContext.currentSuite?.file,
|
|
1181
|
+
shuffle: suiteOptions?.shuffle,
|
|
1045
1182
|
tasks: [],
|
|
1046
|
-
meta:
|
|
1047
|
-
concurrent: suiteOptions
|
|
1048
|
-
tags: unique([...
|
|
1183
|
+
meta: suiteOptions?.meta ?? Object.create(null),
|
|
1184
|
+
concurrent: suiteOptions?.concurrent,
|
|
1185
|
+
tags: unique([...parentTask?.tags || [], ...suiteTags])
|
|
1049
1186
|
};
|
|
1050
1187
|
if (runner && includeLocation && runner.config.includeTaskLocation) {
|
|
1051
1188
|
const limit = Error.stackTraceLimit;
|
|
@@ -1099,7 +1236,6 @@ function withAwaitAsyncAssertions(fn, task) {
|
|
|
1099
1236
|
}
|
|
1100
1237
|
function createSuite() {
|
|
1101
1238
|
function suiteFn(name, factoryOrOptions, optionsOrFactory) {
|
|
1102
|
-
var _currentSuite$options;
|
|
1103
1239
|
if (getCurrentTest()) {
|
|
1104
1240
|
throw new Error("Calling the suite function inside test function is not allowed. It can be only called at the top level or inside another suite function.");
|
|
1105
1241
|
}
|
|
@@ -1107,13 +1243,13 @@ function createSuite() {
|
|
|
1107
1243
|
let { options, handler: factory } = parseArguments(factoryOrOptions, optionsOrFactory);
|
|
1108
1244
|
const isConcurrentSpecified = options.concurrent || this.concurrent || options.sequential === false;
|
|
1109
1245
|
const isSequentialSpecified = options.sequential || this.sequential || options.concurrent === false;
|
|
1110
|
-
const { meta: parentMeta, ...parentOptions } =
|
|
1246
|
+
const { meta: parentMeta, ...parentOptions } = currentSuite?.options || {};
|
|
1111
1247
|
// inherit options from current suite
|
|
1112
1248
|
options = {
|
|
1113
1249
|
...parentOptions,
|
|
1114
1250
|
...options
|
|
1115
1251
|
};
|
|
1116
|
-
const shuffle = this.shuffle ?? options.shuffle ??
|
|
1252
|
+
const shuffle = this.shuffle ?? options.shuffle ?? currentSuite?.options?.shuffle ?? runner?.config.sequence.shuffle;
|
|
1117
1253
|
if (shuffle != null) {
|
|
1118
1254
|
options.shuffle = shuffle;
|
|
1119
1255
|
}
|
|
@@ -1134,11 +1270,12 @@ function createSuite() {
|
|
|
1134
1270
|
if (parentMeta) {
|
|
1135
1271
|
options.meta = Object.assign(Object.create(null), parentMeta, options.meta);
|
|
1136
1272
|
}
|
|
1137
|
-
return createSuiteCollector(formatName(name), factory, mode, this.each, options
|
|
1273
|
+
return createSuiteCollector(formatName(name), factory, mode, this.each, options);
|
|
1138
1274
|
}
|
|
1139
1275
|
suiteFn.each = function(cases, ...args) {
|
|
1140
|
-
const
|
|
1141
|
-
|
|
1276
|
+
const context = getChainableContext(this);
|
|
1277
|
+
const suite = context.withContext();
|
|
1278
|
+
context.setContext("each", true);
|
|
1142
1279
|
if (Array.isArray(cases) && args.length) {
|
|
1143
1280
|
cases = formatTemplateString(cases, args);
|
|
1144
1281
|
}
|
|
@@ -1163,7 +1300,7 @@ function createSuite() {
|
|
|
1163
1300
|
}
|
|
1164
1301
|
}
|
|
1165
1302
|
});
|
|
1166
|
-
|
|
1303
|
+
context.setContext("each", undefined);
|
|
1167
1304
|
};
|
|
1168
1305
|
};
|
|
1169
1306
|
suiteFn.for = function(cases, ...args) {
|
|
@@ -1189,11 +1326,12 @@ function createSuite() {
|
|
|
1189
1326
|
"todo"
|
|
1190
1327
|
], suiteFn);
|
|
1191
1328
|
}
|
|
1192
|
-
function createTaskCollector(fn
|
|
1329
|
+
function createTaskCollector(fn) {
|
|
1193
1330
|
const taskFn = fn;
|
|
1194
1331
|
taskFn.each = function(cases, ...args) {
|
|
1195
|
-
const
|
|
1196
|
-
|
|
1332
|
+
const context = getChainableContext(this);
|
|
1333
|
+
const test = context.withContext();
|
|
1334
|
+
context.setContext("each", true);
|
|
1197
1335
|
if (Array.isArray(cases) && args.length) {
|
|
1198
1336
|
cases = formatTemplateString(cases, args);
|
|
1199
1337
|
}
|
|
@@ -1218,11 +1356,12 @@ function createTaskCollector(fn, context) {
|
|
|
1218
1356
|
}
|
|
1219
1357
|
}
|
|
1220
1358
|
});
|
|
1221
|
-
|
|
1359
|
+
context.setContext("each", undefined);
|
|
1222
1360
|
};
|
|
1223
1361
|
};
|
|
1224
1362
|
taskFn.for = function(cases, ...args) {
|
|
1225
|
-
const
|
|
1363
|
+
const context = getChainableContext(this);
|
|
1364
|
+
const test = context.withContext();
|
|
1226
1365
|
if (Array.isArray(cases) && args.length) {
|
|
1227
1366
|
cases = formatTemplateString(cases, args);
|
|
1228
1367
|
}
|
|
@@ -1233,8 +1372,10 @@ function createTaskCollector(fn, context) {
|
|
|
1233
1372
|
// monkey-patch handler to allow parsing fixture
|
|
1234
1373
|
const handlerWrapper = handler ? (ctx) => handler(item, ctx) : undefined;
|
|
1235
1374
|
if (handlerWrapper) {
|
|
1236
|
-
handlerWrapper
|
|
1237
|
-
|
|
1375
|
+
configureProps(handlerWrapper, {
|
|
1376
|
+
index: 1,
|
|
1377
|
+
original: handler
|
|
1378
|
+
});
|
|
1238
1379
|
}
|
|
1239
1380
|
test(formatTitle(_name, toArray(item), idx), options, handlerWrapper);
|
|
1240
1381
|
});
|
|
@@ -1246,24 +1387,85 @@ function createTaskCollector(fn, context) {
|
|
|
1246
1387
|
taskFn.runIf = function(condition) {
|
|
1247
1388
|
return condition ? this : this.skip;
|
|
1248
1389
|
};
|
|
1390
|
+
/**
|
|
1391
|
+
* Parse builder pattern arguments into a fixtures object.
|
|
1392
|
+
* Handles both builder pattern (name, options?, value) and object syntax.
|
|
1393
|
+
*/
|
|
1394
|
+
function parseBuilderFixtures(fixturesOrName, optionsOrFn, maybeFn) {
|
|
1395
|
+
// Object syntax: just return as-is
|
|
1396
|
+
if (typeof fixturesOrName !== "string") {
|
|
1397
|
+
return fixturesOrName;
|
|
1398
|
+
}
|
|
1399
|
+
const fixtureName = fixturesOrName;
|
|
1400
|
+
let fixtureOptions;
|
|
1401
|
+
let fixtureValue;
|
|
1402
|
+
if (maybeFn !== undefined) {
|
|
1403
|
+
// (name, options, value) or (name, options, fn)
|
|
1404
|
+
fixtureOptions = optionsOrFn;
|
|
1405
|
+
fixtureValue = maybeFn;
|
|
1406
|
+
} else {
|
|
1407
|
+
// (name, value) or (name, fn)
|
|
1408
|
+
// Check if optionsOrFn looks like fixture options (has scope or auto)
|
|
1409
|
+
if (optionsOrFn !== null && typeof optionsOrFn === "object" && !Array.isArray(optionsOrFn) && ("scope" in optionsOrFn || "auto" in optionsOrFn)) {
|
|
1410
|
+
// (name, options) with no value - treat as empty object fixture
|
|
1411
|
+
fixtureOptions = optionsOrFn;
|
|
1412
|
+
fixtureValue = {};
|
|
1413
|
+
} else {
|
|
1414
|
+
// (name, value) or (name, fn)
|
|
1415
|
+
fixtureOptions = undefined;
|
|
1416
|
+
fixtureValue = optionsOrFn;
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
// Function value: wrap with onCleanup pattern
|
|
1420
|
+
if (typeof fixtureValue === "function") {
|
|
1421
|
+
const builderFn = fixtureValue;
|
|
1422
|
+
// Wrap builder pattern function (returns value) to use() pattern
|
|
1423
|
+
const fixture = async (ctx, use) => {
|
|
1424
|
+
let cleanup;
|
|
1425
|
+
const onCleanup = (fn) => {
|
|
1426
|
+
if (cleanup !== undefined) {
|
|
1427
|
+
throw new Error(`onCleanup can only be called once per fixture. ` + `Define separate fixtures if you need multiple cleanup functions.`);
|
|
1428
|
+
}
|
|
1429
|
+
cleanup = fn;
|
|
1430
|
+
};
|
|
1431
|
+
const value = await builderFn(ctx, { onCleanup });
|
|
1432
|
+
await use(value);
|
|
1433
|
+
if (cleanup) {
|
|
1434
|
+
await cleanup();
|
|
1435
|
+
}
|
|
1436
|
+
};
|
|
1437
|
+
configureProps(fixture, { original: builderFn });
|
|
1438
|
+
if (fixtureOptions) {
|
|
1439
|
+
return { [fixtureName]: [fixture, fixtureOptions] };
|
|
1440
|
+
}
|
|
1441
|
+
return { [fixtureName]: fixture };
|
|
1442
|
+
}
|
|
1443
|
+
// Non-function value: use directly
|
|
1444
|
+
if (fixtureOptions) {
|
|
1445
|
+
return { [fixtureName]: [fixtureValue, fixtureOptions] };
|
|
1446
|
+
}
|
|
1447
|
+
return { [fixtureName]: fixtureValue };
|
|
1448
|
+
}
|
|
1449
|
+
taskFn.override = function(fixturesOrName, optionsOrFn, maybeFn) {
|
|
1450
|
+
const userFixtures = parseBuilderFixtures(fixturesOrName, optionsOrFn, maybeFn);
|
|
1451
|
+
getChainableContext(this).getFixtures().override(runner, userFixtures);
|
|
1452
|
+
return this;
|
|
1453
|
+
};
|
|
1249
1454
|
taskFn.scoped = function(fixtures) {
|
|
1250
|
-
|
|
1251
|
-
|
|
1455
|
+
console.warn(`test.scoped() is deprecated and will be removed in future versions. Please use test.override() instead.`);
|
|
1456
|
+
return this.override(fixtures);
|
|
1252
1457
|
};
|
|
1253
|
-
taskFn.extend = function(
|
|
1254
|
-
const
|
|
1255
|
-
const
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
context.fixtures = mergeScopedFixtures(context.fixtures || [], scopedFixtures);
|
|
1262
|
-
}
|
|
1263
|
-
originalWrapper.call(context, formatName(name), optionsOrFn, optionsOrTest);
|
|
1264
|
-
}, _context);
|
|
1458
|
+
taskFn.extend = function(fixturesOrName, optionsOrFn, maybeFn) {
|
|
1459
|
+
const userFixtures = parseBuilderFixtures(fixturesOrName, optionsOrFn, maybeFn);
|
|
1460
|
+
const fixtures = getChainableContext(this).getFixtures().extend(runner, userFixtures);
|
|
1461
|
+
const _test = createTest(function(name, optionsOrFn, optionsOrTest) {
|
|
1462
|
+
fn.call(this, formatName(name), optionsOrFn, optionsOrTest);
|
|
1463
|
+
});
|
|
1464
|
+
getChainableContext(_test).mergeContext({ fixtures });
|
|
1465
|
+
return _test;
|
|
1265
1466
|
};
|
|
1266
1467
|
taskFn.describe = suite;
|
|
1468
|
+
taskFn.suite = suite;
|
|
1267
1469
|
taskFn.beforeEach = beforeEach;
|
|
1268
1470
|
taskFn.afterEach = afterEach;
|
|
1269
1471
|
taskFn.beforeAll = beforeAll;
|
|
@@ -1277,14 +1479,11 @@ function createTaskCollector(fn, context) {
|
|
|
1277
1479
|
"only",
|
|
1278
1480
|
"todo",
|
|
1279
1481
|
"fails"
|
|
1280
|
-
], taskFn);
|
|
1281
|
-
if (context) {
|
|
1282
|
-
_test.mergeContext(context);
|
|
1283
|
-
}
|
|
1482
|
+
], taskFn, { fixtures: new TestFixtures() });
|
|
1284
1483
|
return _test;
|
|
1285
1484
|
}
|
|
1286
|
-
function createTest(fn
|
|
1287
|
-
return createTaskCollector(fn
|
|
1485
|
+
function createTest(fn) {
|
|
1486
|
+
return createTaskCollector(fn);
|
|
1288
1487
|
}
|
|
1289
1488
|
function formatName(name) {
|
|
1290
1489
|
return typeof name === "string" ? name : typeof name === "function" ? name.name || "<anonymous>" : String(name);
|
|
@@ -1311,14 +1510,13 @@ function formatTitle(template, items, idx) {
|
|
|
1311
1510
|
const isObjectItem = isObject(items[0]);
|
|
1312
1511
|
function formatAttribute(s) {
|
|
1313
1512
|
return s.replace(/\$([$\w.]+)/g, (_, key) => {
|
|
1314
|
-
var _runner$config;
|
|
1315
1513
|
const isArrayKey = /^\d+$/.test(key);
|
|
1316
1514
|
if (!isObjectItem && !isArrayKey) {
|
|
1317
1515
|
return `$${key}`;
|
|
1318
1516
|
}
|
|
1319
1517
|
const arrayElement = isArrayKey ? objectAttr(items, key) : undefined;
|
|
1320
1518
|
const value = isObjectItem ? objectAttr(items[0], key, arrayElement) : arrayElement;
|
|
1321
|
-
return objDisplay(value, { truncate: runner
|
|
1519
|
+
return objDisplay(value, { truncate: runner?.config?.chaiConfig?.truncateThreshold });
|
|
1322
1520
|
});
|
|
1323
1521
|
}
|
|
1324
1522
|
let output = "";
|
|
@@ -1374,8 +1572,7 @@ const collectorContext = {
|
|
|
1374
1572
|
currentSuite: null
|
|
1375
1573
|
};
|
|
1376
1574
|
function collectTask(task) {
|
|
1377
|
-
|
|
1378
|
-
(_collectorContext$cur = collectorContext.currentSuite) === null || _collectorContext$cur === void 0 ? void 0 : _collectorContext$cur.tasks.push(task);
|
|
1575
|
+
collectorContext.currentSuite?.tasks.push(task);
|
|
1379
1576
|
}
|
|
1380
1577
|
async function runWithSuite(suite, fn) {
|
|
1381
1578
|
const prev = collectorContext.currentSuite;
|
|
@@ -1395,16 +1592,15 @@ function withTimeout(fn, timeout, isHook = false, stackTraceError, onTimeout) {
|
|
|
1395
1592
|
runner._currentTaskStartTime = startTime;
|
|
1396
1593
|
runner._currentTaskTimeout = timeout;
|
|
1397
1594
|
return new Promise((resolve_, reject_) => {
|
|
1398
|
-
var _timer$unref;
|
|
1399
1595
|
const timer = setTimeout(() => {
|
|
1400
1596
|
clearTimeout(timer);
|
|
1401
1597
|
rejectTimeoutError();
|
|
1402
1598
|
}, timeout);
|
|
1403
1599
|
// `unref` might not exist in browser
|
|
1404
|
-
|
|
1600
|
+
timer.unref?.();
|
|
1405
1601
|
function rejectTimeoutError() {
|
|
1406
1602
|
const error = makeTimeoutError(isHook, timeout, stackTraceError);
|
|
1407
|
-
onTimeout
|
|
1603
|
+
onTimeout?.(args, error);
|
|
1408
1604
|
reject_(error);
|
|
1409
1605
|
}
|
|
1410
1606
|
function resolve(result) {
|
|
@@ -1444,6 +1640,23 @@ catch (error) {
|
|
|
1444
1640
|
});
|
|
1445
1641
|
});
|
|
1446
1642
|
}
|
|
1643
|
+
function withCancel(fn, signal) {
|
|
1644
|
+
return (function runWithCancel(...args) {
|
|
1645
|
+
return new Promise((resolve, reject) => {
|
|
1646
|
+
signal.addEventListener("abort", () => reject(signal.reason));
|
|
1647
|
+
try {
|
|
1648
|
+
const result = fn(...args);
|
|
1649
|
+
if (typeof result === "object" && result != null && typeof result.then === "function") {
|
|
1650
|
+
result.then(resolve, reject);
|
|
1651
|
+
} else {
|
|
1652
|
+
resolve(result);
|
|
1653
|
+
}
|
|
1654
|
+
} catch (error) {
|
|
1655
|
+
reject(error);
|
|
1656
|
+
}
|
|
1657
|
+
});
|
|
1658
|
+
});
|
|
1659
|
+
}
|
|
1447
1660
|
const abortControllers = new WeakMap();
|
|
1448
1661
|
function abortIfTimeout([context], error) {
|
|
1449
1662
|
if (context) {
|
|
@@ -1452,10 +1665,9 @@ function abortIfTimeout([context], error) {
|
|
|
1452
1665
|
}
|
|
1453
1666
|
function abortContextSignal(context, error) {
|
|
1454
1667
|
const abortController = abortControllers.get(context);
|
|
1455
|
-
abortController
|
|
1668
|
+
abortController?.abort(error);
|
|
1456
1669
|
}
|
|
1457
1670
|
function createTestContext(test, runner) {
|
|
1458
|
-
var _runner$extendTaskCon;
|
|
1459
1671
|
const context = function() {
|
|
1460
1672
|
throw new Error("done() callback is deprecated, use promise instead");
|
|
1461
1673
|
};
|
|
@@ -1471,7 +1683,7 @@ function createTestContext(test, runner) {
|
|
|
1471
1683
|
// do nothing
|
|
1472
1684
|
return undefined;
|
|
1473
1685
|
}
|
|
1474
|
-
test.result
|
|
1686
|
+
test.result ??= { state: "skip" };
|
|
1475
1687
|
test.result.pending = true;
|
|
1476
1688
|
throw new PendingError("test is skipped; abort execution", test, typeof condition === "string" ? condition : note);
|
|
1477
1689
|
};
|
|
@@ -1502,34 +1714,23 @@ function createTestContext(test, runner) {
|
|
|
1502
1714
|
}));
|
|
1503
1715
|
});
|
|
1504
1716
|
context.onTestFailed = (handler, timeout) => {
|
|
1505
|
-
test.onFailed
|
|
1717
|
+
test.onFailed ||= [];
|
|
1506
1718
|
test.onFailed.push(withTimeout(handler, timeout ?? runner.config.hookTimeout, true, new Error("STACK_TRACE_ERROR"), (_, error) => abortController.abort(error)));
|
|
1507
1719
|
};
|
|
1508
1720
|
context.onTestFinished = (handler, timeout) => {
|
|
1509
|
-
test.onFinished
|
|
1721
|
+
test.onFinished ||= [];
|
|
1510
1722
|
test.onFinished.push(withTimeout(handler, timeout ?? runner.config.hookTimeout, true, new Error("STACK_TRACE_ERROR"), (_, error) => abortController.abort(error)));
|
|
1511
1723
|
};
|
|
1512
|
-
return
|
|
1724
|
+
return runner.extendTaskContext?.(context) || context;
|
|
1513
1725
|
}
|
|
1514
1726
|
function makeTimeoutError(isHook, timeout, stackTraceError) {
|
|
1515
1727
|
const message = `${isHook ? "Hook" : "Test"} timed out in ${timeout}ms.\nIf this is a long-running ${isHook ? "hook" : "test"}, pass a timeout value as the last argument or configure it globally with "${isHook ? "hookTimeout" : "testTimeout"}".`;
|
|
1516
1728
|
const error = new Error(message);
|
|
1517
|
-
if (stackTraceError
|
|
1729
|
+
if (stackTraceError?.stack) {
|
|
1518
1730
|
error.stack = stackTraceError.stack.replace(error.message, stackTraceError.message);
|
|
1519
1731
|
}
|
|
1520
1732
|
return error;
|
|
1521
1733
|
}
|
|
1522
|
-
const fileContexts = new WeakMap();
|
|
1523
|
-
function getFileContext(file) {
|
|
1524
|
-
const context = fileContexts.get(file);
|
|
1525
|
-
if (!context) {
|
|
1526
|
-
throw new Error(`Cannot find file context for ${file.name}`);
|
|
1527
|
-
}
|
|
1528
|
-
return context;
|
|
1529
|
-
}
|
|
1530
|
-
function setFileContext(file, context) {
|
|
1531
|
-
fileContexts.set(file, context);
|
|
1532
|
-
}
|
|
1533
1734
|
|
|
1534
1735
|
async function runSetupFiles(config, files, runner) {
|
|
1535
1736
|
if (config.sequence.setupFiles === "parallel") {
|
|
@@ -1558,13 +1759,11 @@ async function collectTests(specs, runner) {
|
|
|
1558
1759
|
const testTagsFilter = typeof spec === "object" && spec.testTagsFilter ? createTagsFilter(spec.testTagsFilter, config.tags) : undefined;
|
|
1559
1760
|
const fileTags = typeof spec === "string" ? [] : spec.fileTags || [];
|
|
1560
1761
|
const file = createFileTask(filepath, config.root, config.name, runner.pool, runner.viteEnvironment);
|
|
1561
|
-
setFileContext(file, Object.create(null));
|
|
1562
1762
|
file.tags = fileTags;
|
|
1563
1763
|
file.shuffle = config.sequence.shuffle;
|
|
1564
1764
|
try {
|
|
1565
|
-
var _runner$onCollectStar, _runner$getImportDura;
|
|
1566
1765
|
validateTags(runner.config, fileTags);
|
|
1567
|
-
|
|
1766
|
+
runner.onCollectStart?.(file);
|
|
1568
1767
|
clearCollectorContext(file, runner);
|
|
1569
1768
|
const setupFiles = toArray(config.setupFiles);
|
|
1570
1769
|
if (setupFiles.length) {
|
|
@@ -1577,7 +1776,7 @@ async function collectTests(specs, runner) {
|
|
|
1577
1776
|
}
|
|
1578
1777
|
const collectStart = now$1();
|
|
1579
1778
|
await runner.importFile(filepath, "collect");
|
|
1580
|
-
const durations =
|
|
1779
|
+
const durations = runner.getImportDurations?.();
|
|
1581
1780
|
if (durations) {
|
|
1582
1781
|
file.importDurations = durations;
|
|
1583
1782
|
}
|
|
@@ -1601,13 +1800,12 @@ async function collectTests(specs, runner) {
|
|
|
1601
1800
|
setHooks(file, fileHooks);
|
|
1602
1801
|
file.collectDuration = now$1() - collectStart;
|
|
1603
1802
|
} catch (e) {
|
|
1604
|
-
|
|
1605
|
-
const error = processError(e);
|
|
1803
|
+
const errors = e instanceof AggregateError ? e.errors.map((e) => processError(e, runner.config.diffOptions)) : [processError(e, runner.config.diffOptions)];
|
|
1606
1804
|
file.result = {
|
|
1607
1805
|
state: "fail",
|
|
1608
|
-
errors
|
|
1806
|
+
errors
|
|
1609
1807
|
};
|
|
1610
|
-
const durations =
|
|
1808
|
+
const durations = runner.getImportDurations?.();
|
|
1611
1809
|
if (durations) {
|
|
1612
1810
|
file.importDurations = durations;
|
|
1613
1811
|
}
|
|
@@ -1637,6 +1835,7 @@ function mergeHooks(baseHooks, hooks) {
|
|
|
1637
1835
|
const now = globalThis.performance ? globalThis.performance.now.bind(globalThis.performance) : Date.now;
|
|
1638
1836
|
const unixNow = Date.now;
|
|
1639
1837
|
const { clearTimeout, setTimeout } = getSafeTimers();
|
|
1838
|
+
let limitMaxConcurrency;
|
|
1640
1839
|
/**
|
|
1641
1840
|
* Normalizes retry configuration to extract individual values.
|
|
1642
1841
|
* Handles both number and object forms.
|
|
@@ -1710,14 +1909,14 @@ async function callTestHooks(runner, test, hooks, sequence) {
|
|
|
1710
1909
|
};
|
|
1711
1910
|
if (sequence === "parallel") {
|
|
1712
1911
|
try {
|
|
1713
|
-
await Promise.all(hooks.map((fn) => fn(test.context)));
|
|
1912
|
+
await Promise.all(hooks.map((fn) => limitMaxConcurrency(() => fn(test.context))));
|
|
1714
1913
|
} catch (e) {
|
|
1715
1914
|
failTask(test.result, e, runner.config.diffOptions);
|
|
1716
1915
|
}
|
|
1717
1916
|
} else {
|
|
1718
1917
|
for (const fn of hooks) {
|
|
1719
1918
|
try {
|
|
1720
|
-
await fn(test.context);
|
|
1919
|
+
await limitMaxConcurrency(() => fn(test.context));
|
|
1721
1920
|
} catch (e) {
|
|
1722
1921
|
failTask(test.result, e, runner.config.diffOptions);
|
|
1723
1922
|
}
|
|
@@ -1739,7 +1938,9 @@ async function callSuiteHook(suite, currentTask, name, runner, args) {
|
|
|
1739
1938
|
updateSuiteHookState(currentTask, name, "run", runner);
|
|
1740
1939
|
}
|
|
1741
1940
|
async function runHook(hook) {
|
|
1742
|
-
return
|
|
1941
|
+
return limitMaxConcurrency(async () => {
|
|
1942
|
+
return getBeforeHookCleanupCallback(hook, await hook(...args), name === "beforeEach" ? args[0] : undefined);
|
|
1943
|
+
});
|
|
1743
1944
|
}
|
|
1744
1945
|
if (sequence === "parallel") {
|
|
1745
1946
|
callbacks.push(...await Promise.all(hooks.map((hook) => runHook(hook))));
|
|
@@ -1772,7 +1973,7 @@ function makeAroundHookTimeoutError(hookName, phase, timeout, stackTraceError) {
|
|
|
1772
1973
|
const message = `The ${phase} phase of "${hookName}" hook timed out after ${timeout}ms.`;
|
|
1773
1974
|
const ErrorClass = phase === "setup" ? AroundHookSetupError : AroundHookTeardownError;
|
|
1774
1975
|
const error = new ErrorClass(message);
|
|
1775
|
-
if (stackTraceError
|
|
1976
|
+
if (stackTraceError?.stack) {
|
|
1776
1977
|
error.stack = stackTraceError.stack.replace(stackTraceError.message, error.message);
|
|
1777
1978
|
}
|
|
1778
1979
|
return error;
|
|
@@ -1783,17 +1984,19 @@ async function callAroundHooks(runInner, options) {
|
|
|
1783
1984
|
await runInner();
|
|
1784
1985
|
return;
|
|
1785
1986
|
}
|
|
1987
|
+
const hookErrors = [];
|
|
1786
1988
|
const createTimeoutPromise = (timeout, phase, stackTraceError) => {
|
|
1787
1989
|
let timer;
|
|
1990
|
+
let timedout = false;
|
|
1788
1991
|
const promise = new Promise((_, reject) => {
|
|
1789
1992
|
if (timeout > 0 && timeout !== Number.POSITIVE_INFINITY) {
|
|
1790
|
-
var _timer$unref;
|
|
1791
1993
|
timer = setTimeout(() => {
|
|
1994
|
+
timedout = true;
|
|
1792
1995
|
const error = makeAroundHookTimeoutError(hookName, phase, timeout, stackTraceError);
|
|
1793
|
-
onTimeout
|
|
1996
|
+
onTimeout?.(error);
|
|
1794
1997
|
reject(error);
|
|
1795
1998
|
}, timeout);
|
|
1796
|
-
|
|
1999
|
+
timer.unref?.();
|
|
1797
2000
|
}
|
|
1798
2001
|
});
|
|
1799
2002
|
const clear = () => {
|
|
@@ -1804,7 +2007,8 @@ async function callAroundHooks(runInner, options) {
|
|
|
1804
2007
|
};
|
|
1805
2008
|
return {
|
|
1806
2009
|
promise,
|
|
1807
|
-
clear
|
|
2010
|
+
clear,
|
|
2011
|
+
isTimedOut: () => timedout
|
|
1808
2012
|
};
|
|
1809
2013
|
};
|
|
1810
2014
|
const runNextHook = async (index) => {
|
|
@@ -1817,6 +2021,8 @@ async function callAroundHooks(runInner, options) {
|
|
|
1817
2021
|
let useCalled = false;
|
|
1818
2022
|
let setupTimeout;
|
|
1819
2023
|
let teardownTimeout;
|
|
2024
|
+
let setupLimitConcurrencyRelease;
|
|
2025
|
+
let teardownLimitConcurrencyRelease;
|
|
1820
2026
|
// Promise that resolves when use() is called (setup phase complete)
|
|
1821
2027
|
let resolveUseCalled;
|
|
1822
2028
|
const useCalledPromise = new Promise((resolve) => {
|
|
@@ -1835,6 +2041,13 @@ async function callAroundHooks(runInner, options) {
|
|
|
1835
2041
|
rejectHookComplete = reject;
|
|
1836
2042
|
});
|
|
1837
2043
|
const use = async () => {
|
|
2044
|
+
// shouldn't continue to next (runTest/Suite or inner aroundEach/All) when aroundEach/All setup timed out.
|
|
2045
|
+
if (setupTimeout.isTimedOut()) {
|
|
2046
|
+
// we can throw any error to bail out.
|
|
2047
|
+
// this error is not seen by end users since `runNextHook` already rejected with timeout error
|
|
2048
|
+
// and this error is caught by `rejectHookComplete`.
|
|
2049
|
+
throw new Error("__VITEST_INTERNAL_AROUND_HOOK_ABORT__");
|
|
2050
|
+
}
|
|
1838
2051
|
if (useCalled) {
|
|
1839
2052
|
throw new AroundHookMultipleCallsError(`The \`${callbackName}\` callback was called multiple times in the \`${hookName}\` hook. ` + `The callback can only be called once per hook.`);
|
|
1840
2053
|
}
|
|
@@ -1842,13 +2055,16 @@ async function callAroundHooks(runInner, options) {
|
|
|
1842
2055
|
resolveUseCalled();
|
|
1843
2056
|
// Setup phase completed - clear setup timer
|
|
1844
2057
|
setupTimeout.clear();
|
|
2058
|
+
setupLimitConcurrencyRelease?.();
|
|
1845
2059
|
// Run inner hooks - don't time this against our teardown timeout
|
|
1846
|
-
await runNextHook(index + 1);
|
|
2060
|
+
await runNextHook(index + 1).catch((e) => hookErrors.push(e));
|
|
2061
|
+
teardownLimitConcurrencyRelease = await limitMaxConcurrency.acquire();
|
|
1847
2062
|
// Start teardown timer after inner hooks complete - only times this hook's teardown code
|
|
1848
2063
|
teardownTimeout = createTimeoutPromise(timeout, "teardown", stackTraceError);
|
|
1849
2064
|
// Signal that use() is returning (teardown phase starting)
|
|
1850
2065
|
resolveUseReturned();
|
|
1851
2066
|
};
|
|
2067
|
+
setupLimitConcurrencyRelease = await limitMaxConcurrency.acquire();
|
|
1852
2068
|
// Start setup timeout
|
|
1853
2069
|
setupTimeout = createTimeoutPromise(timeout, "setup", stackTraceError);
|
|
1854
2070
|
(async () => {
|
|
@@ -1860,6 +2076,9 @@ async function callAroundHooks(runInner, options) {
|
|
|
1860
2076
|
resolveHookComplete();
|
|
1861
2077
|
} catch (error) {
|
|
1862
2078
|
rejectHookComplete(error);
|
|
2079
|
+
} finally {
|
|
2080
|
+
setupLimitConcurrencyRelease?.();
|
|
2081
|
+
teardownLimitConcurrencyRelease?.();
|
|
1863
2082
|
}
|
|
1864
2083
|
})();
|
|
1865
2084
|
// Wait for either: use() to be called OR hook to complete (error) OR setup timeout
|
|
@@ -1870,6 +2089,7 @@ async function callAroundHooks(runInner, options) {
|
|
|
1870
2089
|
setupTimeout.promise
|
|
1871
2090
|
]);
|
|
1872
2091
|
} finally {
|
|
2092
|
+
setupLimitConcurrencyRelease?.();
|
|
1873
2093
|
setupTimeout.clear();
|
|
1874
2094
|
}
|
|
1875
2095
|
// Wait for use() to return (inner hooks complete) OR hook to complete (error during inner hooks)
|
|
@@ -1877,12 +2097,16 @@ async function callAroundHooks(runInner, options) {
|
|
|
1877
2097
|
// Now teardownTimeout is guaranteed to be set
|
|
1878
2098
|
// Wait for hook to complete (teardown) OR teardown timeout
|
|
1879
2099
|
try {
|
|
1880
|
-
await Promise.race([hookCompletePromise, teardownTimeout
|
|
2100
|
+
await Promise.race([hookCompletePromise, teardownTimeout?.promise]);
|
|
1881
2101
|
} finally {
|
|
1882
|
-
|
|
2102
|
+
teardownLimitConcurrencyRelease?.();
|
|
2103
|
+
teardownTimeout?.clear();
|
|
1883
2104
|
}
|
|
1884
2105
|
};
|
|
1885
|
-
await runNextHook(0);
|
|
2106
|
+
await runNextHook(0).catch((e) => hookErrors.push(e));
|
|
2107
|
+
if (hookErrors.length > 0) {
|
|
2108
|
+
throw hookErrors;
|
|
2109
|
+
}
|
|
1886
2110
|
}
|
|
1887
2111
|
async function callAroundAllHooks(suite, runSuiteInner) {
|
|
1888
2112
|
await callAroundHooks(runSuiteInner, {
|
|
@@ -1912,7 +2136,6 @@ const eventsPacks = [];
|
|
|
1912
2136
|
const pendingTasksUpdates = [];
|
|
1913
2137
|
function sendTasksUpdate(runner) {
|
|
1914
2138
|
if (packs.size) {
|
|
1915
|
-
var _runner$onTaskUpdate;
|
|
1916
2139
|
const taskPacks = Array.from(packs).map(([id, task]) => {
|
|
1917
2140
|
return [
|
|
1918
2141
|
id,
|
|
@@ -1920,7 +2143,7 @@ function sendTasksUpdate(runner) {
|
|
|
1920
2143
|
task[1]
|
|
1921
2144
|
];
|
|
1922
2145
|
});
|
|
1923
|
-
const p =
|
|
2146
|
+
const p = runner.onTaskUpdate?.(taskPacks, eventsPacks);
|
|
1924
2147
|
if (p) {
|
|
1925
2148
|
pendingTasksUpdates.push(p);
|
|
1926
2149
|
// remove successful promise to not grow array indefnitely,
|
|
@@ -1947,7 +2170,7 @@ function throttle(fn, ms) {
|
|
|
1947
2170
|
return fn.apply(this, args);
|
|
1948
2171
|
}
|
|
1949
2172
|
// Make sure fn is still called even if there are no further calls
|
|
1950
|
-
pendingCall
|
|
2173
|
+
pendingCall ??= setTimeout(() => call.bind(this)(...args), ms);
|
|
1951
2174
|
};
|
|
1952
2175
|
}
|
|
1953
2176
|
// throttle based on summary reporter's DURATION_UPDATE_INTERVAL_MS
|
|
@@ -1971,14 +2194,14 @@ async function callCleanupHooks(runner, cleanups) {
|
|
|
1971
2194
|
if (typeof fn !== "function") {
|
|
1972
2195
|
return;
|
|
1973
2196
|
}
|
|
1974
|
-
await fn();
|
|
2197
|
+
await limitMaxConcurrency(() => fn());
|
|
1975
2198
|
}));
|
|
1976
2199
|
} else {
|
|
1977
2200
|
for (const fn of cleanups) {
|
|
1978
2201
|
if (typeof fn !== "function") {
|
|
1979
2202
|
continue;
|
|
1980
2203
|
}
|
|
1981
|
-
await fn();
|
|
2204
|
+
await limitMaxConcurrency(() => fn());
|
|
1982
2205
|
}
|
|
1983
2206
|
}
|
|
1984
2207
|
}
|
|
@@ -2002,14 +2225,13 @@ function passesRetryCondition(test, errors) {
|
|
|
2002
2225
|
return false;
|
|
2003
2226
|
}
|
|
2004
2227
|
async function runTest(test, runner) {
|
|
2005
|
-
|
|
2006
|
-
await ((_runner$onBeforeRunTa = runner.onBeforeRunTask) === null || _runner$onBeforeRunTa === void 0 ? void 0 : _runner$onBeforeRunTa.call(runner, test));
|
|
2228
|
+
await runner.onBeforeRunTask?.(test);
|
|
2007
2229
|
if (test.mode !== "run" && test.mode !== "queued") {
|
|
2008
2230
|
updateTask("test-prepare", test, runner);
|
|
2009
2231
|
updateTask("test-finished", test, runner);
|
|
2010
2232
|
return;
|
|
2011
2233
|
}
|
|
2012
|
-
if (
|
|
2234
|
+
if (test.result?.state === "fail") {
|
|
2013
2235
|
// should not be possible to get here, I think this is just copy pasted from suite
|
|
2014
2236
|
// TODO: maybe someone fails tests in `beforeAll` hooks?
|
|
2015
2237
|
// https://github.com/vitest-dev/vitest/pull/7069
|
|
@@ -2031,48 +2253,40 @@ async function runTest(test, runner) {
|
|
|
2031
2253
|
for (let repeatCount = 0; repeatCount <= repeats; repeatCount++) {
|
|
2032
2254
|
const retry = getRetryCount(test.retry);
|
|
2033
2255
|
for (let retryCount = 0; retryCount <= retry; retryCount++) {
|
|
2034
|
-
var _test$result2, _test$result3;
|
|
2035
2256
|
let beforeEachCleanups = [];
|
|
2036
2257
|
// fixtureCheckpoint is passed by callAroundEachHooks - it represents the count
|
|
2037
2258
|
// of fixture cleanup functions AFTER all aroundEach fixtures have been resolved
|
|
2038
2259
|
// but BEFORE the test runs. This allows us to clean up only fixtures created
|
|
2039
2260
|
// inside runTest while preserving aroundEach fixtures for teardown.
|
|
2040
2261
|
await callAroundEachHooks(suite, test, async (fixtureCheckpoint) => {
|
|
2041
|
-
var _test$onFinished, _test$onFailed, _runner$onAfterRetryT;
|
|
2042
2262
|
try {
|
|
2043
|
-
|
|
2044
|
-
await ((_runner$onBeforeTryTa = runner.onBeforeTryTask) === null || _runner$onBeforeTryTa === void 0 ? void 0 : _runner$onBeforeTryTa.call(runner, test, {
|
|
2263
|
+
await runner.onBeforeTryTask?.(test, {
|
|
2045
2264
|
retry: retryCount,
|
|
2046
2265
|
repeats: repeatCount
|
|
2047
|
-
})
|
|
2266
|
+
});
|
|
2048
2267
|
test.result.repeatCount = repeatCount;
|
|
2049
2268
|
beforeEachCleanups = await $("test.beforeEach", () => callSuiteHook(suite, test, "beforeEach", runner, [test.context, suite]));
|
|
2050
2269
|
if (runner.runTask) {
|
|
2051
|
-
await $("test.callback", () => runner.runTask(test));
|
|
2270
|
+
await $("test.callback", () => limitMaxConcurrency(() => runner.runTask(test)));
|
|
2052
2271
|
} else {
|
|
2053
2272
|
const fn = getFn(test);
|
|
2054
2273
|
if (!fn) {
|
|
2055
2274
|
throw new Error("Test function is not found. Did you add it using `setFn`?");
|
|
2056
2275
|
}
|
|
2057
|
-
await $("test.callback", () => fn());
|
|
2276
|
+
await $("test.callback", () => limitMaxConcurrency(() => fn()));
|
|
2058
2277
|
}
|
|
2059
|
-
await
|
|
2278
|
+
await runner.onAfterTryTask?.(test, {
|
|
2060
2279
|
retry: retryCount,
|
|
2061
2280
|
repeats: repeatCount
|
|
2062
|
-
})
|
|
2281
|
+
});
|
|
2063
2282
|
if (test.result.state !== "fail") {
|
|
2064
|
-
|
|
2065
|
-
test.result.state = "pass";
|
|
2066
|
-
} else if (test.repeats && retry === retryCount) {
|
|
2067
|
-
test.result.state = "pass";
|
|
2068
|
-
}
|
|
2283
|
+
test.result.state = "pass";
|
|
2069
2284
|
}
|
|
2070
2285
|
} catch (e) {
|
|
2071
2286
|
failTask(test.result, e, runner.config.diffOptions);
|
|
2072
2287
|
}
|
|
2073
2288
|
try {
|
|
2074
|
-
|
|
2075
|
-
await ((_runner$onTaskFinishe = runner.onTaskFinished) === null || _runner$onTaskFinishe === void 0 ? void 0 : _runner$onTaskFinishe.call(runner, test));
|
|
2289
|
+
await runner.onTaskFinished?.(test);
|
|
2076
2290
|
} catch (e) {
|
|
2077
2291
|
failTask(test.result, e, runner.config.diffOptions);
|
|
2078
2292
|
}
|
|
@@ -2087,18 +2301,18 @@ async function runTest(test, runner) {
|
|
|
2087
2301
|
} catch (e) {
|
|
2088
2302
|
failTask(test.result, e, runner.config.diffOptions);
|
|
2089
2303
|
}
|
|
2090
|
-
if (
|
|
2304
|
+
if (test.onFinished?.length) {
|
|
2091
2305
|
await $("test.onFinished", () => callTestHooks(runner, test, test.onFinished, "stack"));
|
|
2092
2306
|
}
|
|
2093
|
-
if (test.result.state === "fail" &&
|
|
2307
|
+
if (test.result.state === "fail" && test.onFailed?.length) {
|
|
2094
2308
|
await $("test.onFailed", () => callTestHooks(runner, test, test.onFailed, runner.config.sequence.hooks));
|
|
2095
2309
|
}
|
|
2096
2310
|
test.onFailed = undefined;
|
|
2097
2311
|
test.onFinished = undefined;
|
|
2098
|
-
await
|
|
2312
|
+
await runner.onAfterRetryTask?.(test, {
|
|
2099
2313
|
retry: retryCount,
|
|
2100
2314
|
repeats: repeatCount
|
|
2101
|
-
})
|
|
2315
|
+
});
|
|
2102
2316
|
}).catch((error) => {
|
|
2103
2317
|
failTask(test.result, error, runner.config.diffOptions);
|
|
2104
2318
|
});
|
|
@@ -2110,12 +2324,11 @@ async function runTest(test, runner) {
|
|
|
2110
2324
|
failTask(test.result, e, runner.config.diffOptions);
|
|
2111
2325
|
}
|
|
2112
2326
|
// skipped with new PendingError
|
|
2113
|
-
if (
|
|
2114
|
-
var _test$result4;
|
|
2327
|
+
if (test.result?.pending || test.result?.state === "skip") {
|
|
2115
2328
|
test.mode = "skip";
|
|
2116
2329
|
test.result = {
|
|
2117
2330
|
state: "skip",
|
|
2118
|
-
note:
|
|
2331
|
+
note: test.result?.note,
|
|
2119
2332
|
pending: true,
|
|
2120
2333
|
duration: now() - start
|
|
2121
2334
|
};
|
|
@@ -2157,7 +2370,7 @@ async function runTest(test, runner) {
|
|
|
2157
2370
|
cleanupRunningTest();
|
|
2158
2371
|
setCurrentTest(undefined);
|
|
2159
2372
|
test.result.duration = now() - start;
|
|
2160
|
-
await
|
|
2373
|
+
await runner.onAfterRunTask?.(test);
|
|
2161
2374
|
updateTask("test-finished", test, runner);
|
|
2162
2375
|
}
|
|
2163
2376
|
function failTask(result, err, diffOptions) {
|
|
@@ -2167,12 +2380,17 @@ function failTask(result, err, diffOptions) {
|
|
|
2167
2380
|
result.pending = true;
|
|
2168
2381
|
return;
|
|
2169
2382
|
}
|
|
2383
|
+
if (err instanceof TestRunAbortError) {
|
|
2384
|
+
result.state = "skip";
|
|
2385
|
+
result.note = err.message;
|
|
2386
|
+
return;
|
|
2387
|
+
}
|
|
2170
2388
|
result.state = "fail";
|
|
2171
2389
|
const errors = Array.isArray(err) ? err : [err];
|
|
2172
2390
|
for (const e of errors) {
|
|
2173
|
-
const
|
|
2174
|
-
result.errors
|
|
2175
|
-
result.errors.push(
|
|
2391
|
+
const errors = e instanceof AggregateError ? e.errors.map((e) => processError(e, diffOptions)) : [processError(e, diffOptions)];
|
|
2392
|
+
result.errors ??= [];
|
|
2393
|
+
result.errors.push(...errors);
|
|
2176
2394
|
}
|
|
2177
2395
|
}
|
|
2178
2396
|
function markTasksAsSkipped(suite, runner) {
|
|
@@ -2188,10 +2406,25 @@ function markTasksAsSkipped(suite, runner) {
|
|
|
2188
2406
|
}
|
|
2189
2407
|
});
|
|
2190
2408
|
}
|
|
2409
|
+
function markPendingTasksAsSkipped(suite, runner, note) {
|
|
2410
|
+
suite.tasks.forEach((t) => {
|
|
2411
|
+
if (!t.result || t.result.state === "run") {
|
|
2412
|
+
t.mode = "skip";
|
|
2413
|
+
t.result = {
|
|
2414
|
+
...t.result,
|
|
2415
|
+
state: "skip",
|
|
2416
|
+
note
|
|
2417
|
+
};
|
|
2418
|
+
updateTask("test-cancel", t, runner);
|
|
2419
|
+
}
|
|
2420
|
+
if (t.type === "suite") {
|
|
2421
|
+
markPendingTasksAsSkipped(t, runner, note);
|
|
2422
|
+
}
|
|
2423
|
+
});
|
|
2424
|
+
}
|
|
2191
2425
|
async function runSuite(suite, runner) {
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
if (((_suite$result = suite.result) === null || _suite$result === void 0 ? void 0 : _suite$result.state) === "fail") {
|
|
2426
|
+
await runner.onBeforeRunSuite?.(suite);
|
|
2427
|
+
if (suite.result?.state === "fail") {
|
|
2195
2428
|
markTasksAsSkipped(suite, runner);
|
|
2196
2429
|
// failed during collection
|
|
2197
2430
|
updateTask("suite-failed-early", suite, runner);
|
|
@@ -2213,7 +2446,6 @@ async function runSuite(suite, runner) {
|
|
|
2213
2446
|
suite.result.state = "todo";
|
|
2214
2447
|
updateTask("suite-finished", suite, runner);
|
|
2215
2448
|
} else {
|
|
2216
|
-
var _runner$onAfterRunSui;
|
|
2217
2449
|
let suiteRan = false;
|
|
2218
2450
|
try {
|
|
2219
2451
|
await callAroundAllHooks(suite, async () => {
|
|
@@ -2257,8 +2489,8 @@ async function runSuite(suite, runner) {
|
|
|
2257
2489
|
await $("suite.cleanup", () => callCleanupHooks(runner, beforeAllCleanups));
|
|
2258
2490
|
}
|
|
2259
2491
|
if (suite.file === suite) {
|
|
2260
|
-
const
|
|
2261
|
-
await callFixtureCleanup(context);
|
|
2492
|
+
const contexts = TestFixtures.getFileContexts(suite.file);
|
|
2493
|
+
await Promise.all(contexts.map((context) => callFixtureCleanup(context)));
|
|
2262
2494
|
}
|
|
2263
2495
|
} catch (e) {
|
|
2264
2496
|
failTask(suite.result, e, runner.config.diffOptions);
|
|
@@ -2274,9 +2506,8 @@ async function runSuite(suite, runner) {
|
|
|
2274
2506
|
}
|
|
2275
2507
|
if (suite.mode === "run" || suite.mode === "queued") {
|
|
2276
2508
|
if (!runner.config.passWithNoTests && !hasTests(suite)) {
|
|
2277
|
-
var _suite$result$errors;
|
|
2278
2509
|
suite.result.state = "fail";
|
|
2279
|
-
if (!
|
|
2510
|
+
if (!suite.result.errors?.length) {
|
|
2280
2511
|
const error = processError(new Error(`No test found in suite ${suite.name}`));
|
|
2281
2512
|
suite.result.errors = [error];
|
|
2282
2513
|
}
|
|
@@ -2287,44 +2518,38 @@ async function runSuite(suite, runner) {
|
|
|
2287
2518
|
}
|
|
2288
2519
|
}
|
|
2289
2520
|
suite.result.duration = now() - start;
|
|
2290
|
-
await
|
|
2521
|
+
await runner.onAfterRunSuite?.(suite);
|
|
2291
2522
|
updateTask("suite-finished", suite, runner);
|
|
2292
2523
|
}
|
|
2293
2524
|
}
|
|
2294
|
-
let limitMaxConcurrency;
|
|
2295
2525
|
async function runSuiteChild(c, runner) {
|
|
2296
2526
|
const $ = runner.trace;
|
|
2297
2527
|
if (c.type === "test") {
|
|
2298
|
-
return
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
"code.column.number": (_c$location2 = c.location) === null || _c$location2 === void 0 ? void 0 : _c$location2.column
|
|
2308
|
-
}, () => runTest(c, runner));
|
|
2309
|
-
});
|
|
2528
|
+
return $("run.test", {
|
|
2529
|
+
"vitest.test.id": c.id,
|
|
2530
|
+
"vitest.test.name": c.name,
|
|
2531
|
+
"vitest.test.mode": c.mode,
|
|
2532
|
+
"vitest.test.timeout": c.timeout,
|
|
2533
|
+
"code.file.path": c.file.filepath,
|
|
2534
|
+
"code.line.number": c.location?.line,
|
|
2535
|
+
"code.column.number": c.location?.column
|
|
2536
|
+
}, () => runTest(c, runner));
|
|
2310
2537
|
} else if (c.type === "suite") {
|
|
2311
|
-
var _c$location3, _c$location4;
|
|
2312
2538
|
return $("run.suite", {
|
|
2313
2539
|
"vitest.suite.id": c.id,
|
|
2314
2540
|
"vitest.suite.name": c.name,
|
|
2315
2541
|
"vitest.suite.mode": c.mode,
|
|
2316
2542
|
"code.file.path": c.file.filepath,
|
|
2317
|
-
"code.line.number":
|
|
2318
|
-
"code.column.number":
|
|
2543
|
+
"code.line.number": c.location?.line,
|
|
2544
|
+
"code.column.number": c.location?.column
|
|
2319
2545
|
}, () => runSuite(c, runner));
|
|
2320
2546
|
}
|
|
2321
2547
|
}
|
|
2322
2548
|
async function runFiles(files, runner) {
|
|
2323
|
-
limitMaxConcurrency
|
|
2549
|
+
limitMaxConcurrency ??= limitConcurrency(runner.config.maxConcurrency);
|
|
2324
2550
|
for (const file of files) {
|
|
2325
2551
|
if (!file.tasks.length && !runner.config.passWithNoTests) {
|
|
2326
|
-
|
|
2327
|
-
if (!((_file$result = file.result) === null || _file$result === void 0 || (_file$result = _file$result.errors) === null || _file$result === void 0 ? void 0 : _file$result.length)) {
|
|
2552
|
+
if (!file.result?.errors?.length) {
|
|
2328
2553
|
const error = processError(new Error(`No test suite found in file ${file.filepath}`));
|
|
2329
2554
|
file.result = {
|
|
2330
2555
|
state: "fail",
|
|
@@ -2346,37 +2571,35 @@ function defaultTrace(_, attributes, cb) {
|
|
|
2346
2571
|
return cb();
|
|
2347
2572
|
}
|
|
2348
2573
|
async function startTests(specs, runner) {
|
|
2349
|
-
|
|
2350
|
-
runner.
|
|
2351
|
-
const cancel = (_runner$cancel = runner.cancel) === null || _runner$cancel === void 0 ? void 0 : _runner$cancel.bind(runner);
|
|
2574
|
+
runner.trace ??= defaultTrace;
|
|
2575
|
+
const cancel = runner.cancel?.bind(runner);
|
|
2352
2576
|
// Ideally, we need to have an event listener for this, but only have a runner here.
|
|
2353
2577
|
// Adding another onCancel felt wrong (maybe it needs to be refactored)
|
|
2354
2578
|
runner.cancel = (reason) => {
|
|
2355
2579
|
// We intentionally create only one error since there is only one test run that can be cancelled
|
|
2356
2580
|
const error = new TestRunAbortError("The test run was aborted by the user.", reason);
|
|
2357
|
-
getRunningTests().forEach((test) =>
|
|
2358
|
-
|
|
2581
|
+
getRunningTests().forEach((test) => {
|
|
2582
|
+
abortContextSignal(test.context, error);
|
|
2583
|
+
markPendingTasksAsSkipped(test.file, runner, error.message);
|
|
2584
|
+
});
|
|
2585
|
+
return cancel?.(reason);
|
|
2359
2586
|
};
|
|
2360
2587
|
if (!workerRunners.has(runner)) {
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
if (context) {
|
|
2366
|
-
await callFixtureCleanup(context);
|
|
2367
|
-
}
|
|
2588
|
+
runner.onCleanupWorkerContext?.(async () => {
|
|
2589
|
+
await Promise.all([...TestFixtures.getWorkerContexts()].map((context) => callFixtureCleanup(context))).finally(() => {
|
|
2590
|
+
TestFixtures.clearDefinitions();
|
|
2591
|
+
});
|
|
2368
2592
|
});
|
|
2369
2593
|
workerRunners.add(runner);
|
|
2370
2594
|
}
|
|
2371
2595
|
try {
|
|
2372
|
-
var _runner$onBeforeColle, _runner$onCollected, _runner$onBeforeRunFi, _runner$onAfterRunFil;
|
|
2373
2596
|
const paths = specs.map((f) => typeof f === "string" ? f : f.filepath);
|
|
2374
|
-
await
|
|
2597
|
+
await runner.onBeforeCollect?.(paths);
|
|
2375
2598
|
const files = await collectTests(specs, runner);
|
|
2376
|
-
await
|
|
2377
|
-
await
|
|
2599
|
+
await runner.onCollected?.(files);
|
|
2600
|
+
await runner.onBeforeRunFiles?.(files);
|
|
2378
2601
|
await runFiles(files, runner);
|
|
2379
|
-
await
|
|
2602
|
+
await runner.onAfterRunFiles?.(files);
|
|
2380
2603
|
await finishSendTasksUpdate(runner);
|
|
2381
2604
|
return files;
|
|
2382
2605
|
} finally {
|
|
@@ -2384,12 +2607,11 @@ async function startTests(specs, runner) {
|
|
|
2384
2607
|
}
|
|
2385
2608
|
}
|
|
2386
2609
|
async function publicCollect(specs, runner) {
|
|
2387
|
-
|
|
2388
|
-
runner.trace ?? (runner.trace = defaultTrace);
|
|
2610
|
+
runner.trace ??= defaultTrace;
|
|
2389
2611
|
const paths = specs.map((f) => typeof f === "string" ? f : f.filepath);
|
|
2390
|
-
await
|
|
2612
|
+
await runner.onBeforeCollect?.(paths);
|
|
2391
2613
|
const files = await collectTests(specs, runner);
|
|
2392
|
-
await
|
|
2614
|
+
await runner.onCollected?.(files);
|
|
2393
2615
|
return files;
|
|
2394
2616
|
}
|
|
2395
2617
|
|
|
@@ -2403,12 +2625,13 @@ async function publicCollect(specs, runner) {
|
|
|
2403
2625
|
*
|
|
2404
2626
|
* Vitest automatically injects the source location where the artifact was created and manages any attachments you include.
|
|
2405
2627
|
*
|
|
2628
|
+
* **Note:** artifacts must be recorded before the task is reported. Any artifacts recorded after that will not be included in the task.
|
|
2629
|
+
*
|
|
2406
2630
|
* @param task - The test task context, typically accessed via `this.task` in custom matchers or `context.task` in tests
|
|
2407
2631
|
* @param artifact - The artifact to record. Must extend {@linkcode TestArtifactBase}
|
|
2408
2632
|
*
|
|
2409
2633
|
* @returns A promise that resolves to the recorded artifact with location injected
|
|
2410
2634
|
*
|
|
2411
|
-
* @throws {Error} If called after the test has finished running
|
|
2412
2635
|
* @throws {Error} If the test runner doesn't support artifacts
|
|
2413
2636
|
*
|
|
2414
2637
|
* @example
|
|
@@ -2429,9 +2652,6 @@ async function publicCollect(specs, runner) {
|
|
|
2429
2652
|
*/
|
|
2430
2653
|
async function recordArtifact(task, artifact) {
|
|
2431
2654
|
const runner = getRunner();
|
|
2432
|
-
if (task.result && task.result.state !== "run") {
|
|
2433
|
-
throw new Error(`Cannot record a test artifact outside of the test run. The test "${task.name}" finished running with the "${task.result.state}" state already.`);
|
|
2434
|
-
}
|
|
2435
2655
|
const stack = findTestFileStackTrace(task.file.filepath, new Error("STACK_TRACE").stack);
|
|
2436
2656
|
if (stack) {
|
|
2437
2657
|
artifact.location = {
|