ava 3.11.0 → 3.13.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/index.d.ts CHANGED
@@ -433,7 +433,7 @@ export interface CbExecutionContext<Context = unknown> extends ExecutionContext<
433
433
  end(error?: any): void;
434
434
  }
435
435
 
436
- export type ImplementationResult = PromiseLike<void> | Subscribable | void; // eslint-disable-line @typescript-eslint/no-invalid-void-type
436
+ export type ImplementationResult = PromiseLike<void> | Subscribable | void;
437
437
  export type Implementation<Context = unknown> = (t: ExecutionContext<Context>) => ImplementationResult;
438
438
  export type CbImplementation<Context = unknown> = (t: CbExecutionContext<Context>) => ImplementationResult;
439
439
 
package/lib/api.js CHANGED
@@ -17,6 +17,7 @@ const RunStatus = require('./run-status');
17
17
  const fork = require('./fork');
18
18
  const serializeError = require('./serialize-error');
19
19
  const {getApplicableLineNumbers} = require('./line-numbers');
20
+ const sharedWorkers = require('./plugin-support/shared-workers');
20
21
 
21
22
  function resolveModules(modules) {
22
23
  return arrify(modules).map(name => {
@@ -206,6 +207,8 @@ class Api extends Emittery {
206
207
  concurrency = 1;
207
208
  }
208
209
 
210
+ const deregisteredSharedWorkers = [];
211
+
209
212
  // Try and run each file, limited by `concurrency`.
210
213
  await pMap(selectedFiles, async file => {
211
214
  // No new files should be run once a test has timed out or failed,
@@ -231,6 +234,7 @@ class Api extends Emittery {
231
234
 
232
235
  const worker = fork(file, options, apiOptions.nodeArguments);
233
236
  runStatus.observeWorker(worker, file, {selectingLines: lineNumbers.length > 0});
237
+ deregisteredSharedWorkers.push(sharedWorkers.observeWorkerProcess(worker, runStatus));
234
238
 
235
239
  pendingWorkers.add(worker);
236
240
  worker.promise.then(() => {
@@ -238,8 +242,11 @@ class Api extends Emittery {
238
242
  });
239
243
  restartTimer();
240
244
 
241
- return worker.promise;
245
+ await worker.promise;
242
246
  }, {concurrency, stopOnError: false});
247
+
248
+ // Allow shared workers to clean up before the run ends.
249
+ await Promise.all(deregisteredSharedWorkers);
243
250
  } catch (error) {
244
251
  if (error && error.name === 'AggregateError') {
245
252
  for (const err of error) {
package/lib/assert.js CHANGED
@@ -87,8 +87,16 @@ function getErrorWithLongStackTrace() {
87
87
  return err;
88
88
  }
89
89
 
90
- function validateExpectations(assertion, expectations, numberArgs) { // eslint-disable-line complexity
90
+ function validateExpectations(assertion, expectations, numberArgs, experiments) { // eslint-disable-line complexity
91
91
  if (numberArgs === 1 || expectations === null || expectations === undefined) {
92
+ if (experiments.disableNullExpectations && expectations === null) {
93
+ throw new AssertionError({
94
+ assertion,
95
+ message: `The second argument to \`t.${assertion}()\` must be an expectation object or \`undefined\``,
96
+ values: [formatWithLabel('Called with:', expectations)]
97
+ });
98
+ }
99
+
92
100
  expectations = {};
93
101
  } else if (
94
102
  typeof expectations === 'function' ||
@@ -465,7 +473,7 @@ class Assertions {
465
473
  }
466
474
 
467
475
  try {
468
- expectations = validateExpectations('throws', expectations, args.length);
476
+ expectations = validateExpectations('throws', expectations, args.length, experiments);
469
477
  } catch (error) {
470
478
  fail(error);
471
479
  return;
@@ -531,7 +539,7 @@ class Assertions {
531
539
  }
532
540
 
533
541
  try {
534
- expectations = validateExpectations('throwsAsync', expectations, args.length);
542
+ expectations = validateExpectations('throwsAsync', expectations, args.length, experiments);
535
543
  } catch (error) {
536
544
  fail(error);
537
545
  return Promise.resolve();
package/lib/cli.js CHANGED
@@ -284,6 +284,7 @@ exports.run = async () => { // eslint-disable-line complexity
284
284
  const TapReporter = require('./reporters/tap');
285
285
  const Watcher = require('./watcher');
286
286
  const normalizeExtensions = require('./extensions');
287
+ const normalizeModuleTypes = require('./module-types');
287
288
  const {normalizeGlobs, normalizePattern} = require('./globs');
288
289
  const normalizeNodeArguments = require('./node-arguments');
289
290
  const validateEnvironmentVariables = require('./environment-variables');
@@ -301,12 +302,6 @@ exports.run = async () => { // eslint-disable-line complexity
301
302
 
302
303
  const {type: defaultModuleType = 'commonjs'} = pkg || {};
303
304
 
304
- const moduleTypes = {
305
- cjs: 'commonjs',
306
- mjs: 'module',
307
- js: defaultModuleType
308
- };
309
-
310
305
  const providers = [];
311
306
  if (Reflect.has(conf, 'babel')) {
312
307
  try {
@@ -348,6 +343,13 @@ exports.run = async () => { // eslint-disable-line complexity
348
343
  exit(error.message);
349
344
  }
350
345
 
346
+ let moduleTypes;
347
+ try {
348
+ moduleTypes = normalizeModuleTypes(conf.extensions, defaultModuleType, experiments);
349
+ } catch (error) {
350
+ exit(error.message);
351
+ }
352
+
351
353
  let globs;
352
354
  try {
353
355
  globs = normalizeGlobs({files: conf.files, ignoredByWatcher: conf.ignoredByWatcher, extensions, providers});
@@ -428,14 +430,17 @@ exports.run = async () => { // eslint-disable-line complexity
428
430
  reporter.startRun(plan);
429
431
 
430
432
  if (process.env.AVA_EMIT_RUN_STATUS_OVER_IPC === 'I\'ll find a payphone baby / Take some time to talk to you') {
433
+ const {controlFlow} = require('./ipc-flow-control');
434
+ const bufferedSend = controlFlow(process);
435
+
431
436
  if (process.versions.node >= '12.16.0') {
432
437
  plan.status.on('stateChange', evt => {
433
- process.send(evt);
438
+ bufferedSend(evt);
434
439
  });
435
440
  } else {
436
441
  const v8 = require('v8');
437
442
  plan.status.on('stateChange', evt => {
438
- process.send([...v8.serialize(evt)]);
443
+ bufferedSend([...v8.serialize(evt)]);
439
444
  });
440
445
  }
441
446
  }
package/lib/extensions.js CHANGED
@@ -2,8 +2,11 @@ module.exports = (configuredExtensions, providers = []) => {
2
2
  // Combine all extensions possible for testing. Remove duplicate extensions.
3
3
  const duplicates = new Set();
4
4
  const seen = new Set();
5
+
6
+ const normalize = extensions => Array.isArray(extensions) ? extensions : Object.keys(extensions);
7
+
5
8
  const combine = extensions => {
6
- for (const ext of extensions) {
9
+ for (const ext of normalize(extensions)) {
7
10
  if (seen.has(ext)) {
8
11
  duplicates.add(ext);
9
12
  } else {
package/lib/fork.js CHANGED
@@ -3,6 +3,7 @@ const childProcess = require('child_process');
3
3
  const path = require('path');
4
4
  const fs = require('fs');
5
5
  const Emittery = require('emittery');
6
+ const {controlFlow} = require('./ipc-flow-control');
6
7
 
7
8
  if (fs.realpathSync(__filename) !== __filename) {
8
9
  console.warn('WARNING: `npm link ava` and the `--preserve-symlink` flag are incompatible. We have detected that AVA is linked via `npm link`, and that you are using either an early version of Node 6, or the `--preserve-symlink` flag. This breaks AVA. You should upgrade to Node 6.2.0+, avoid the `--preserve-symlink` flag, or avoid using `npm link ava`.');
@@ -11,10 +12,57 @@ if (fs.realpathSync(__filename) !== __filename) {
11
12
  // In case the test file imports a different AVA install,
12
13
  // the presence of this variable allows it to require this one instead
13
14
  const AVA_PATH = path.resolve(__dirname, '..');
15
+ const WORKER_PATH = require.resolve('./worker/subprocess');
16
+
17
+ class SharedWorkerChannel extends Emittery {
18
+ constructor({channelId, filename, initialData}, sendToFork) {
19
+ super();
20
+
21
+ this.id = channelId;
22
+ this.filename = filename;
23
+ this.initialData = initialData;
24
+ this.sendToFork = sendToFork;
25
+ }
26
+
27
+ signalReady() {
28
+ this.sendToFork({
29
+ type: 'shared-worker-ready',
30
+ channelId: this.id
31
+ });
32
+ }
33
+
34
+ signalError() {
35
+ this.sendToFork({
36
+ type: 'shared-worker-error',
37
+ channelId: this.id
38
+ });
39
+ }
40
+
41
+ emitMessage({messageId, replyTo, serializedData}) {
42
+ this.emit('message', {
43
+ messageId,
44
+ replyTo,
45
+ serializedData
46
+ });
47
+ }
48
+
49
+ forwardMessageToFork({messageId, replyTo, serializedData}) {
50
+ this.sendToFork({
51
+ type: 'shared-worker-message',
52
+ channelId: this.id,
53
+ messageId,
54
+ replyTo,
55
+ serializedData
56
+ });
57
+ }
58
+ }
14
59
 
15
- const workerPath = require.resolve('./worker/subprocess');
60
+ let forkCounter = 0;
16
61
 
17
62
  module.exports = (file, options, execArgv = process.execArgv) => {
63
+ const forkId = `fork/${++forkCounter}`;
64
+ const sharedWorkerChannels = new Map();
65
+
18
66
  let finished = false;
19
67
 
20
68
  const emitter = new Emittery();
@@ -25,12 +73,13 @@ module.exports = (file, options, execArgv = process.execArgv) => {
25
73
  };
26
74
 
27
75
  options = {
28
- file,
29
76
  baseDir: process.cwd(),
77
+ file,
78
+ forkId,
30
79
  ...options
31
80
  };
32
81
 
33
- const subprocess = childProcess.fork(workerPath, options.workerArgv, {
82
+ const subprocess = childProcess.fork(WORKER_PATH, options.workerArgv, {
34
83
  cwd: options.projectDir,
35
84
  silent: true,
36
85
  env: {NODE_ENV: 'test', ...process.env, ...options.environmentVariables, AVA_PATH},
@@ -45,12 +94,12 @@ module.exports = (file, options, execArgv = process.execArgv) => {
45
94
  emitStateChange({type: 'worker-stderr', chunk});
46
95
  });
47
96
 
97
+ const bufferedSend = controlFlow(subprocess);
98
+
48
99
  let forcedExit = false;
49
100
  const send = evt => {
50
- if (subprocess.connected && !finished && !forcedExit) {
51
- subprocess.send({ava: evt}, () => {
52
- // Disregard errors.
53
- });
101
+ if (!finished && !forcedExit) {
102
+ bufferedSend({ava: evt});
54
103
  }
55
104
  };
56
105
 
@@ -65,15 +114,25 @@ module.exports = (file, options, execArgv = process.execArgv) => {
65
114
  return;
66
115
  }
67
116
 
68
- if (message.ava.type === 'ready-for-options') {
69
- send({type: 'options', options});
70
- return;
71
- }
72
-
73
- if (message.ava.type === 'ping') {
74
- send({type: 'pong'});
75
- } else {
76
- emitStateChange(message.ava);
117
+ switch (message.ava.type) {
118
+ case 'ready-for-options':
119
+ send({type: 'options', options});
120
+ break;
121
+ case 'shared-worker-connect': {
122
+ const channel = new SharedWorkerChannel(message.ava, send);
123
+ sharedWorkerChannels.set(channel.id, channel);
124
+ emitter.emit('connectSharedWorker', channel);
125
+ break;
126
+ }
127
+
128
+ case 'shared-worker-message':
129
+ sharedWorkerChannels.get(message.ava.channelId).emitMessage(message.ava);
130
+ break;
131
+ case 'ping':
132
+ send({type: 'pong'});
133
+ break;
134
+ default:
135
+ emitStateChange(message.ava);
77
136
  }
78
137
  });
79
138
 
@@ -98,6 +157,10 @@ module.exports = (file, options, execArgv = process.execArgv) => {
98
157
  });
99
158
 
100
159
  return {
160
+ file,
161
+ forkId,
162
+ promise,
163
+
101
164
  exit() {
102
165
  forcedExit = true;
103
166
  subprocess.kill();
@@ -107,11 +170,12 @@ module.exports = (file, options, execArgv = process.execArgv) => {
107
170
  send({type: 'peer-failed'});
108
171
  },
109
172
 
110
- onStateChange(listener) {
111
- return emitter.on('stateChange', listener);
173
+ onConnectSharedWorker(listener) {
174
+ return emitter.on('connectSharedWorker', listener);
112
175
  },
113
176
 
114
- file,
115
- promise
177
+ onStateChange(listener) {
178
+ return emitter.on('stateChange', listener);
179
+ }
116
180
  };
117
181
  };
@@ -0,0 +1,39 @@
1
+ function controlFlow(channel) {
2
+ let errored = false;
3
+ let deliverImmediately = true;
4
+
5
+ const backlog = [];
6
+ const deliverNext = error => {
7
+ if (error !== null) {
8
+ errored = true;
9
+ }
10
+
11
+ if (errored || !channel.connected) {
12
+ backlog.length = 0; // Free memory.
13
+ return; // We can't send.
14
+ }
15
+
16
+ let ok = true;
17
+ while (ok && backlog.length > 0) { // Stop sending after backpressure.
18
+ ok = channel.send(backlog.shift(), deliverNext);
19
+ }
20
+
21
+ // Re-enable immediate delivery if there is no backpressure and the backlog
22
+ // has been cleared.
23
+ deliverImmediately = ok && backlog.length === 0;
24
+ };
25
+
26
+ return message => {
27
+ if (errored || !channel.connected) {
28
+ return;
29
+ }
30
+
31
+ if (deliverImmediately) {
32
+ deliverImmediately = channel.send(message, deliverNext);
33
+ } else {
34
+ backlog.push(message);
35
+ }
36
+ };
37
+ }
38
+
39
+ exports.controlFlow = controlFlow;
@@ -2,12 +2,18 @@
2
2
  const fs = require('fs');
3
3
  const path = require('path');
4
4
  const vm = require('vm');
5
- const isPlainObject = require('is-plain-object');
5
+ const {isPlainObject} = require('is-plain-object');
6
6
  const pkgConf = require('pkg-conf');
7
7
 
8
8
  const NO_SUCH_FILE = Symbol('no ava.config.js file');
9
9
  const MISSING_DEFAULT_EXPORT = Symbol('missing default export');
10
- const EXPERIMENTS = new Set(['disableSnapshotsInHooks', 'reverseTeardowns']);
10
+ const EXPERIMENTS = new Set([
11
+ 'configurableModuleFormat',
12
+ 'disableNullExpectations',
13
+ 'disableSnapshotsInHooks',
14
+ 'reverseTeardowns',
15
+ 'sharedWorkers'
16
+ ]);
11
17
 
12
18
  // *Very* rudimentary support for loading ava.config.js files containing an `export default` statement.
13
19
  const evaluateJsConfig = configFile => {
@@ -0,0 +1,75 @@
1
+ const requireTrueValue = value => {
2
+ if (value !== true) {
3
+ throw new TypeError('When specifying module types, use `true` for ’cjs’, ’mjs’ and ’js’ extensions');
4
+ }
5
+ };
6
+
7
+ const normalize = (extension, type, defaultModuleType) => {
8
+ switch (extension) {
9
+ case 'cjs':
10
+ requireTrueValue(type);
11
+ return 'commonjs';
12
+ case 'mjs':
13
+ requireTrueValue(type);
14
+ return 'module';
15
+ case 'js':
16
+ requireTrueValue(type);
17
+ return defaultModuleType;
18
+ default:
19
+ if (type !== 'commonjs' && type !== 'module') {
20
+ throw new TypeError(`Module type for ’${extension}’ must be ’commonjs’ or ’module’`);
21
+ }
22
+
23
+ return type;
24
+ }
25
+ };
26
+
27
+ const deriveFromObject = (extensionsObject, defaultModuleType) => {
28
+ const moduleTypes = {};
29
+ for (const [extension, type] of Object.entries(extensionsObject)) {
30
+ moduleTypes[extension] = normalize(extension, type, defaultModuleType);
31
+ }
32
+
33
+ return moduleTypes;
34
+ };
35
+
36
+ const deriveFromArray = (extensions, defaultModuleType) => {
37
+ const moduleTypes = {};
38
+ for (const extension of extensions) {
39
+ switch (extension) {
40
+ case 'cjs':
41
+ moduleTypes.cjs = 'commonjs';
42
+ break;
43
+ case 'mjs':
44
+ moduleTypes.mjs = 'module';
45
+ break;
46
+ case 'js':
47
+ moduleTypes.js = defaultModuleType;
48
+ break;
49
+ default:
50
+ moduleTypes[extension] = 'commonjs';
51
+ }
52
+ }
53
+
54
+ return moduleTypes;
55
+ };
56
+
57
+ module.exports = (configuredExtensions, defaultModuleType, experiments) => {
58
+ if (configuredExtensions === undefined) {
59
+ return {
60
+ cjs: 'commonjs',
61
+ mjs: 'module',
62
+ js: defaultModuleType
63
+ };
64
+ }
65
+
66
+ if (Array.isArray(configuredExtensions)) {
67
+ return deriveFromArray(configuredExtensions, defaultModuleType);
68
+ }
69
+
70
+ if (!experiments.configurableModuleFormat) {
71
+ throw new Error('You must enable the `configurableModuleFormat` experiment in order to specify module types');
72
+ }
73
+
74
+ return deriveFromObject(configuredExtensions, defaultModuleType);
75
+ };
@@ -0,0 +1,252 @@
1
+ const {EventEmitter, on} = require('events');
2
+ const v8 = require('v8');
3
+ const {workerData, parentPort} = require('worker_threads'); // eslint-disable-line node/no-unsupported-features/node-builtins
4
+ const pkg = require('../../package.json');
5
+
6
+ // Used to forward messages received over the `parentPort`. Every subscription
7
+ // adds a listener, so do not enforce any maximums.
8
+ const events = new EventEmitter().setMaxListeners(0);
9
+
10
+ // Map of active test workers, used in receiveMessages() to get a reference to
11
+ // the TestWorker instance, and relevant release functions.
12
+ const activeTestWorkers = new Map();
13
+
14
+ class TestWorker {
15
+ constructor(id, file) {
16
+ this.id = id;
17
+ this.file = file;
18
+ }
19
+
20
+ teardown(fn) {
21
+ let done = false;
22
+ const teardownFn = async () => {
23
+ if (done) {
24
+ return;
25
+ }
26
+
27
+ done = true;
28
+ if (activeTestWorkers.has(this.id)) {
29
+ activeTestWorkers.get(this.id).teardownFns.delete(teardownFn);
30
+ }
31
+
32
+ await fn();
33
+ };
34
+
35
+ activeTestWorkers.get(this.id).teardownFns.add(teardownFn);
36
+
37
+ return teardownFn;
38
+ }
39
+
40
+ publish(data) {
41
+ return publishMessage(this, data);
42
+ }
43
+
44
+ async * subscribe() {
45
+ yield * receiveMessages(this);
46
+ }
47
+ }
48
+
49
+ class ReceivedMessage {
50
+ constructor(testWorker, id, serializedData) {
51
+ this.testWorker = testWorker;
52
+ this.id = id;
53
+ this.data = v8.deserialize(new Uint8Array(serializedData));
54
+ }
55
+
56
+ reply(data) {
57
+ return publishMessage(this.testWorker, data, this.id);
58
+ }
59
+ }
60
+
61
+ // Ensure that, no matter how often it's received, we have a stable message
62
+ // object.
63
+ const messageCache = new WeakMap();
64
+
65
+ async function * receiveMessages(fromTestWorker, replyTo) {
66
+ for await (const [message] of on(events, 'message')) {
67
+ if (fromTestWorker !== undefined) {
68
+ if (message.type === 'deregister-test-worker' && message.id === fromTestWorker.id) {
69
+ return;
70
+ }
71
+
72
+ if (message.type === 'message' && message.testWorkerId !== fromTestWorker.id) {
73
+ continue;
74
+ }
75
+ }
76
+
77
+ if (message.type !== 'message') {
78
+ continue;
79
+ }
80
+
81
+ if (replyTo === undefined && message.replyTo !== undefined) {
82
+ continue;
83
+ }
84
+
85
+ if (replyTo !== undefined && message.replyTo !== replyTo) {
86
+ continue;
87
+ }
88
+
89
+ const active = activeTestWorkers.get(message.testWorkerId);
90
+ // It is possible for a message to have been buffering for so long — perhaps
91
+ // due to the caller waiting before iterating to the next message — that the
92
+ // test worker has been deregistered. Ignore such messages.
93
+ //
94
+ // (This is really hard to write a test for, however!)
95
+ if (active === undefined) {
96
+ continue;
97
+ }
98
+
99
+ let received = messageCache.get(message);
100
+ if (received === undefined) {
101
+ received = new ReceivedMessage(active.instance, message.messageId, message.serializedData);
102
+ messageCache.set(message, received);
103
+ }
104
+
105
+ yield received;
106
+ }
107
+ }
108
+
109
+ let messageCounter = 0;
110
+ const messageIdPrefix = `${workerData.id}/message`;
111
+ const nextMessageId = () => `${messageIdPrefix}/${++messageCounter}`;
112
+
113
+ function publishMessage(testWorker, data, replyTo) {
114
+ const id = nextMessageId();
115
+ parentPort.postMessage({
116
+ type: 'message',
117
+ messageId: id,
118
+ testWorkerId: testWorker.id,
119
+ serializedData: [...v8.serialize(data)],
120
+ replyTo
121
+ });
122
+
123
+ return {
124
+ id,
125
+ async * replies() {
126
+ yield * receiveMessages(testWorker, id);
127
+ }
128
+ };
129
+ }
130
+
131
+ function broadcastMessage(data) {
132
+ const id = nextMessageId();
133
+ parentPort.postMessage({
134
+ type: 'broadcast',
135
+ messageId: id,
136
+ serializedData: [...v8.serialize(data)]
137
+ });
138
+
139
+ return {
140
+ id,
141
+ async * replies() {
142
+ yield * receiveMessages(undefined, id);
143
+ }
144
+ };
145
+ }
146
+
147
+ async function loadFactory() {
148
+ try {
149
+ const mod = require(workerData.filename);
150
+ if (typeof mod === 'function') {
151
+ return mod;
152
+ }
153
+
154
+ return mod.default;
155
+ } catch (error) {
156
+ if (error && (error.code === 'ERR_REQUIRE_ESM' || (error.code === 'MODULE_NOT_FOUND' && workerData.filename.startsWith('file://')))) {
157
+ const {default: factory} = await import(workerData.filename); // eslint-disable-line node/no-unsupported-features/es-syntax
158
+ return factory;
159
+ }
160
+
161
+ throw error;
162
+ }
163
+ }
164
+
165
+ let signalAvailable = () => {
166
+ parentPort.postMessage({type: 'available'});
167
+ signalAvailable = () => {};
168
+ };
169
+
170
+ let fatal;
171
+ loadFactory(workerData.filename).then(factory => {
172
+ if (typeof factory !== 'function') {
173
+ throw new TypeError(`Missing default factory function export for shared worker plugin at ${workerData.filename}`);
174
+ }
175
+
176
+ factory({
177
+ negotiateProtocol(supported) {
178
+ if (!supported.includes('experimental')) {
179
+ fatal = new Error(`This version of AVA (${pkg.version}) is not compatible with shared worker plugin at ${workerData.filename}`);
180
+ throw fatal;
181
+ }
182
+
183
+ const produceTestWorker = instance => events.emit('testWorker', instance);
184
+
185
+ parentPort.on('message', async message => {
186
+ if (message.type === 'register-test-worker') {
187
+ const {id, file} = message;
188
+ const instance = new TestWorker(id, file);
189
+
190
+ activeTestWorkers.set(id, {instance, teardownFns: new Set()});
191
+
192
+ produceTestWorker(instance);
193
+ }
194
+
195
+ if (message.type === 'deregister-test-worker') {
196
+ const {id} = message;
197
+ const {teardownFns} = activeTestWorkers.get(id);
198
+ activeTestWorkers.delete(id);
199
+
200
+ // Run possibly asynchronous release functions serially, in reverse
201
+ // order. Any error will crash the worker.
202
+ for await (const fn of [...teardownFns].reverse()) {
203
+ await fn();
204
+ }
205
+
206
+ parentPort.postMessage({
207
+ type: 'deregistered-test-worker',
208
+ id
209
+ });
210
+ }
211
+
212
+ // Wait for a turn of the event loop, to allow new subscriptions to be
213
+ // set up in response to the previous message.
214
+ setImmediate(() => events.emit('message', message));
215
+ });
216
+
217
+ return {
218
+ initialData: workerData.initialData,
219
+ protocol: 'experimental',
220
+
221
+ ready() {
222
+ signalAvailable();
223
+ return this;
224
+ },
225
+
226
+ broadcast(data) {
227
+ return broadcastMessage(data);
228
+ },
229
+
230
+ async * subscribe() {
231
+ yield * receiveMessages();
232
+ },
233
+
234
+ async * testWorkers() {
235
+ for await (const [worker] of on(events, 'testWorker')) {
236
+ yield worker;
237
+ }
238
+ }
239
+ };
240
+ }
241
+ });
242
+ }).catch(error => {
243
+ if (fatal === undefined) {
244
+ fatal = error;
245
+ }
246
+ }).finally(() => {
247
+ if (fatal !== undefined) {
248
+ process.nextTick(() => {
249
+ throw fatal;
250
+ });
251
+ }
252
+ });