ava 4.0.0-alpha.1 → 4.0.1
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/{eslint-plugin-helper.js → entrypoints/eslint-plugin-helper.cjs} +20 -7
- 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 -709
- package/lib/api.js +95 -46
- package/lib/assert.js +122 -173
- package/lib/chalk.js +9 -14
- package/lib/cli.js +105 -97
- package/lib/code-excerpt.js +12 -17
- package/lib/concordance-options.js +30 -31
- package/lib/context-ref.js +3 -6
- package/lib/create-chain.js +32 -4
- package/lib/environment-variables.js +1 -4
- package/lib/eslint-plugin-helper-worker.js +16 -26
- package/lib/extensions.js +2 -2
- package/lib/fork.js +42 -83
- 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 +10 -17
- package/lib/load-config.js +62 -56
- package/lib/module-types.js +3 -3
- 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 -43
- package/lib/provider-manager.js +20 -14
- package/lib/reporters/beautify-stack.js +6 -11
- package/lib/reporters/colors.js +40 -15
- package/lib/reporters/default.js +115 -350
- 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 +15 -16
- package/lib/run-status.js +25 -23
- package/lib/runner.js +138 -127
- package/lib/scheduler.js +42 -36
- package/lib/serialize-error.js +34 -34
- package/lib/snapshot-manager.js +83 -76
- package/lib/test.js +114 -195
- package/lib/watcher.js +65 -40
- package/lib/worker/base.js +48 -99
- package/lib/worker/channel.cjs +290 -0
- package/lib/worker/dependency-tracker.js +22 -22
- package/lib/worker/guard-environment.cjs +19 -0
- package/lib/worker/line-numbers.js +57 -19
- package/lib/worker/main.cjs +12 -0
- package/lib/worker/{options.js → options.cjs} +0 -0
- package/lib/worker/{plugin.js → plugin.cjs} +31 -16
- package/lib/worker/state.cjs +5 -0
- package/lib/worker/{utils.js → utils.cjs} +1 -1
- package/package.json +60 -68
- package/plugin.d.ts +51 -53
- package/readme.md +5 -12
- 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/index.js +0 -8
- package/lib/worker/channel.js +0 -218
- package/lib/worker/main.js +0 -20
- package/plugin.js +0 -9
package/lib/worker/base.js
CHANGED
|
@@ -1,44 +1,34 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
1
|
+
import {createRequire} from 'node:module';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import {pathToFileURL} from 'node:url';
|
|
4
|
+
import {workerData} from 'node:worker_threads';
|
|
5
|
+
|
|
6
|
+
import setUpCurrentlyUnhandled from 'currently-unhandled';
|
|
7
|
+
|
|
8
|
+
import {set as setChalk} from '../chalk.js';
|
|
9
|
+
import nowAndTimers from '../now-and-timers.cjs';
|
|
10
|
+
import providerManager from '../provider-manager.js';
|
|
11
|
+
import Runner from '../runner.js';
|
|
12
|
+
import serializeError from '../serialize-error.js';
|
|
13
|
+
|
|
14
|
+
import channel from './channel.cjs';
|
|
15
|
+
import dependencyTracking from './dependency-tracker.js';
|
|
16
|
+
import lineNumberSelection from './line-numbers.js';
|
|
17
|
+
import {set as setOptions} from './options.cjs';
|
|
18
|
+
import {flags, refs, sharedWorkerTeardowns} from './state.cjs';
|
|
19
|
+
import {isRunningInThread, isRunningInChildProcess} from './utils.cjs';
|
|
21
20
|
|
|
22
|
-
const
|
|
21
|
+
const currentlyUnhandled = setUpCurrentlyUnhandled();
|
|
23
22
|
|
|
24
23
|
const run = async options => {
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
setOptions(options);
|
|
25
|
+
setChalk(options.chalkOptions);
|
|
27
26
|
|
|
28
27
|
if (options.chalkOptions.level > 0) {
|
|
29
28
|
const {stdout, stderr} = process;
|
|
30
29
|
global.console = Object.assign(global.console, new console.Console({stdout, stderr, colorMode: true}));
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
const nowAndTimers = require('../now-and-timers');
|
|
34
|
-
const providerManager = require('../provider-manager');
|
|
35
|
-
const Runner = require('../runner');
|
|
36
|
-
const serializeError = require('../serialize-error');
|
|
37
|
-
const dependencyTracking = require('./dependency-tracker');
|
|
38
|
-
const lineNumberSelection = require('./line-numbers');
|
|
39
|
-
|
|
40
|
-
const sharedWorkerTeardowns = [];
|
|
41
|
-
|
|
42
32
|
async function exit(code) {
|
|
43
33
|
if (!process.exitCode) {
|
|
44
34
|
process.exitCode = code;
|
|
@@ -46,16 +36,14 @@ const run = async options => {
|
|
|
46
36
|
|
|
47
37
|
dependencyTracking.flush();
|
|
48
38
|
await channel.flush();
|
|
49
|
-
process.exit();
|
|
39
|
+
process.exit(); // eslint-disable-line unicorn/no-process-exit
|
|
50
40
|
}
|
|
51
41
|
|
|
52
|
-
// TODO: Initialize providers here, then pass to lineNumberSelection() so they
|
|
53
|
-
// can be used to parse the test file.
|
|
54
42
|
let checkSelectedByLineNumbers;
|
|
55
43
|
try {
|
|
56
44
|
checkSelectedByLineNumbers = lineNumberSelection({
|
|
57
45
|
file: options.file,
|
|
58
|
-
lineNumbers: options.lineNumbers
|
|
46
|
+
lineNumbers: options.lineNumbers,
|
|
59
47
|
});
|
|
60
48
|
} catch (error) {
|
|
61
49
|
channel.send({type: 'line-number-selection-error', err: serializeError('Line number selection error', false, error, options.file)});
|
|
@@ -74,18 +62,13 @@ const run = async options => {
|
|
|
74
62
|
runOnlyExclusive: options.runOnlyExclusive,
|
|
75
63
|
serial: options.serial,
|
|
76
64
|
snapshotDir: options.snapshotDir,
|
|
77
|
-
updateSnapshots: options.updateSnapshots
|
|
65
|
+
updateSnapshots: options.updateSnapshots,
|
|
78
66
|
});
|
|
79
67
|
|
|
80
|
-
|
|
81
|
-
runner.interrupt();
|
|
82
|
-
});
|
|
68
|
+
refs.runnerChain = runner.chain;
|
|
83
69
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (runner.attributeLeakedError(reason)) {
|
|
87
|
-
attributedRejections.add(promise);
|
|
88
|
-
}
|
|
70
|
+
channel.peerFailed.then(() => {
|
|
71
|
+
runner.interrupt();
|
|
89
72
|
});
|
|
90
73
|
|
|
91
74
|
runner.on('dependency', dependencyTracking.track);
|
|
@@ -98,7 +81,7 @@ const run = async options => {
|
|
|
98
81
|
|
|
99
82
|
runner.on('finish', async () => {
|
|
100
83
|
try {
|
|
101
|
-
const {touchedFiles} = runner.saveSnapshotState();
|
|
84
|
+
const {touchedFiles} = await runner.saveSnapshotState();
|
|
102
85
|
if (touchedFiles) {
|
|
103
86
|
channel.send({type: 'touched-files', files: touchedFiles});
|
|
104
87
|
}
|
|
@@ -117,7 +100,7 @@ const run = async options => {
|
|
|
117
100
|
}
|
|
118
101
|
|
|
119
102
|
nowAndTimers.setImmediate(() => {
|
|
120
|
-
for (const rejection of currentlyUnhandled()
|
|
103
|
+
for (const rejection of currentlyUnhandled()) {
|
|
121
104
|
channel.send({type: 'unhandled-rejection', err: serializeError('Unhandled rejection', true, rejection.reason, runner.file)});
|
|
122
105
|
}
|
|
123
106
|
|
|
@@ -126,43 +109,13 @@ const run = async options => {
|
|
|
126
109
|
});
|
|
127
110
|
|
|
128
111
|
process.on('uncaughtException', error => {
|
|
129
|
-
if (runner.attributeLeakedError(error)) {
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
112
|
channel.send({type: 'uncaught-exception', err: serializeError('Uncaught exception', true, error, runner.file)});
|
|
134
113
|
exit(1);
|
|
135
114
|
});
|
|
136
115
|
|
|
137
|
-
let accessedRunner = false;
|
|
138
|
-
exports.getRunner = () => {
|
|
139
|
-
accessedRunner = true;
|
|
140
|
-
return runner;
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
exports.registerSharedWorker = (filename, initialData, teardown) => {
|
|
144
|
-
const {channel: sharedWorkerChannel, forceUnref, ready} = channel.registerSharedWorker(filename, initialData);
|
|
145
|
-
runner.waitForReady.push(ready);
|
|
146
|
-
sharedWorkerTeardowns.push(async () => {
|
|
147
|
-
try {
|
|
148
|
-
await teardown();
|
|
149
|
-
} finally {
|
|
150
|
-
forceUnref();
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
return sharedWorkerChannel;
|
|
154
|
-
};
|
|
155
|
-
|
|
156
116
|
// Store value to prevent required modules from modifying it.
|
|
157
117
|
const testPath = options.file;
|
|
158
118
|
|
|
159
|
-
// Install basic source map support.
|
|
160
|
-
const sourceMapSupport = require('source-map-support');
|
|
161
|
-
sourceMapSupport.install({
|
|
162
|
-
environment: 'node',
|
|
163
|
-
handleUncaughtExceptions: false
|
|
164
|
-
});
|
|
165
|
-
|
|
166
119
|
const extensionsToLoadAsModules = Object.entries(options.moduleTypes)
|
|
167
120
|
.filter(([, type]) => type === 'module')
|
|
168
121
|
.map(([extension]) => extension);
|
|
@@ -170,33 +123,29 @@ const run = async options => {
|
|
|
170
123
|
// Install before processing options.require, so if helpers are added to the
|
|
171
124
|
// require configuration the *compiled* helper will be loaded.
|
|
172
125
|
const {projectDir, providerStates = []} = options;
|
|
173
|
-
const providers =
|
|
174
|
-
|
|
175
|
-
const provider = providerManager.babel(projectDir).worker({extensionsToLoadAsModules, state});
|
|
176
|
-
runner.powerAssert = provider.powerAssert;
|
|
177
|
-
return provider;
|
|
178
|
-
}
|
|
179
|
-
|
|
126
|
+
const providers = [];
|
|
127
|
+
await Promise.all(providerStates.map(async ({type, state}) => {
|
|
180
128
|
if (type === 'typescript') {
|
|
181
|
-
|
|
129
|
+
const provider = await providerManager.typescript(projectDir);
|
|
130
|
+
providers.push(provider.worker({extensionsToLoadAsModules, state}));
|
|
182
131
|
}
|
|
132
|
+
}));
|
|
183
133
|
|
|
184
|
-
|
|
185
|
-
}).filter(provider => provider !== null);
|
|
186
|
-
|
|
134
|
+
const require = createRequire(import.meta.url);
|
|
187
135
|
const load = async ref => {
|
|
188
|
-
for (const extension of extensionsToLoadAsModules) {
|
|
189
|
-
if (ref.endsWith(`.${extension}`)) {
|
|
190
|
-
return import(pathToFileURL(ref)); // eslint-disable-line node/no-unsupported-features/es-syntax
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
136
|
for (const provider of providers) {
|
|
195
137
|
if (provider.canLoad(ref)) {
|
|
196
138
|
return provider.load(ref, {requireFn: require});
|
|
197
139
|
}
|
|
198
140
|
}
|
|
199
141
|
|
|
142
|
+
for (const extension of extensionsToLoadAsModules) {
|
|
143
|
+
if (ref.endsWith(`.${extension}`)) {
|
|
144
|
+
return import(pathToFileURL(ref)); // eslint-disable-line node/no-unsupported-features/es-syntax
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// We still support require() since it's more easily monkey-patched.
|
|
200
149
|
return require(ref);
|
|
201
150
|
};
|
|
202
151
|
|
|
@@ -207,12 +156,12 @@ const run = async options => {
|
|
|
207
156
|
|
|
208
157
|
// Install dependency tracker after the require configuration has been evaluated
|
|
209
158
|
// to make sure we also track dependencies with custom require hooks
|
|
210
|
-
dependencyTracking.install(testPath);
|
|
159
|
+
dependencyTracking.install(require.extensions, testPath);
|
|
211
160
|
|
|
212
161
|
if (options.debug && options.debug.port !== undefined && options.debug.host !== undefined) {
|
|
213
162
|
// If an inspector was active when the main process started, and is
|
|
214
163
|
// already active for the worker process, do not open a new one.
|
|
215
|
-
const inspector =
|
|
164
|
+
const {default: inspector} = await import('node:inspector'); // eslint-disable-line node/no-unsupported-features/es-syntax
|
|
216
165
|
if (!options.debug.active || inspector.url() === undefined) {
|
|
217
166
|
inspector.open(options.debug.port, options.debug.host, true);
|
|
218
167
|
}
|
|
@@ -224,7 +173,7 @@ const run = async options => {
|
|
|
224
173
|
|
|
225
174
|
await load(testPath);
|
|
226
175
|
|
|
227
|
-
if (
|
|
176
|
+
if (flags.loadedMain) {
|
|
228
177
|
// Unreference the channel if the test file required AVA. This stops it
|
|
229
178
|
// from keeping the event loop busy, which means the `beforeExit` event can be
|
|
230
179
|
// used to detect when tests stall.
|
|
@@ -249,11 +198,11 @@ const onError = error => {
|
|
|
249
198
|
};
|
|
250
199
|
|
|
251
200
|
if (isRunningInThread) {
|
|
252
|
-
|
|
201
|
+
channel.send({type: 'starting'}); // AVA won't terminate the worker thread until it's seen this message.
|
|
253
202
|
const {options} = workerData;
|
|
254
203
|
delete workerData.options; // Don't allow user code access.
|
|
255
204
|
run(options).catch(onError);
|
|
256
205
|
} else if (isRunningInChildProcess) {
|
|
257
206
|
channel.send({type: 'ready-for-options'});
|
|
258
|
-
channel.options.then(run).catch(onError);
|
|
207
|
+
channel.options.then(run).catch(onError);
|
|
259
208
|
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const events = require('events');
|
|
3
|
+
const process = require('process');
|
|
4
|
+
const {MessageChannel, threadId} = require('worker_threads');
|
|
5
|
+
|
|
6
|
+
const timers = require('../now-and-timers.cjs');
|
|
7
|
+
|
|
8
|
+
const {isRunningInChildProcess, isRunningInThread} = require('./utils.cjs');
|
|
9
|
+
|
|
10
|
+
let pEvent = async (emitter, event, options) => {
|
|
11
|
+
// We need to import p-event, but import() is asynchronous. Buffer any events
|
|
12
|
+
// emitted in the meantime. Don't handle errors.
|
|
13
|
+
const buffer = [];
|
|
14
|
+
const addToBuffer = (...args) => buffer.push(args);
|
|
15
|
+
emitter.on(event, addToBuffer);
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
({pEvent} = await import('p-event')); // eslint-disable-line node/no-unsupported-features/es-syntax
|
|
19
|
+
} finally {
|
|
20
|
+
emitter.off(event, addToBuffer);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (buffer.length === 0) {
|
|
24
|
+
return pEvent(emitter, event, options);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Now replay buffered events.
|
|
28
|
+
const replayEmitter = new events.EventEmitter();
|
|
29
|
+
const promise = pEvent(replayEmitter, event, options);
|
|
30
|
+
for (const args of buffer) {
|
|
31
|
+
replayEmitter.emit(event, ...args);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const replay = (...args) => replayEmitter.emit(event, ...args);
|
|
35
|
+
emitter.on(event, replay);
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
return await promise;
|
|
39
|
+
} finally {
|
|
40
|
+
emitter.off(event, replay);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const selectAvaMessage = type => message => message.ava && message.ava.type === type;
|
|
45
|
+
|
|
46
|
+
class RefCounter {
|
|
47
|
+
constructor() {
|
|
48
|
+
this.count = 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
refAndTest() {
|
|
52
|
+
return ++this.count === 1;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
testAndUnref() {
|
|
56
|
+
return this.count > 0 && --this.count === 0;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
class MessagePortHandle {
|
|
61
|
+
constructor(port) {
|
|
62
|
+
this.counter = new RefCounter();
|
|
63
|
+
this.unreferenceable = false;
|
|
64
|
+
this.channel = port;
|
|
65
|
+
// Referencing the port does not immediately prevent the thread from
|
|
66
|
+
// exiting. Use a timer to keep a reference for at least a second.
|
|
67
|
+
this.workaroundTimer = timers.setTimeout(() => {}, 1000).unref();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
forceUnref() {
|
|
71
|
+
if (this.unreferenceable) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
this.unreferenceable = true;
|
|
76
|
+
this.workaroundTimer.unref();
|
|
77
|
+
this.channel.unref();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
ref() {
|
|
81
|
+
if (!this.unreferenceable && this.counter.refAndTest()) {
|
|
82
|
+
this.workaroundTimer.refresh().ref();
|
|
83
|
+
this.channel.ref();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
unref() {
|
|
88
|
+
if (!this.unreferenceable && this.counter.testAndUnref()) {
|
|
89
|
+
this.workaroundTimer.unref();
|
|
90
|
+
this.channel.unref();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
send(evt, transferList) {
|
|
95
|
+
this.channel.postMessage({ava: evt}, transferList);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
class IpcHandle {
|
|
100
|
+
constructor(bufferedSend) {
|
|
101
|
+
this.counter = new RefCounter();
|
|
102
|
+
this.channel = process;
|
|
103
|
+
this.sendRaw = bufferedSend;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
ref() {
|
|
107
|
+
if (this.counter.refAndTest()) {
|
|
108
|
+
process.channel.ref();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
unref() {
|
|
113
|
+
if (this.counter.testAndUnref()) {
|
|
114
|
+
process.channel.unref();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
send(evt) {
|
|
119
|
+
this.sendRaw({ava: evt});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let handle;
|
|
124
|
+
if (isRunningInChildProcess) {
|
|
125
|
+
const {controlFlow} = require('../ipc-flow-control.cjs');
|
|
126
|
+
handle = new IpcHandle(controlFlow(process));
|
|
127
|
+
} else if (isRunningInThread) {
|
|
128
|
+
const {parentPort} = require('worker_threads');
|
|
129
|
+
handle = new MessagePortHandle(parentPort);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// The attaching of message listeners will cause the port to be referenced by
|
|
133
|
+
// Node.js. In order to keep track, explicitly reference before attaching.
|
|
134
|
+
handle.ref();
|
|
135
|
+
|
|
136
|
+
exports.options = pEvent(handle.channel, 'message', selectAvaMessage('options')).then(message => message.ava.options);
|
|
137
|
+
exports.peerFailed = pEvent(handle.channel, 'message', selectAvaMessage('peer-failed'));
|
|
138
|
+
exports.send = handle.send.bind(handle);
|
|
139
|
+
exports.unref = handle.unref.bind(handle);
|
|
140
|
+
|
|
141
|
+
let pendingPings = Promise.resolve();
|
|
142
|
+
async function flush() {
|
|
143
|
+
handle.ref();
|
|
144
|
+
const promise = pendingPings.then(async () => {
|
|
145
|
+
handle.send({type: 'ping'});
|
|
146
|
+
await pEvent(handle.channel, 'message', selectAvaMessage('pong'));
|
|
147
|
+
if (promise === pendingPings) {
|
|
148
|
+
handle.unref();
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
pendingPings = promise;
|
|
152
|
+
await promise;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
exports.flush = flush;
|
|
156
|
+
|
|
157
|
+
let channelCounter = 0;
|
|
158
|
+
let messageCounter = 0;
|
|
159
|
+
|
|
160
|
+
const channelEmitters = new Map();
|
|
161
|
+
function createChannelEmitter(channelId) {
|
|
162
|
+
if (channelEmitters.size === 0) {
|
|
163
|
+
handle.channel.on('message', message => {
|
|
164
|
+
if (!message.ava) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const {channelId, type, ...payload} = message.ava;
|
|
169
|
+
if (type === 'shared-worker-error') {
|
|
170
|
+
const emitter = channelEmitters.get(channelId);
|
|
171
|
+
if (emitter !== undefined) {
|
|
172
|
+
emitter.emit(type, payload);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const emitter = new events.EventEmitter();
|
|
179
|
+
channelEmitters.set(channelId, emitter);
|
|
180
|
+
return [emitter, () => channelEmitters.delete(channelId)];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function registerSharedWorker(filename, initialData) {
|
|
184
|
+
const channelId = `${threadId}/channel/${++channelCounter}`;
|
|
185
|
+
|
|
186
|
+
const {port1: ourPort, port2: theirPort} = new MessageChannel();
|
|
187
|
+
const sharedWorkerHandle = new MessagePortHandle(ourPort);
|
|
188
|
+
|
|
189
|
+
const [channelEmitter, unsubscribe] = createChannelEmitter(channelId);
|
|
190
|
+
|
|
191
|
+
handle.send({
|
|
192
|
+
type: 'shared-worker-connect',
|
|
193
|
+
channelId,
|
|
194
|
+
filename,
|
|
195
|
+
initialData,
|
|
196
|
+
port: theirPort,
|
|
197
|
+
}, [theirPort]);
|
|
198
|
+
|
|
199
|
+
let currentlyAvailable = false;
|
|
200
|
+
let error = null;
|
|
201
|
+
|
|
202
|
+
// The attaching of message listeners will cause the port to be referenced by
|
|
203
|
+
// Node.js. In order to keep track, explicitly reference before attaching.
|
|
204
|
+
sharedWorkerHandle.ref();
|
|
205
|
+
const ready = pEvent(ourPort, 'message', ({type}) => type === 'ready').then(() => {
|
|
206
|
+
currentlyAvailable = error === null;
|
|
207
|
+
}).finally(() => {
|
|
208
|
+
// Once ready, it's up to user code to subscribe to messages, which (see
|
|
209
|
+
// below) causes us to reference the port.
|
|
210
|
+
sharedWorkerHandle.unref();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const messageEmitters = new Set();
|
|
214
|
+
|
|
215
|
+
// Errors are received over the test worker channel, not the message port
|
|
216
|
+
// dedicated to the shared worker.
|
|
217
|
+
pEvent(channelEmitter, 'shared-worker-error').then(() => {
|
|
218
|
+
unsubscribe();
|
|
219
|
+
sharedWorkerHandle.forceUnref();
|
|
220
|
+
error = new Error('The shared worker is no longer available');
|
|
221
|
+
currentlyAvailable = false;
|
|
222
|
+
for (const emitter of messageEmitters) {
|
|
223
|
+
emitter.emit('error', error);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
ourPort.on('message', message => {
|
|
228
|
+
if (message.type === 'message') {
|
|
229
|
+
// Wait for a turn of the event loop, to allow new subscriptions to be set
|
|
230
|
+
// up in response to the previous message.
|
|
231
|
+
setImmediate(() => {
|
|
232
|
+
for (const emitter of messageEmitters) {
|
|
233
|
+
emitter.emit('message', message);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
forceUnref: () => sharedWorkerHandle.forceUnref(),
|
|
241
|
+
ready,
|
|
242
|
+
channel: {
|
|
243
|
+
available: ready,
|
|
244
|
+
|
|
245
|
+
get currentlyAvailable() {
|
|
246
|
+
return currentlyAvailable;
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
async * receive() {
|
|
250
|
+
if (error !== null) {
|
|
251
|
+
throw error;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const emitter = new events.EventEmitter();
|
|
255
|
+
messageEmitters.add(emitter);
|
|
256
|
+
try {
|
|
257
|
+
sharedWorkerHandle.ref();
|
|
258
|
+
for await (const [message] of events.on(emitter, 'message')) {
|
|
259
|
+
yield message;
|
|
260
|
+
}
|
|
261
|
+
} finally {
|
|
262
|
+
sharedWorkerHandle.unref();
|
|
263
|
+
messageEmitters.delete(emitter);
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
post(data, replyTo) {
|
|
268
|
+
if (error !== null) {
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (!currentlyAvailable) {
|
|
273
|
+
throw new Error('Shared worker is not yet available');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const messageId = `${channelId}/message/${++messageCounter}`;
|
|
277
|
+
ourPort.postMessage({
|
|
278
|
+
type: 'message',
|
|
279
|
+
messageId,
|
|
280
|
+
replyTo,
|
|
281
|
+
data,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
return messageId;
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
exports.registerSharedWorker = registerSharedWorker;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
|
|
3
|
+
import channel from './channel.cjs';
|
|
4
4
|
|
|
5
5
|
const seenDependencies = new Set();
|
|
6
6
|
let newDependencies = [];
|
|
@@ -14,8 +14,6 @@ function flush() {
|
|
|
14
14
|
newDependencies = [];
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
exports.flush = flush;
|
|
18
|
-
|
|
19
17
|
function track(filename) {
|
|
20
18
|
if (seenDependencies.has(filename)) {
|
|
21
19
|
return;
|
|
@@ -29,20 +27,22 @@ function track(filename) {
|
|
|
29
27
|
newDependencies.push(filename);
|
|
30
28
|
}
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
30
|
+
const tracker = {
|
|
31
|
+
flush,
|
|
32
|
+
track,
|
|
33
|
+
install(extensions, testPath) {
|
|
34
|
+
for (const ext of Object.keys(extensions)) {
|
|
35
|
+
const wrappedHandler = extensions[ext];
|
|
36
|
+
|
|
37
|
+
extensions[ext] = (module, filename) => {
|
|
38
|
+
if (filename !== testPath) {
|
|
39
|
+
track(filename);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
wrappedHandler(module, filename);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default tracker;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const process = require('process');
|
|
4
|
+
|
|
5
|
+
const {isRunningInThread, isRunningInChildProcess} = require('./utils.cjs');
|
|
6
|
+
|
|
7
|
+
// Check if the test is being run without AVA cli
|
|
8
|
+
if (!isRunningInChildProcess && !isRunningInThread) {
|
|
9
|
+
if (process.argv[1]) {
|
|
10
|
+
const fp = path.relative('.', process.argv[1]);
|
|
11
|
+
|
|
12
|
+
console.log();
|
|
13
|
+
console.error(`Test files must be run with the AVA CLI:\n\n $ ava ${fp}\n`);
|
|
14
|
+
|
|
15
|
+
process.exit(1); // eslint-disable-line unicorn/no-process-exit
|
|
16
|
+
} else {
|
|
17
|
+
throw new Error('The ’ava’ module can only be imported in test files');
|
|
18
|
+
}
|
|
19
|
+
}
|