ava 7.0.0 → 8.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/{eslint-plugin-helper.cjs → eslint-plugin-helper.js} +10 -13
- package/entrypoints/{internal.d.mts → internal.d.ts} +1 -1
- package/entrypoints/{main.d.mts → main.d.ts} +5 -5
- package/entrypoints/{main.mjs → main.js} +1 -1
- package/entrypoints/{plugin.d.cts → plugin.d.ts} +2 -2
- package/entrypoints/plugin.js +1 -0
- package/index.d.ts +2 -2
- package/lib/api-event-iterator.js +2 -2
- package/lib/api.js +6 -6
- package/lib/assert.js +6 -5
- package/lib/cli.js +7 -27
- package/lib/create-chain.js +68 -25
- package/lib/extensions.js +5 -7
- package/lib/fork.js +3 -3
- package/lib/{glob-helpers.cjs → glob-helpers.js} +16 -30
- package/lib/globs.js +3 -3
- package/lib/{ipc-flow-control.cjs → ipc-flow-control.js} +1 -4
- package/lib/load-config.js +2 -3
- package/lib/{now-and-timers.cjs → now-and-timers.js} +11 -8
- package/lib/pkg.js +1 -0
- package/lib/plugin-support/shared-worker-loader.js +2 -2
- package/lib/plugin-support/shared-workers.js +3 -3
- package/lib/provider-manager.js +6 -5
- package/lib/reporters/default.js +1 -1
- package/lib/reporters/improper-usage-messages.js +1 -1
- package/lib/reporters/tap.js +10 -4
- package/lib/runner.js +5 -5
- package/lib/scheduler.js +1 -1
- package/lib/snapshot-manager.js +2 -2
- package/lib/test.js +15 -8
- package/lib/watcher.js +10 -18
- package/lib/worker/base.js +17 -41
- package/lib/worker/{channel.cjs → channel.js} +21 -25
- package/lib/worker/completion-handlers.js +3 -3
- package/lib/worker/{guard-environment.cjs → guard-environment.js} +4 -5
- package/lib/worker/line-numbers.js +3 -7
- package/lib/worker/main.js +11 -0
- package/lib/worker/{options.cjs → options.js} +4 -5
- package/lib/worker/{plugin.cjs → plugin.js} +8 -10
- package/lib/worker/state.js +5 -0
- package/lib/worker/utils.js +5 -0
- package/package.json +28 -36
- package/plugin.d.ts +1 -1
- package/types/{test-fn.d.cts → test-fn.d.ts} +24 -3
- package/types/{try-fn.d.cts → try-fn.d.ts} +1 -2
- package/entrypoints/main.cjs +0 -2
- package/entrypoints/main.d.cts +0 -12
- package/entrypoints/plugin.cjs +0 -2
- package/entrypoints/plugin.d.mts +0 -6
- package/entrypoints/plugin.mjs +0 -4
- package/lib/module-types.js +0 -85
- package/lib/pkg.cjs +0 -2
- package/lib/slash.cjs +0 -36
- package/lib/worker/main.cjs +0 -12
- package/lib/worker/state.cjs +0 -6
- package/lib/worker/utils.cjs +0 -6
- /package/entrypoints/{cli.mjs → cli.js} +0 -0
- /package/types/{assertions.d.cts → assertions.d.ts} +0 -0
- /package/types/{shared-worker.d.cts → shared-worker.d.ts} +0 -0
- /package/types/{state-change-events.d.cts → state-change-events.d.ts} +0 -0
- /package/types/{subscribable.d.cts → subscribable.d.ts} +0 -0
|
@@ -2,7 +2,7 @@ import {EventEmitter, on} from 'node:events';
|
|
|
2
2
|
import process from 'node:process';
|
|
3
3
|
import {workerData, parentPort, threadId} from 'node:worker_threads';
|
|
4
4
|
|
|
5
|
-
import pkg from '../pkg.
|
|
5
|
+
import pkg from '../pkg.js';
|
|
6
6
|
|
|
7
7
|
// Used to forward messages received over the `parentPort` and any direct ports
|
|
8
8
|
// to test workers. Every subscription adds a listener, so do not enforce any
|
|
@@ -199,7 +199,7 @@ try {
|
|
|
199
199
|
|
|
200
200
|
// Run possibly asynchronous release functions serially, in reverse
|
|
201
201
|
// order. Any error will crash the worker.
|
|
202
|
-
for await (const fn of [...teardownFns].
|
|
202
|
+
for await (const fn of [...teardownFns].toReversed()) {
|
|
203
203
|
await fn();
|
|
204
204
|
}
|
|
205
205
|
|
|
@@ -84,7 +84,7 @@ export async function observeWorkerProcess(fork, runStatus) {
|
|
|
84
84
|
}
|
|
85
85
|
};
|
|
86
86
|
|
|
87
|
-
fork.promise.finally(() => {
|
|
87
|
+
fork.promise.finally(() => {
|
|
88
88
|
removeAllInstances();
|
|
89
89
|
});
|
|
90
90
|
|
|
@@ -99,7 +99,7 @@ export async function observeWorkerProcess(fork, runStatus) {
|
|
|
99
99
|
}
|
|
100
100
|
};
|
|
101
101
|
|
|
102
|
-
launched.statePromises.error.then(error => {
|
|
102
|
+
launched.statePromises.error.then(error => {
|
|
103
103
|
launched.worker.off('message', handleWorkerMessage);
|
|
104
104
|
removeAllInstances();
|
|
105
105
|
runStatus.emitStateChange({type: 'shared-worker-error', err: serializeError(error)});
|
|
@@ -118,7 +118,7 @@ export async function observeWorkerProcess(fork, runStatus) {
|
|
|
118
118
|
port,
|
|
119
119
|
}, [port]);
|
|
120
120
|
|
|
121
|
-
fork.promise.finally(() => {
|
|
121
|
+
fork.promise.finally(() => {
|
|
122
122
|
launched.worker.postMessage({
|
|
123
123
|
type: 'deregister-test-worker',
|
|
124
124
|
id: fork.threadId,
|
package/lib/provider-manager.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import * as globs from './globs.js';
|
|
2
|
-
import pkg from './pkg.
|
|
2
|
+
import pkg from './pkg.js';
|
|
3
3
|
|
|
4
|
+
// Provides an integer representation of the protocol level. This is internal to a particular AVA installation, and
|
|
5
|
+
// allows other parts of AVA to assert minimum protocol levels for certain features without having to hardcode
|
|
6
|
+
// identifier strings. Integer values can be reused across protocols when older identifiers are removed.
|
|
4
7
|
export const levels = {
|
|
5
|
-
|
|
6
|
-
// compatible with different versions.
|
|
7
|
-
ava6: 1,
|
|
8
|
+
ava8: 1,
|
|
8
9
|
};
|
|
9
10
|
|
|
10
11
|
const levelsByProtocol = Object.assign(Object.create(null), {
|
|
11
|
-
'ava-
|
|
12
|
+
'ava-8': levels.ava8,
|
|
12
13
|
});
|
|
13
14
|
|
|
14
15
|
async function load(providerModule, projectDir, selectProtocol = () => true) {
|
package/lib/reporters/default.js
CHANGED
|
@@ -148,7 +148,7 @@ export default class Reporter {
|
|
|
148
148
|
this.prefixTitle = (testFile, title) => prefixTitle(this.extensions, plan.filePathPrefix, testFile, title);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
this.removePreviousListener = plan.status.on('stateChange', evt => {
|
|
151
|
+
this.removePreviousListener = plan.status.on('stateChange', ({data: evt}) => {
|
|
152
152
|
this.consumeStateChange(evt);
|
|
153
153
|
});
|
|
154
154
|
|
package/lib/reporters/tap.js
CHANGED
|
@@ -24,7 +24,13 @@ function dumpError({
|
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
originalError.
|
|
27
|
+
if (originalError && Object.getOwnPropertyDescriptor(originalError, 'name')?.writable !== false) {
|
|
28
|
+
try {
|
|
29
|
+
originalError.name = name; // Restore the original name.
|
|
30
|
+
} catch {
|
|
31
|
+
// Ignore
|
|
32
|
+
}
|
|
33
|
+
}
|
|
28
34
|
|
|
29
35
|
if (type === 'ava') {
|
|
30
36
|
if (assertion) {
|
|
@@ -46,8 +52,6 @@ function dumpError({
|
|
|
46
52
|
|
|
47
53
|
export default class TapReporter {
|
|
48
54
|
constructor(options) {
|
|
49
|
-
this.i = 0;
|
|
50
|
-
|
|
51
55
|
this.extensions = options.extensions;
|
|
52
56
|
this.stdStream = options.stdStream;
|
|
53
57
|
this.reportStream = options.reportStream;
|
|
@@ -65,7 +69,7 @@ export default class TapReporter {
|
|
|
65
69
|
this.prefixTitle = (testFile, title) => prefixTitle(this.extensions, plan.filePathPrefix, testFile, title);
|
|
66
70
|
}
|
|
67
71
|
|
|
68
|
-
plan.status.on('stateChange', evt => this.consumeStateChange(evt));
|
|
72
|
+
plan.status.on('stateChange', ({data: evt}) => this.consumeStateChange(evt));
|
|
69
73
|
|
|
70
74
|
this.reportStream.write(supertap.start() + os.EOL);
|
|
71
75
|
}
|
|
@@ -259,4 +263,6 @@ export default class TapReporter {
|
|
|
259
263
|
}
|
|
260
264
|
}
|
|
261
265
|
}
|
|
266
|
+
|
|
267
|
+
i = 0;
|
|
262
268
|
}
|
package/lib/runner.js
CHANGED
|
@@ -10,7 +10,7 @@ import parseTestArgs from './parse-test-args.js';
|
|
|
10
10
|
import serializeError from './serialize-error.js';
|
|
11
11
|
import {load as loadSnapshots, determineSnapshotDir} from './snapshot-manager.js';
|
|
12
12
|
import Runnable from './test.js';
|
|
13
|
-
import {waitForReady} from './worker/state.
|
|
13
|
+
import {waitForReady} from './worker/state.js';
|
|
14
14
|
|
|
15
15
|
const makeFileURL = file => file.startsWith('file://') ? file : pathToFileURL(file).toString();
|
|
16
16
|
|
|
@@ -254,7 +254,7 @@ export default class Runner extends Emittery {
|
|
|
254
254
|
let waitForSerial = Promise.resolve();
|
|
255
255
|
await runnables.reduce((previous, runnable) => { // eslint-disable-line unicorn/no-array-reduce
|
|
256
256
|
if (runnable.metadata.serial || this.serial) {
|
|
257
|
-
waitForSerial = previous.then(() =>
|
|
257
|
+
waitForSerial = previous.then(() =>
|
|
258
258
|
// Serial runnables run as long as there was no previous failure, unless
|
|
259
259
|
// the runnable should always be run.
|
|
260
260
|
(allPassed || runnable.metadata.always) && runAndStoreResult(runnable));
|
|
@@ -263,7 +263,7 @@ export default class Runner extends Emittery {
|
|
|
263
263
|
|
|
264
264
|
return Promise.all([
|
|
265
265
|
previous,
|
|
266
|
-
waitForSerial.then(() =>
|
|
266
|
+
waitForSerial.then(() =>
|
|
267
267
|
// Concurrent runnables are kicked off after the previous serial
|
|
268
268
|
// runnables have completed, as long as there was no previous failure
|
|
269
269
|
// (or if the runnable should always be run). One concurrent runnable's
|
|
@@ -476,7 +476,7 @@ export default class Runner extends Emittery {
|
|
|
476
476
|
|
|
477
477
|
// Note that the hooks and tests always begin running asynchronously.
|
|
478
478
|
const beforePromise = this.runHooks(this.tasks.before, contextRef);
|
|
479
|
-
const serialPromise = beforePromise.then(beforeHooksOk => {
|
|
479
|
+
const serialPromise = beforePromise.then(beforeHooksOk => {
|
|
480
480
|
// Don't run tests if a `before` hook failed.
|
|
481
481
|
if (!beforeHooksOk) {
|
|
482
482
|
return false;
|
|
@@ -498,7 +498,7 @@ export default class Runner extends Emittery {
|
|
|
498
498
|
return this.runTest(task, contextRef.copy());
|
|
499
499
|
}, true);
|
|
500
500
|
});
|
|
501
|
-
const concurrentPromise = Promise.all([beforePromise, serialPromise]).then(async ([beforeHooksOk, serialOk]) => {
|
|
501
|
+
const concurrentPromise = Promise.all([beforePromise, serialPromise]).then(async ([beforeHooksOk, serialOk]) => {
|
|
502
502
|
// Don't run tests if a `before` hook failed, or if `failFast` is enabled
|
|
503
503
|
// and a previous serial test failed.
|
|
504
504
|
if (!beforeHooksOk || (!serialOk && this.failFast)) {
|
package/lib/scheduler.js
CHANGED
package/lib/snapshot-manager.js
CHANGED
|
@@ -10,10 +10,10 @@ import cbor from 'cbor';
|
|
|
10
10
|
import concordance from 'concordance';
|
|
11
11
|
import indentString from 'indent-string';
|
|
12
12
|
import memoize from 'memoize';
|
|
13
|
+
import slash from 'slash';
|
|
13
14
|
import writeFileAtomic from 'write-file-atomic';
|
|
14
15
|
|
|
15
16
|
import {snapshotManager as concordanceOptions} from './concordance-options.js';
|
|
16
|
-
import slash from './slash.cjs';
|
|
17
17
|
|
|
18
18
|
// Increment if encoding layout or Concordance serialization versions change. Previous AVA versions will not be able to
|
|
19
19
|
// decode buffers generated by a newer version, so changing this value will require a major version bump of AVA itself.
|
|
@@ -160,7 +160,7 @@ class BufferBuilder {
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
function sortBlocks(blocksByTitle, blockIndices) {
|
|
163
|
-
return [...blocksByTitle].
|
|
163
|
+
return [...blocksByTitle].toSorted(([aTitle], [bTitle]) => {
|
|
164
164
|
const a = blockIndices.get(aTitle);
|
|
165
165
|
const b = blockIndices.get(bTitle);
|
|
166
166
|
|
package/lib/test.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
AssertionError, Assertions, checkAssertionMessage, getAssertionStack,
|
|
7
7
|
} from './assert.js';
|
|
8
8
|
import concordanceOptions from './concordance-options.js';
|
|
9
|
-
import nowAndTimers from './now-and-timers.
|
|
9
|
+
import * as nowAndTimers from './now-and-timers.js';
|
|
10
10
|
import parseTestArgs from './parse-test-args.js';
|
|
11
11
|
|
|
12
12
|
function isExternalAssertError(error) {
|
|
@@ -43,8 +43,8 @@ class ExecutionContext extends Assertions {
|
|
|
43
43
|
test.countPassedAssertion();
|
|
44
44
|
return true;
|
|
45
45
|
},
|
|
46
|
-
pending(promise) {
|
|
47
|
-
test.addPendingAssertion(promise);
|
|
46
|
+
pending(promise, assertionStack) {
|
|
47
|
+
test.addPendingAssertion(promise, assertionStack);
|
|
48
48
|
},
|
|
49
49
|
fail(error) {
|
|
50
50
|
return test.addFailedAssertion(error);
|
|
@@ -293,6 +293,7 @@ export default class Test {
|
|
|
293
293
|
this.finishDueToTimeout = null;
|
|
294
294
|
this.finishing = false;
|
|
295
295
|
this.pendingAssertionCount = 0;
|
|
296
|
+
this.pendingAssertionMetadata = new Set();
|
|
296
297
|
this.pendingAttemptCount = 0;
|
|
297
298
|
this.planCount = null;
|
|
298
299
|
this.startedAt = 0;
|
|
@@ -321,7 +322,7 @@ export default class Test {
|
|
|
321
322
|
this.logs.push(text);
|
|
322
323
|
}
|
|
323
324
|
|
|
324
|
-
async addPendingAssertion(promise) {
|
|
325
|
+
async addPendingAssertion(promise, assertionStack) {
|
|
325
326
|
if (this.finishing) {
|
|
326
327
|
this.saveFirstError(new Error('Assertion started, but test has already finished'));
|
|
327
328
|
}
|
|
@@ -332,6 +333,8 @@ export default class Test {
|
|
|
332
333
|
|
|
333
334
|
this.assertCount++;
|
|
334
335
|
this.pendingAssertionCount++;
|
|
336
|
+
const metadata = {assertionStack};
|
|
337
|
+
this.pendingAssertionMetadata.add(metadata);
|
|
335
338
|
this.refreshTimeout();
|
|
336
339
|
|
|
337
340
|
try {
|
|
@@ -340,6 +343,7 @@ export default class Test {
|
|
|
340
343
|
// Ignore errors.
|
|
341
344
|
} finally {
|
|
342
345
|
this.pendingAssertionCount--;
|
|
346
|
+
this.pendingAssertionMetadata.delete(metadata);
|
|
343
347
|
this.refreshTimeout();
|
|
344
348
|
}
|
|
345
349
|
}
|
|
@@ -474,7 +478,7 @@ export default class Test {
|
|
|
474
478
|
}
|
|
475
479
|
|
|
476
480
|
async runTeardowns() {
|
|
477
|
-
const teardowns =
|
|
481
|
+
const teardowns = this.teardowns.toReversed();
|
|
478
482
|
|
|
479
483
|
for (const teardown of teardowns) {
|
|
480
484
|
try {
|
|
@@ -505,7 +509,10 @@ export default class Test {
|
|
|
505
509
|
}
|
|
506
510
|
|
|
507
511
|
if (this.pendingAssertionCount > 0) {
|
|
508
|
-
|
|
512
|
+
const [first] = this.pendingAssertionMetadata;
|
|
513
|
+
this.saveFirstError(new AssertionError('Test finished, but an assertion was not awaited', {
|
|
514
|
+
assertionStack: first?.assertionStack ?? '',
|
|
515
|
+
}));
|
|
509
516
|
return;
|
|
510
517
|
}
|
|
511
518
|
|
|
@@ -590,7 +597,7 @@ export default class Test {
|
|
|
590
597
|
};
|
|
591
598
|
|
|
592
599
|
promise
|
|
593
|
-
.catch(error => {
|
|
600
|
+
.catch(error => {
|
|
594
601
|
if (this.testFailure !== null && error === this.testFailure) {
|
|
595
602
|
return;
|
|
596
603
|
}
|
|
@@ -607,7 +614,7 @@ export default class Test {
|
|
|
607
614
|
}));
|
|
608
615
|
}
|
|
609
616
|
})
|
|
610
|
-
.then(() => resolve(this.finish()));
|
|
617
|
+
.then(() => resolve(this.finish()));
|
|
611
618
|
});
|
|
612
619
|
}
|
|
613
620
|
|
package/lib/watcher.js
CHANGED
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
applyTestFileFilter, classify, buildIgnoreMatcher, findTests,
|
|
13
13
|
normalizePattern,
|
|
14
14
|
} from './globs.js';
|
|
15
|
-
import {levels as providerLevels} from './provider-manager.js';
|
|
16
15
|
|
|
17
16
|
const debug = createDebug('ava:watcher');
|
|
18
17
|
|
|
@@ -158,7 +157,6 @@ const promptForMatchPattern = async (reporter, lineReader, currentPattern) => {
|
|
|
158
157
|
};
|
|
159
158
|
|
|
160
159
|
export async function start({api, filter, globs, projectDir, providers, reporter, stdin, signal}) {
|
|
161
|
-
providers = providers.filter(({level}) => level >= providerLevels.ava6);
|
|
162
160
|
for await (const {files, testFileSelector, ...runtimeOptions} of plan({
|
|
163
161
|
api,
|
|
164
162
|
filter,
|
|
@@ -210,7 +208,7 @@ async function * plan({
|
|
|
210
208
|
};
|
|
211
209
|
|
|
212
210
|
// Begin a file trace in the background.
|
|
213
|
-
fileTracer.update(findTests(cwdAndGlobs).then(testFiles => testFiles.map(path => ({
|
|
211
|
+
fileTracer.update(findTests(cwdAndGlobs).then(testFiles => testFiles.map(path => ({
|
|
214
212
|
path: nodePath.relative(projectDir, path),
|
|
215
213
|
isTest: true,
|
|
216
214
|
exists: true,
|
|
@@ -231,8 +229,8 @@ async function * plan({
|
|
|
231
229
|
};
|
|
232
230
|
|
|
233
231
|
// Observe all test runs.
|
|
234
|
-
api.on('run', ({status}) => {
|
|
235
|
-
status.on('stateChange', evt => {
|
|
232
|
+
api.on('run', ({data: {status}}) => {
|
|
233
|
+
status.on('stateChange', ({data: evt}) => {
|
|
236
234
|
switch (evt.type) {
|
|
237
235
|
case 'accessed-snapshots': {
|
|
238
236
|
fileTracer.addDependency(nodePath.relative(projectDir, evt.testFile), nodePath.relative(projectDir, evt.filename));
|
|
@@ -258,8 +256,11 @@ async function * plan({
|
|
|
258
256
|
case 'uncaught-exception':
|
|
259
257
|
case 'unhandled-rejection':
|
|
260
258
|
case 'worker-failed': {
|
|
261
|
-
|
|
262
|
-
|
|
259
|
+
if (evt.testFile) {
|
|
260
|
+
const path = nodePath.relative(projectDir, evt.testFile);
|
|
261
|
+
failureCounts.set(path, 1 + (failureCounts.get(path) ?? 0));
|
|
262
|
+
}
|
|
263
|
+
|
|
263
264
|
break;
|
|
264
265
|
}
|
|
265
266
|
|
|
@@ -425,7 +426,7 @@ async function * plan({
|
|
|
425
426
|
// If the file tracer is still analyzing dependencies, wait for that to
|
|
426
427
|
// complete.
|
|
427
428
|
if (fileTracer.busy !== null) {
|
|
428
|
-
fileTracer.busy.then(() => debounce.refresh());
|
|
429
|
+
fileTracer.busy.then(() => debounce.refresh());
|
|
429
430
|
takeCoverageForSelfTests?.();
|
|
430
431
|
return;
|
|
431
432
|
}
|
|
@@ -707,15 +708,10 @@ class FileTracer {
|
|
|
707
708
|
#base;
|
|
708
709
|
#cache = Object.create(null);
|
|
709
710
|
#pendingTrace = null;
|
|
710
|
-
#updateRunning;
|
|
711
|
-
#signalUpdateRunning;
|
|
712
711
|
#tree = new Tree();
|
|
713
712
|
|
|
714
713
|
constructor({base}) {
|
|
715
714
|
this.#base = base;
|
|
716
|
-
this.#updateRunning = new Promise(resolve => {
|
|
717
|
-
this.#signalUpdateRunning = resolve;
|
|
718
|
-
});
|
|
719
715
|
}
|
|
720
716
|
|
|
721
717
|
get busy() {
|
|
@@ -761,12 +757,9 @@ class FileTracer {
|
|
|
761
757
|
}
|
|
762
758
|
|
|
763
759
|
update(changes) {
|
|
764
|
-
const current = this.#update(changes).finally(() => {
|
|
760
|
+
const current = this.#update(changes).finally(() => {
|
|
765
761
|
if (this.#pendingTrace === current) {
|
|
766
762
|
this.#pendingTrace = null;
|
|
767
|
-
this.#updateRunning = new Promise(resolve => {
|
|
768
|
-
this.#signalUpdateRunning = resolve;
|
|
769
|
-
});
|
|
770
763
|
}
|
|
771
764
|
});
|
|
772
765
|
|
|
@@ -775,7 +768,6 @@ class FileTracer {
|
|
|
775
768
|
|
|
776
769
|
async #update(changes) {
|
|
777
770
|
await this.#pendingTrace; // Guard against race conditions.
|
|
778
|
-
this.#signalUpdateRunning();
|
|
779
771
|
|
|
780
772
|
let reuseCache = true;
|
|
781
773
|
const knownTestFiles = new Set();
|
package/lib/worker/base.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {mkdir} from 'node:fs/promises';
|
|
2
|
-
import {createRequire} from 'node:module';
|
|
3
2
|
import path from 'node:path';
|
|
4
3
|
import process from 'node:process';
|
|
5
4
|
import {pathToFileURL} from 'node:url';
|
|
@@ -9,17 +8,17 @@ import setUpCurrentlyUnhandled from 'currently-unhandled';
|
|
|
9
8
|
import writeFileAtomic from 'write-file-atomic';
|
|
10
9
|
|
|
11
10
|
import {set as setChalk} from '../chalk.js';
|
|
12
|
-
import
|
|
11
|
+
import {setImmediate} from '../now-and-timers.js';
|
|
13
12
|
import providerManager from '../provider-manager.js';
|
|
14
13
|
import Runner from '../runner.js';
|
|
15
14
|
import serializeError from '../serialize-error.js';
|
|
16
15
|
|
|
17
|
-
import channel from './channel.
|
|
16
|
+
import * as channel from './channel.js';
|
|
18
17
|
import {runCompletionHandlers} from './completion-handlers.js';
|
|
19
18
|
import lineNumberSelection from './line-numbers.js';
|
|
20
|
-
import {set as setOptions} from './options.
|
|
21
|
-
import {flags, refs, sharedWorkerTeardowns} from './state.
|
|
22
|
-
import {isRunningInThread, isRunningInChildProcess} from './utils.
|
|
19
|
+
import {set as setOptions} from './options.js';
|
|
20
|
+
import {flags, refs, sharedWorkerTeardowns} from './state.js';
|
|
21
|
+
import {isRunningInThread, isRunningInChildProcess} from './utils.js';
|
|
23
22
|
|
|
24
23
|
const currentlyUnhandled = setUpCurrentlyUnhandled();
|
|
25
24
|
let runner;
|
|
@@ -88,14 +87,14 @@ const run = async options => {
|
|
|
88
87
|
|
|
89
88
|
refs.runnerChain = runner.chain;
|
|
90
89
|
|
|
91
|
-
channel.peerFailed.then(() => {
|
|
90
|
+
channel.peerFailed.then(() => {
|
|
92
91
|
runner.interrupt();
|
|
93
92
|
});
|
|
94
93
|
|
|
95
|
-
runner.on('accessed-snapshots', filename => channel.send({type: 'accessed-snapshots', filename}));
|
|
96
|
-
runner.on('stateChange', state => channel.send(state));
|
|
94
|
+
runner.on('accessed-snapshots', ({data: filename}) => channel.send({type: 'accessed-snapshots', filename}));
|
|
95
|
+
runner.on('stateChange', ({data: state}) => channel.send(state));
|
|
97
96
|
|
|
98
|
-
runner.on('error', error => {
|
|
97
|
+
runner.on('error', ({data: error}) => {
|
|
99
98
|
channel.send({type: 'internal-error', err: serializeError(error)});
|
|
100
99
|
forceExit();
|
|
101
100
|
});
|
|
@@ -129,7 +128,7 @@ const run = async options => {
|
|
|
129
128
|
await channel.workerFreed;
|
|
130
129
|
channel.unref();
|
|
131
130
|
|
|
132
|
-
|
|
131
|
+
setImmediate(() => {
|
|
133
132
|
const unhandled = currentlyUnhandled();
|
|
134
133
|
if (unhandled.length === 0) {
|
|
135
134
|
return avaIsDone();
|
|
@@ -151,10 +150,6 @@ const run = async options => {
|
|
|
151
150
|
// Store value to prevent required modules from modifying it.
|
|
152
151
|
const testPath = options.file;
|
|
153
152
|
|
|
154
|
-
const extensionsToLoadAsModules = Object.entries(options.moduleTypes)
|
|
155
|
-
.filter(([, type]) => type === 'module')
|
|
156
|
-
.map(([extension]) => extension);
|
|
157
|
-
|
|
158
153
|
// Install before processing options.require, so if helpers are added to the
|
|
159
154
|
// require configuration the *compiled* helper will be loaded.
|
|
160
155
|
const {projectDir, providerStates = []} = options;
|
|
@@ -162,26 +157,18 @@ const run = async options => {
|
|
|
162
157
|
await Promise.all(providerStates.map(async ({type, state, protocol}) => {
|
|
163
158
|
if (type === 'typescript') {
|
|
164
159
|
const provider = await providerManager.typescript(projectDir, {protocol});
|
|
165
|
-
providers.push(provider.worker({
|
|
160
|
+
providers.push(provider.worker({state}));
|
|
166
161
|
}
|
|
167
162
|
}));
|
|
168
163
|
|
|
169
|
-
const require = createRequire(import.meta.url);
|
|
170
164
|
const load = async ref => {
|
|
171
165
|
for (const provider of providers) {
|
|
172
166
|
if (provider.canLoad(ref)) {
|
|
173
|
-
return provider.load(ref
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
for (const extension of extensionsToLoadAsModules) {
|
|
178
|
-
if (ref.endsWith(`.${extension}`)) {
|
|
179
|
-
return import(pathToFileURL(ref));
|
|
167
|
+
return provider.load(ref);
|
|
180
168
|
}
|
|
181
169
|
}
|
|
182
170
|
|
|
183
|
-
|
|
184
|
-
return require(ref);
|
|
171
|
+
return import(pathToFileURL(ref));
|
|
185
172
|
};
|
|
186
173
|
|
|
187
174
|
const loadRequiredModule = async ref => {
|
|
@@ -189,21 +176,14 @@ const run = async options => {
|
|
|
189
176
|
// dependency.
|
|
190
177
|
for (const provider of providers) {
|
|
191
178
|
if (provider.canLoad(ref)) {
|
|
192
|
-
return provider.load(ref, {
|
|
179
|
+
return provider.load(ref, {});
|
|
193
180
|
}
|
|
194
181
|
}
|
|
195
182
|
|
|
196
183
|
// Try to load the module as a file, relative to the project directory.
|
|
197
|
-
// Match load() behavior.
|
|
198
184
|
const fullPath = path.resolve(projectDir, ref);
|
|
199
185
|
try {
|
|
200
|
-
|
|
201
|
-
if (fullPath.endsWith(`.${extension}`)) {
|
|
202
|
-
return await import(pathToFileURL(fullPath)); // eslint-disable-line no-await-in-loop
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return require(fullPath);
|
|
186
|
+
return await import(pathToFileURL(fullPath));
|
|
207
187
|
} catch (error) {
|
|
208
188
|
// If the module could not be found, assume it's not a file but a dependency.
|
|
209
189
|
if (error.code === 'ERR_MODULE_NOT_FOUND' || error.code === 'MODULE_NOT_FOUND') {
|
|
@@ -226,12 +206,8 @@ const run = async options => {
|
|
|
226
206
|
|
|
227
207
|
try {
|
|
228
208
|
for await (const [ref, ...args] of (options.require ?? [])) {
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
if (typeof loadedModule === 'function') { // CJS module
|
|
232
|
-
await loadedModule(...args);
|
|
233
|
-
} else if (typeof loadedModule.default === 'function') { // ES module, or exports.default from CJS
|
|
234
|
-
const {default: fn} = loadedModule;
|
|
209
|
+
const {default: fn} = await loadRequiredModule(ref);
|
|
210
|
+
if (typeof fn === 'function') {
|
|
235
211
|
await fn(...args);
|
|
236
212
|
}
|
|
237
213
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const {MessageChannel, threadId} = require('node:worker_threads');
|
|
1
|
+
import events from 'node:events';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import {MessageChannel, parentPort, threadId} from 'node:worker_threads';
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
import {controlFlow} from '../ipc-flow-control.js';
|
|
6
|
+
import {setTimeout as setTimeoutTimer} from '../now-and-timers.js';
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
import {isRunningInChildProcess, isRunningInThread} from './utils.js';
|
|
9
9
|
|
|
10
10
|
const selectAvaMessage = async (channel, type) => {
|
|
11
11
|
for await (const [message] of events.on(channel, 'message')) {
|
|
@@ -16,10 +16,6 @@ const selectAvaMessage = async (channel, type) => {
|
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
class RefCounter {
|
|
19
|
-
constructor() {
|
|
20
|
-
this.count = 0;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
19
|
refAndTest() {
|
|
24
20
|
return ++this.count === 1;
|
|
25
21
|
}
|
|
@@ -27,6 +23,8 @@ class RefCounter {
|
|
|
27
23
|
testAndUnref() {
|
|
28
24
|
return this.count > 0 && --this.count === 0;
|
|
29
25
|
}
|
|
26
|
+
|
|
27
|
+
count = 0;
|
|
30
28
|
}
|
|
31
29
|
|
|
32
30
|
class MessagePortHandle {
|
|
@@ -36,7 +34,7 @@ class MessagePortHandle {
|
|
|
36
34
|
this.channel = port;
|
|
37
35
|
// Referencing the port does not immediately prevent the thread from
|
|
38
36
|
// exiting. Use a timer to keep a reference for at least a second.
|
|
39
|
-
this.workaroundTimer =
|
|
37
|
+
this.workaroundTimer = setTimeoutTimer(() => {}, 1000).unref();
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
forceUnref() {
|
|
@@ -94,10 +92,8 @@ class IpcHandle {
|
|
|
94
92
|
|
|
95
93
|
let handle;
|
|
96
94
|
if (isRunningInChildProcess) {
|
|
97
|
-
const {controlFlow} = require('../ipc-flow-control.cjs');
|
|
98
95
|
handle = new IpcHandle(controlFlow(process));
|
|
99
96
|
} else if (isRunningInThread) {
|
|
100
|
-
const {parentPort} = require('node:worker_threads');
|
|
101
97
|
handle = new MessagePortHandle(parentPort);
|
|
102
98
|
}
|
|
103
99
|
|
|
@@ -105,12 +101,14 @@ if (isRunningInChildProcess) {
|
|
|
105
101
|
// Node.js. In order to keep track, explicitly reference before attaching.
|
|
106
102
|
handle.ref();
|
|
107
103
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
104
|
+
/* eslint-disable unicorn/prefer-top-level-await */
|
|
105
|
+
export const options = selectAvaMessage(handle.channel, 'options').then(message => message.ava.options);
|
|
106
|
+
export const peerFailed = selectAvaMessage(handle.channel, 'peer-failed');
|
|
107
|
+
export const workerFreed = selectAvaMessage(handle.channel, 'free-worker');
|
|
108
|
+
/* eslint-enable unicorn/prefer-top-level-await */
|
|
109
|
+
export const send = handle.send.bind(handle);
|
|
110
|
+
export const ref = handle.ref.bind(handle);
|
|
111
|
+
export const unref = handle.unref.bind(handle);
|
|
114
112
|
|
|
115
113
|
let channelCounter = 0;
|
|
116
114
|
let messageCounter = 0;
|
|
@@ -138,7 +136,7 @@ function createChannelEmitter(channelId) {
|
|
|
138
136
|
return [emitter, () => channelEmitters.delete(channelId)];
|
|
139
137
|
}
|
|
140
138
|
|
|
141
|
-
function registerSharedWorker(filename, initialData) {
|
|
139
|
+
export function registerSharedWorker(filename, initialData) {
|
|
142
140
|
const channelId = `${threadId}/channel/${++channelCounter}`;
|
|
143
141
|
|
|
144
142
|
const {port1: ourPort, port2: theirPort} = new MessageChannel();
|
|
@@ -160,9 +158,9 @@ function registerSharedWorker(filename, initialData) {
|
|
|
160
158
|
// The attaching of message listeners will cause the port to be referenced by
|
|
161
159
|
// Node.js. In order to keep track, explicitly reference before attaching.
|
|
162
160
|
sharedWorkerHandle.ref();
|
|
163
|
-
const ready = selectAvaMessage(ourPort, 'ready').then(() => {
|
|
161
|
+
const ready = selectAvaMessage(ourPort, 'ready').then(() => {
|
|
164
162
|
currentlyAvailable = error === null;
|
|
165
|
-
}).finally(() => {
|
|
163
|
+
}).finally(() => {
|
|
166
164
|
// Once ready, it's up to user code to subscribe to messages, which (see
|
|
167
165
|
// below) causes us to reference the port.
|
|
168
166
|
sharedWorkerHandle.unref();
|
|
@@ -172,7 +170,7 @@ function registerSharedWorker(filename, initialData) {
|
|
|
172
170
|
|
|
173
171
|
// Errors are received over the test worker channel, not the message port
|
|
174
172
|
// dedicated to the shared worker.
|
|
175
|
-
events.once(channelEmitter, 'shared-worker-error').then(() => {
|
|
173
|
+
events.once(channelEmitter, 'shared-worker-error').then(() => {
|
|
176
174
|
unsubscribe();
|
|
177
175
|
sharedWorkerHandle.forceUnref();
|
|
178
176
|
error = new Error('The shared worker is no longer available');
|
|
@@ -244,5 +242,3 @@ function registerSharedWorker(filename, initialData) {
|
|
|
244
242
|
},
|
|
245
243
|
};
|
|
246
244
|
}
|
|
247
|
-
|
|
248
|
-
exports.registerSharedWorker = registerSharedWorker;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import process from 'node:process';
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import {completionHandlers} from './state.js';
|
|
4
4
|
|
|
5
5
|
export function runCompletionHandlers() {
|
|
6
|
-
for (const handler of
|
|
6
|
+
for (const handler of completionHandlers) {
|
|
7
7
|
process.nextTick(() => handler());
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export function registerCompletionHandler(handler) {
|
|
12
|
-
|
|
12
|
+
completionHandlers.push(handler);
|
|
13
13
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const process = require('node:process');
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import process from 'node:process';
|
|
4
3
|
|
|
5
|
-
|
|
4
|
+
import {isRunningInThread, isRunningInChildProcess} from './utils.js';
|
|
6
5
|
|
|
7
6
|
// Check if the test is being run without AVA cli
|
|
8
7
|
if (!isRunningInChildProcess && !isRunningInThread) {
|
|
@@ -14,6 +13,6 @@ if (!isRunningInChildProcess && !isRunningInThread) {
|
|
|
14
13
|
|
|
15
14
|
process.exit(1); // eslint-disable-line unicorn/no-process-exit
|
|
16
15
|
} else {
|
|
17
|
-
throw new Error('The
|
|
16
|
+
throw new Error('The \u2018ava\u2019 module can only be imported in test files');
|
|
18
17
|
}
|
|
19
18
|
}
|