ava 3.15.0 → 4.0.0
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/entrypoints/cli.mjs +4 -0
- package/entrypoints/eslint-plugin-helper.cjs +109 -0
- package/entrypoints/main.cjs +2 -0
- package/entrypoints/main.mjs +1 -0
- package/entrypoints/plugin.cjs +2 -0
- package/entrypoints/plugin.mjs +4 -0
- package/index.d.ts +6 -816
- package/lib/api.js +108 -49
- package/lib/assert.js +255 -270
- package/lib/chalk.js +9 -14
- package/lib/cli.js +118 -112
- package/lib/code-excerpt.js +12 -17
- package/lib/concordance-options.js +29 -65
- package/lib/context-ref.js +3 -6
- package/lib/create-chain.js +32 -20
- package/lib/environment-variables.js +1 -4
- package/lib/eslint-plugin-helper-worker.js +73 -0
- package/lib/extensions.js +2 -2
- package/lib/fork.js +81 -84
- package/lib/glob-helpers.cjs +140 -0
- package/lib/globs.js +136 -163
- package/lib/{ipc-flow-control.js → ipc-flow-control.cjs} +1 -0
- package/lib/is-ci.js +4 -2
- package/lib/like-selector.js +7 -13
- package/lib/line-numbers.js +11 -18
- package/lib/load-config.js +56 -180
- package/lib/module-types.js +3 -7
- package/lib/node-arguments.js +4 -5
- package/lib/{now-and-timers.js → now-and-timers.cjs} +0 -0
- package/lib/parse-test-args.js +22 -11
- package/lib/pkg.cjs +2 -0
- package/lib/plugin-support/shared-worker-loader.js +45 -48
- package/lib/plugin-support/shared-workers.js +24 -46
- package/lib/provider-manager.js +20 -14
- package/lib/reporters/beautify-stack.js +6 -12
- package/lib/reporters/colors.js +40 -15
- package/lib/reporters/default.js +114 -364
- package/lib/reporters/format-serialized-error.js +7 -18
- package/lib/reporters/improper-usage-messages.js +8 -9
- package/lib/reporters/prefix-title.js +17 -15
- package/lib/reporters/tap.js +18 -25
- package/lib/run-status.js +29 -23
- package/lib/runner.js +157 -172
- package/lib/scheduler.js +53 -0
- package/lib/serialize-error.js +61 -64
- package/lib/snapshot-manager.js +271 -289
- package/lib/test.js +135 -291
- package/lib/watcher.js +69 -44
- package/lib/worker/base.js +208 -0
- package/lib/worker/channel.cjs +290 -0
- package/lib/worker/dependency-tracker.js +24 -23
- package/lib/worker/{ensure-forked.js → guard-environment.cjs} +5 -4
- package/lib/worker/line-numbers.js +58 -20
- package/lib/worker/main.cjs +12 -0
- package/lib/worker/{options.js → options.cjs} +0 -0
- package/lib/worker/{plugin.js → plugin.cjs} +30 -21
- package/lib/worker/state.cjs +5 -0
- package/lib/worker/utils.cjs +6 -0
- package/package.json +71 -68
- package/plugin.d.ts +51 -53
- package/readme.md +5 -13
- package/types/assertions.d.ts +327 -0
- package/types/subscribable.ts +6 -0
- package/types/test-fn.d.ts +231 -0
- package/types/try-fn.d.ts +58 -0
- package/cli.js +0 -11
- package/eslint-plugin-helper.js +0 -201
- package/index.js +0 -8
- package/lib/worker/ipc.js +0 -201
- package/lib/worker/main.js +0 -21
- package/lib/worker/subprocess.js +0 -266
- package/plugin.js +0 -9
package/lib/runner.js
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import {pathToFileURL} from 'node:url';
|
|
3
|
+
|
|
4
|
+
import Emittery from 'emittery';
|
|
5
|
+
import {matcher} from 'matcher';
|
|
6
|
+
|
|
7
|
+
import ContextRef from './context-ref.js';
|
|
8
|
+
import createChain from './create-chain.js';
|
|
9
|
+
import parseTestArgs from './parse-test-args.js';
|
|
10
|
+
import serializeError from './serialize-error.js';
|
|
11
|
+
import {load as loadSnapshots, determineSnapshotDir} from './snapshot-manager.js';
|
|
12
|
+
import Runnable from './test.js';
|
|
13
|
+
import {waitForReady} from './worker/state.cjs';
|
|
14
|
+
|
|
15
|
+
const makeFileURL = file => file.startsWith('file://') ? file : pathToFileURL(file).toString();
|
|
16
|
+
export default class Runner extends Emittery {
|
|
12
17
|
constructor(options = {}) {
|
|
13
18
|
super();
|
|
14
19
|
|
|
@@ -18,21 +23,18 @@ class Runner extends Emittery {
|
|
|
18
23
|
this.file = options.file;
|
|
19
24
|
this.checkSelectedByLineNumbers = options.checkSelectedByLineNumbers;
|
|
20
25
|
this.match = options.match || [];
|
|
21
|
-
this.powerAssert = undefined; // Assigned later.
|
|
22
26
|
this.projectDir = options.projectDir;
|
|
23
27
|
this.recordNewSnapshots = options.recordNewSnapshots === true;
|
|
24
28
|
this.runOnlyExclusive = options.runOnlyExclusive === true;
|
|
25
29
|
this.serial = options.serial === true;
|
|
26
|
-
this.skippingTests = false;
|
|
27
30
|
this.snapshotDir = options.snapshotDir;
|
|
28
31
|
this.updateSnapshots = options.updateSnapshots;
|
|
29
32
|
|
|
30
33
|
this.activeRunnables = new Set();
|
|
31
34
|
this.boundCompareTestSnapshot = this.compareTestSnapshot.bind(this);
|
|
32
|
-
this.skippedSnapshots = false;
|
|
33
35
|
this.boundSkipSnapshot = this.skipSnapshot.bind(this);
|
|
34
36
|
this.interrupted = false;
|
|
35
|
-
|
|
37
|
+
|
|
36
38
|
this.nextTaskIndex = 0;
|
|
37
39
|
this.tasks = {
|
|
38
40
|
after: [],
|
|
@@ -43,9 +45,9 @@ class Runner extends Emittery {
|
|
|
43
45
|
beforeEach: [],
|
|
44
46
|
concurrent: [],
|
|
45
47
|
serial: [],
|
|
46
|
-
todo: []
|
|
48
|
+
todo: [],
|
|
47
49
|
};
|
|
48
|
-
this.waitForReady =
|
|
50
|
+
this.waitForReady = waitForReady;
|
|
49
51
|
|
|
50
52
|
const uniqueTestTitles = new Set();
|
|
51
53
|
this.registerUniqueTitle = title => {
|
|
@@ -57,14 +59,21 @@ class Runner extends Emittery {
|
|
|
57
59
|
return true;
|
|
58
60
|
};
|
|
59
61
|
|
|
62
|
+
this.notifyTimeoutUpdate = timeoutMs => {
|
|
63
|
+
this.emit('stateChange', {
|
|
64
|
+
type: 'test-timeout-configured',
|
|
65
|
+
period: timeoutMs,
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
|
|
60
69
|
let hasStarted = false;
|
|
61
70
|
let scheduledStart = false;
|
|
62
71
|
const meta = Object.freeze({
|
|
63
|
-
file: options.file,
|
|
72
|
+
file: makeFileURL(options.file),
|
|
64
73
|
get snapshotDirectory() {
|
|
65
74
|
const {file, snapshotDir: fixedLocation, projectDir} = options;
|
|
66
|
-
return
|
|
67
|
-
}
|
|
75
|
+
return makeFileURL(determineSnapshotDir({file, fixedLocation, projectDir}));
|
|
76
|
+
},
|
|
68
77
|
});
|
|
69
78
|
this.chain = createChain((metadata, testArgs) => { // eslint-disable-line complexity
|
|
70
79
|
if (hasStarted) {
|
|
@@ -81,97 +90,95 @@ class Runner extends Emittery {
|
|
|
81
90
|
|
|
82
91
|
metadata.taskIndex = this.nextTaskIndex++;
|
|
83
92
|
|
|
84
|
-
const {args,
|
|
93
|
+
const {args, implementation, title} = parseTestArgs(testArgs);
|
|
85
94
|
|
|
86
95
|
if (this.checkSelectedByLineNumbers) {
|
|
87
96
|
metadata.selected = this.checkSelectedByLineNumbers();
|
|
88
97
|
}
|
|
89
98
|
|
|
90
99
|
if (metadata.todo) {
|
|
91
|
-
if (
|
|
100
|
+
if (implementation) {
|
|
92
101
|
throw new TypeError('`todo` tests are not allowed to have an implementation. Use `test.skip()` for tests with an implementation.');
|
|
93
102
|
}
|
|
94
103
|
|
|
95
|
-
if (!
|
|
104
|
+
if (!title.raw) { // Either undefined or a string.
|
|
96
105
|
throw new TypeError('`todo` tests require a title');
|
|
97
106
|
}
|
|
98
107
|
|
|
99
|
-
if (!this.registerUniqueTitle(
|
|
100
|
-
throw new Error(`Duplicate test title: ${
|
|
108
|
+
if (!this.registerUniqueTitle(title.value)) {
|
|
109
|
+
throw new Error(`Duplicate test title: ${title.value}`);
|
|
101
110
|
}
|
|
102
111
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
this.runOnlyExclusive = true;
|
|
108
|
-
}
|
|
112
|
+
// --match selects TODO tests.
|
|
113
|
+
if (this.match.length > 0 && matcher(title.value, this.match).length === 1) {
|
|
114
|
+
metadata.exclusive = true;
|
|
115
|
+
this.runOnlyExclusive = true;
|
|
109
116
|
}
|
|
110
117
|
|
|
111
|
-
this.tasks.todo.push({title:
|
|
118
|
+
this.tasks.todo.push({title: title.value, metadata});
|
|
112
119
|
this.emit('stateChange', {
|
|
113
120
|
type: 'declared-test',
|
|
114
|
-
title:
|
|
121
|
+
title: title.value,
|
|
115
122
|
knownFailing: false,
|
|
116
|
-
todo: true
|
|
123
|
+
todo: true,
|
|
117
124
|
});
|
|
118
125
|
} else {
|
|
119
|
-
if (
|
|
126
|
+
if (!implementation) {
|
|
120
127
|
throw new TypeError('Expected an implementation. Use `test.todo()` for tests without an implementation.');
|
|
121
128
|
}
|
|
122
129
|
|
|
123
|
-
|
|
124
|
-
|
|
130
|
+
if (Array.isArray(implementation)) {
|
|
131
|
+
throw new TypeError('AVA 4 no longer supports multiple implementations.');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (title.isSet && !title.isValid) {
|
|
135
|
+
throw new TypeError('Test & hook titles must be strings');
|
|
136
|
+
}
|
|
125
137
|
|
|
126
|
-
|
|
127
|
-
|
|
138
|
+
let fallbackTitle = title.value;
|
|
139
|
+
if (title.isEmpty) {
|
|
140
|
+
if (metadata.type === 'test') {
|
|
141
|
+
throw new TypeError('Tests must have a title');
|
|
142
|
+
} else if (metadata.always) {
|
|
143
|
+
fallbackTitle = `${metadata.type}.always hook`;
|
|
144
|
+
} else {
|
|
145
|
+
fallbackTitle = `${metadata.type} hook`;
|
|
128
146
|
}
|
|
147
|
+
}
|
|
129
148
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
149
|
+
if (metadata.type === 'test' && !this.registerUniqueTitle(title.value)) {
|
|
150
|
+
throw new Error(`Duplicate test title: ${title.value}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const task = {
|
|
154
|
+
title: title.value || fallbackTitle,
|
|
155
|
+
implementation,
|
|
156
|
+
args,
|
|
157
|
+
metadata: {...metadata},
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
if (metadata.type === 'test') {
|
|
161
|
+
if (this.match.length > 0) {
|
|
162
|
+
// --match overrides .only()
|
|
163
|
+
task.metadata.exclusive = matcher(title.value, this.match).length === 1;
|
|
138
164
|
}
|
|
139
165
|
|
|
140
|
-
if (metadata.
|
|
141
|
-
|
|
166
|
+
if (task.metadata.exclusive) {
|
|
167
|
+
this.runOnlyExclusive = true;
|
|
142
168
|
}
|
|
143
169
|
|
|
144
|
-
|
|
145
|
-
title,
|
|
146
|
-
implementation,
|
|
147
|
-
args,
|
|
148
|
-
metadata: {...metadata}
|
|
149
|
-
};
|
|
170
|
+
this.tasks[metadata.serial ? 'serial' : 'concurrent'].push(task);
|
|
150
171
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (task.metadata.exclusive) {
|
|
162
|
-
this.runOnlyExclusive = true;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
this.tasks[metadata.serial ? 'serial' : 'concurrent'].push(task);
|
|
166
|
-
this.emit('stateChange', {
|
|
167
|
-
type: 'declared-test',
|
|
168
|
-
title,
|
|
169
|
-
knownFailing: metadata.failing,
|
|
170
|
-
todo: false
|
|
171
|
-
});
|
|
172
|
-
} else if (!metadata.skipped) {
|
|
173
|
-
this.tasks[metadata.type + (metadata.always ? 'Always' : '')].push(task);
|
|
174
|
-
}
|
|
172
|
+
this.snapshots.touch(title.value, metadata.taskIndex);
|
|
173
|
+
|
|
174
|
+
this.emit('stateChange', {
|
|
175
|
+
type: 'declared-test',
|
|
176
|
+
title: title.value,
|
|
177
|
+
knownFailing: metadata.failing,
|
|
178
|
+
todo: false,
|
|
179
|
+
});
|
|
180
|
+
} else if (!metadata.skipped) {
|
|
181
|
+
this.tasks[metadata.type + (metadata.always ? 'Always' : '')].push(task);
|
|
175
182
|
}
|
|
176
183
|
}
|
|
177
184
|
}, {
|
|
@@ -182,54 +189,43 @@ class Runner extends Emittery {
|
|
|
182
189
|
failing: false,
|
|
183
190
|
callback: false,
|
|
184
191
|
inline: false, // Set for attempt metadata created by `t.try()`
|
|
185
|
-
always: false
|
|
192
|
+
always: false,
|
|
186
193
|
}, meta);
|
|
187
194
|
}
|
|
188
195
|
|
|
189
|
-
|
|
190
|
-
if (
|
|
191
|
-
this.
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
this.
|
|
196
|
+
get snapshots() {
|
|
197
|
+
if (this._snapshots) {
|
|
198
|
+
return this._snapshots;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Lazy load not when the runner is instantiated but when snapshots are
|
|
202
|
+
// needed. This should be after the test file has been loaded and source
|
|
203
|
+
// maps are available.
|
|
204
|
+
const snapshots = loadSnapshots({
|
|
205
|
+
file: this.file,
|
|
206
|
+
fixedLocation: this.snapshotDir,
|
|
207
|
+
projectDir: this.projectDir,
|
|
208
|
+
recordNewSnapshots: this.recordNewSnapshots,
|
|
209
|
+
updating: this.updateSnapshots,
|
|
210
|
+
});
|
|
211
|
+
if (snapshots.snapPath !== undefined) {
|
|
212
|
+
this.emit('dependency', snapshots.snapPath);
|
|
199
213
|
}
|
|
200
214
|
|
|
215
|
+
this._snapshots = snapshots;
|
|
216
|
+
return snapshots;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
compareTestSnapshot(options) {
|
|
201
220
|
return this.snapshots.compare(options);
|
|
202
221
|
}
|
|
203
222
|
|
|
204
|
-
skipSnapshot() {
|
|
205
|
-
this.
|
|
223
|
+
skipSnapshot(options) {
|
|
224
|
+
return this.snapshots.skipSnapshot(options);
|
|
206
225
|
}
|
|
207
226
|
|
|
208
227
|
saveSnapshotState() {
|
|
209
|
-
|
|
210
|
-
this.updateSnapshots &&
|
|
211
|
-
(
|
|
212
|
-
this.runOnlyExclusive ||
|
|
213
|
-
this.skippingTests ||
|
|
214
|
-
this.skippedSnapshots
|
|
215
|
-
)
|
|
216
|
-
) {
|
|
217
|
-
return {cannotSave: true};
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (this.snapshots) {
|
|
221
|
-
return {touchedFiles: this.snapshots.save()};
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
if (this.updateSnapshots) {
|
|
225
|
-
return {touchedFiles: snapshotManager.cleanSnapshots({
|
|
226
|
-
file: this.file,
|
|
227
|
-
fixedLocation: this.snapshotDir,
|
|
228
|
-
projectDir: this.projectDir
|
|
229
|
-
})};
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
return {};
|
|
228
|
+
return {touchedFiles: this.snapshots.save()};
|
|
233
229
|
}
|
|
234
230
|
|
|
235
231
|
onRun(runnable) {
|
|
@@ -240,16 +236,6 @@ class Runner extends Emittery {
|
|
|
240
236
|
this.activeRunnables.delete(runnable);
|
|
241
237
|
}
|
|
242
238
|
|
|
243
|
-
attributeLeakedError(err) {
|
|
244
|
-
for (const runnable of this.activeRunnables) {
|
|
245
|
-
if (runnable.attributeLeakedError(err)) {
|
|
246
|
-
return true;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
return false;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
239
|
beforeExitHandler() {
|
|
254
240
|
for (const runnable of this.activeRunnables) {
|
|
255
241
|
runnable.finishDueToInactivity();
|
|
@@ -269,25 +255,25 @@ class Runner extends Emittery {
|
|
|
269
255
|
};
|
|
270
256
|
|
|
271
257
|
let waitForSerial = Promise.resolve();
|
|
272
|
-
await runnables.reduce((previous, runnable) => { // eslint-disable-line unicorn/no-reduce
|
|
258
|
+
await runnables.reduce((previous, runnable) => { // eslint-disable-line unicorn/no-array-reduce
|
|
273
259
|
if (runnable.metadata.serial || this.serial) {
|
|
274
|
-
waitForSerial = previous.then(() =>
|
|
260
|
+
waitForSerial = previous.then(() =>
|
|
275
261
|
// Serial runnables run as long as there was no previous failure, unless
|
|
276
262
|
// the runnable should always be run.
|
|
277
|
-
|
|
278
|
-
|
|
263
|
+
(allPassed || runnable.metadata.always) && runAndStoreResult(runnable),
|
|
264
|
+
);
|
|
279
265
|
return waitForSerial;
|
|
280
266
|
}
|
|
281
267
|
|
|
282
268
|
return Promise.all([
|
|
283
269
|
previous,
|
|
284
|
-
waitForSerial.then(() =>
|
|
270
|
+
waitForSerial.then(() =>
|
|
285
271
|
// Concurrent runnables are kicked off after the previous serial
|
|
286
272
|
// runnables have completed, as long as there was no previous failure
|
|
287
273
|
// (or if the runnable should always be run). One concurrent runnable's
|
|
288
274
|
// failure does not prevent the next runnable from running.
|
|
289
|
-
|
|
290
|
-
|
|
275
|
+
(allPassed || runnable.metadata.always) && runAndStoreResult(runnable),
|
|
276
|
+
),
|
|
291
277
|
]);
|
|
292
278
|
}, waitForSerial);
|
|
293
279
|
|
|
@@ -303,22 +289,22 @@ class Runner extends Emittery {
|
|
|
303
289
|
return result;
|
|
304
290
|
}
|
|
305
291
|
|
|
306
|
-
async runHooks(tasks, contextRef, {titleSuffix, testPassed
|
|
292
|
+
async runHooks(tasks, contextRef, {titleSuffix, testPassed} = {}) {
|
|
307
293
|
const hooks = tasks.map(task => new Runnable({
|
|
308
294
|
contextRef,
|
|
309
295
|
experiments: this.experiments,
|
|
310
296
|
failWithoutAssertions: false,
|
|
311
|
-
fn: task.args.length === 0
|
|
312
|
-
task.implementation
|
|
313
|
-
t => task.implementation
|
|
297
|
+
fn: task.args.length === 0
|
|
298
|
+
? task.implementation
|
|
299
|
+
: t => Reflect.apply(task.implementation, null, [t, ...task.args]),
|
|
314
300
|
compareTestSnapshot: this.boundCompareTestSnapshot,
|
|
315
301
|
skipSnapshot: this.boundSkipSnapshot,
|
|
316
302
|
updateSnapshots: this.updateSnapshots,
|
|
317
|
-
metadata:
|
|
318
|
-
powerAssert: this.powerAssert,
|
|
303
|
+
metadata: task.metadata,
|
|
319
304
|
title: `${task.title}${titleSuffix || ''}`,
|
|
320
305
|
isHook: true,
|
|
321
|
-
testPassed
|
|
306
|
+
testPassed,
|
|
307
|
+
notifyTimeoutUpdate: this.notifyTimeoutUpdate,
|
|
322
308
|
}));
|
|
323
309
|
const outcome = await this.runMultiple(hooks, this.serial);
|
|
324
310
|
for (const result of outcome.storedResults) {
|
|
@@ -327,7 +313,7 @@ class Runner extends Emittery {
|
|
|
327
313
|
type: 'hook-finished',
|
|
328
314
|
title: result.title,
|
|
329
315
|
duration: result.duration,
|
|
330
|
-
logs: result.logs
|
|
316
|
+
logs: result.logs,
|
|
331
317
|
});
|
|
332
318
|
} else {
|
|
333
319
|
this.emit('stateChange', {
|
|
@@ -335,7 +321,7 @@ class Runner extends Emittery {
|
|
|
335
321
|
title: result.title,
|
|
336
322
|
err: serializeError('Hook failure', true, result.error),
|
|
337
323
|
duration: result.duration,
|
|
338
|
-
logs: result.logs
|
|
324
|
+
logs: result.logs,
|
|
339
325
|
});
|
|
340
326
|
}
|
|
341
327
|
}
|
|
@@ -350,8 +336,7 @@ class Runner extends Emittery {
|
|
|
350
336
|
contextRef,
|
|
351
337
|
{
|
|
352
338
|
titleSuffix: hookSuffix,
|
|
353
|
-
|
|
354
|
-
}
|
|
339
|
+
},
|
|
355
340
|
);
|
|
356
341
|
|
|
357
342
|
let testOk = false;
|
|
@@ -361,16 +346,16 @@ class Runner extends Emittery {
|
|
|
361
346
|
contextRef,
|
|
362
347
|
experiments: this.experiments,
|
|
363
348
|
failWithoutAssertions: this.failWithoutAssertions,
|
|
364
|
-
fn: task.args.length === 0
|
|
365
|
-
task.implementation
|
|
366
|
-
t => task.implementation
|
|
349
|
+
fn: task.args.length === 0
|
|
350
|
+
? task.implementation
|
|
351
|
+
: t => Reflect.apply(task.implementation, null, [t, ...task.args]),
|
|
367
352
|
compareTestSnapshot: this.boundCompareTestSnapshot,
|
|
368
353
|
skipSnapshot: this.boundSkipSnapshot,
|
|
369
354
|
updateSnapshots: this.updateSnapshots,
|
|
370
355
|
metadata: task.metadata,
|
|
371
|
-
powerAssert: this.powerAssert,
|
|
372
356
|
title: task.title,
|
|
373
|
-
registerUniqueTitle: this.registerUniqueTitle
|
|
357
|
+
registerUniqueTitle: this.registerUniqueTitle,
|
|
358
|
+
notifyTimeoutUpdate: this.notifyTimeoutUpdate,
|
|
374
359
|
});
|
|
375
360
|
|
|
376
361
|
const result = await this.runSingle(test);
|
|
@@ -382,7 +367,7 @@ class Runner extends Emittery {
|
|
|
382
367
|
title: result.title,
|
|
383
368
|
duration: result.duration,
|
|
384
369
|
knownFailing: result.metadata.failing,
|
|
385
|
-
logs: result.logs
|
|
370
|
+
logs: result.logs,
|
|
386
371
|
});
|
|
387
372
|
|
|
388
373
|
hooksOk = await this.runHooks(
|
|
@@ -391,7 +376,6 @@ class Runner extends Emittery {
|
|
|
391
376
|
{
|
|
392
377
|
titleSuffix: hookSuffix,
|
|
393
378
|
testPassed: testOk,
|
|
394
|
-
associatedTaskIndex: task.metadata.taskIndex
|
|
395
379
|
});
|
|
396
380
|
} else {
|
|
397
381
|
this.emit('stateChange', {
|
|
@@ -400,7 +384,7 @@ class Runner extends Emittery {
|
|
|
400
384
|
err: serializeError('Test failure', true, result.error, this.file),
|
|
401
385
|
duration: result.duration,
|
|
402
386
|
knownFailing: result.metadata.failing,
|
|
403
|
-
logs: result.logs
|
|
387
|
+
logs: result.logs,
|
|
404
388
|
});
|
|
405
389
|
// Don't run `afterEach` hooks if the test failed.
|
|
406
390
|
}
|
|
@@ -412,20 +396,21 @@ class Runner extends Emittery {
|
|
|
412
396
|
{
|
|
413
397
|
titleSuffix: hookSuffix,
|
|
414
398
|
testPassed: testOk,
|
|
415
|
-
associatedTaskIndex: task.metadata.taskIndex
|
|
416
399
|
});
|
|
417
400
|
return alwaysOk && hooksOk && testOk;
|
|
418
401
|
}
|
|
419
402
|
|
|
420
|
-
async start() {
|
|
403
|
+
async start() { // eslint-disable-line complexity
|
|
421
404
|
const concurrentTests = [];
|
|
422
405
|
const serialTests = [];
|
|
423
406
|
for (const task of this.tasks.serial) {
|
|
424
407
|
if (this.runOnlyExclusive && !task.metadata.exclusive) {
|
|
408
|
+
this.snapshots.skipBlock(task.title, task.metadata.taskIndex);
|
|
425
409
|
continue;
|
|
426
410
|
}
|
|
427
411
|
|
|
428
412
|
if (this.checkSelectedByLineNumbers && !task.metadata.selected) {
|
|
413
|
+
this.snapshots.skipBlock(task.title, task.metadata.taskIndex);
|
|
429
414
|
continue;
|
|
430
415
|
}
|
|
431
416
|
|
|
@@ -434,20 +419,24 @@ class Runner extends Emittery {
|
|
|
434
419
|
title: task.title,
|
|
435
420
|
knownFailing: task.metadata.failing,
|
|
436
421
|
skip: task.metadata.skipped,
|
|
437
|
-
todo: false
|
|
422
|
+
todo: false,
|
|
438
423
|
});
|
|
439
424
|
|
|
440
|
-
if (
|
|
425
|
+
if (task.metadata.skipped) {
|
|
426
|
+
this.snapshots.skipBlock(task.title, task.metadata.taskIndex);
|
|
427
|
+
} else {
|
|
441
428
|
serialTests.push(task);
|
|
442
429
|
}
|
|
443
430
|
}
|
|
444
431
|
|
|
445
432
|
for (const task of this.tasks.concurrent) {
|
|
446
433
|
if (this.runOnlyExclusive && !task.metadata.exclusive) {
|
|
434
|
+
this.snapshots.skipBlock(task.title, task.metadata.taskIndex);
|
|
447
435
|
continue;
|
|
448
436
|
}
|
|
449
437
|
|
|
450
438
|
if (this.checkSelectedByLineNumbers && !task.metadata.selected) {
|
|
439
|
+
this.snapshots.skipBlock(task.title, task.metadata.taskIndex);
|
|
451
440
|
continue;
|
|
452
441
|
}
|
|
453
442
|
|
|
@@ -456,15 +445,15 @@ class Runner extends Emittery {
|
|
|
456
445
|
title: task.title,
|
|
457
446
|
knownFailing: task.metadata.failing,
|
|
458
447
|
skip: task.metadata.skipped,
|
|
459
|
-
todo: false
|
|
448
|
+
todo: false,
|
|
460
449
|
});
|
|
461
450
|
|
|
462
|
-
if (
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
451
|
+
if (task.metadata.skipped) {
|
|
452
|
+
this.snapshots.skipBlock(task.title, task.metadata.taskIndex);
|
|
453
|
+
} else if (this.serial) {
|
|
454
|
+
serialTests.push(task);
|
|
455
|
+
} else {
|
|
456
|
+
concurrentTests.push(task);
|
|
468
457
|
}
|
|
469
458
|
}
|
|
470
459
|
|
|
@@ -482,7 +471,7 @@ class Runner extends Emittery {
|
|
|
482
471
|
title: task.title,
|
|
483
472
|
knownFailing: false,
|
|
484
473
|
skip: false,
|
|
485
|
-
todo: true
|
|
474
|
+
todo: true,
|
|
486
475
|
});
|
|
487
476
|
}
|
|
488
477
|
|
|
@@ -498,13 +487,13 @@ class Runner extends Emittery {
|
|
|
498
487
|
|
|
499
488
|
// Note that the hooks and tests always begin running asynchronously.
|
|
500
489
|
const beforePromise = this.runHooks(this.tasks.before, contextRef);
|
|
501
|
-
const serialPromise = beforePromise.then(beforeHooksOk => {
|
|
490
|
+
const serialPromise = beforePromise.then(beforeHooksOk => {
|
|
502
491
|
// Don't run tests if a `before` hook failed.
|
|
503
492
|
if (!beforeHooksOk) {
|
|
504
493
|
return false;
|
|
505
494
|
}
|
|
506
495
|
|
|
507
|
-
return serialTests.reduce(async (previous, task) => { // eslint-disable-line unicorn/no-reduce
|
|
496
|
+
return serialTests.reduce(async (previous, task) => { // eslint-disable-line unicorn/no-array-reduce
|
|
508
497
|
const previousOk = await previous;
|
|
509
498
|
// Don't start tests after an interrupt.
|
|
510
499
|
if (this.interrupted) {
|
|
@@ -520,7 +509,7 @@ class Runner extends Emittery {
|
|
|
520
509
|
return this.runTest(task, contextRef.copy());
|
|
521
510
|
}, true);
|
|
522
511
|
});
|
|
523
|
-
const concurrentPromise = Promise.all([beforePromise, serialPromise]).then(async ([beforeHooksOk, serialOk]) => {
|
|
512
|
+
const concurrentPromise = Promise.all([beforePromise, serialPromise]).then(async ([beforeHooksOk, serialOk]) => {
|
|
524
513
|
// Don't run tests if a `before` hook failed, or if `failFast` is enabled
|
|
525
514
|
// and a previous serial test failed.
|
|
526
515
|
if (!beforeHooksOk || (!serialOk && this.failFast)) {
|
|
@@ -534,9 +523,7 @@ class Runner extends Emittery {
|
|
|
534
523
|
|
|
535
524
|
// If a concurrent test fails, even if `failFast` is enabled it won't
|
|
536
525
|
// stop other concurrent tests from running.
|
|
537
|
-
const allOkays = await Promise.all(concurrentTests.map(task =>
|
|
538
|
-
return this.runTest(task, contextRef.copy());
|
|
539
|
-
}));
|
|
526
|
+
const allOkays = await Promise.all(concurrentTests.map(task => this.runTest(task, contextRef.copy())));
|
|
540
527
|
return allOkays.every(ok => ok);
|
|
541
528
|
});
|
|
542
529
|
|
|
@@ -563,5 +550,3 @@ class Runner extends Emittery {
|
|
|
563
550
|
this.interrupted = true;
|
|
564
551
|
}
|
|
565
552
|
}
|
|
566
|
-
|
|
567
|
-
module.exports = Runner;
|
package/lib/scheduler.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import writeFileAtomic from 'write-file-atomic';
|
|
5
|
+
|
|
6
|
+
import isCi from './is-ci.js';
|
|
7
|
+
|
|
8
|
+
const FILENAME = 'failing-tests.json';
|
|
9
|
+
|
|
10
|
+
const scheduler = {
|
|
11
|
+
storeFailedTestFiles(runStatus, cacheDir) {
|
|
12
|
+
if (isCi || !cacheDir) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
writeFileAtomic.sync(path.join(cacheDir, FILENAME), JSON.stringify(runStatus.getFailedTestFiles()));
|
|
18
|
+
} catch {}
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
// Order test-files, so that files with failing tests come first
|
|
22
|
+
failingTestsFirst(selectedFiles, cacheDir, cacheEnabled) {
|
|
23
|
+
if (isCi || cacheEnabled === false) {
|
|
24
|
+
return selectedFiles;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const filePath = path.join(cacheDir, FILENAME);
|
|
28
|
+
let failedTestFiles;
|
|
29
|
+
try {
|
|
30
|
+
failedTestFiles = JSON.parse(fs.readFileSync(filePath));
|
|
31
|
+
} catch {
|
|
32
|
+
return selectedFiles;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return [...selectedFiles].sort((f, s) => {
|
|
36
|
+
if (failedTestFiles.includes(f) && failedTestFiles.includes(s)) {
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (failedTestFiles.includes(f)) {
|
|
41
|
+
return -1;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (failedTestFiles.includes(s)) {
|
|
45
|
+
return 1;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return 0;
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export default scheduler;
|