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.
Files changed (72) hide show
  1. package/entrypoints/cli.mjs +4 -0
  2. package/entrypoints/eslint-plugin-helper.cjs +109 -0
  3. package/entrypoints/main.cjs +2 -0
  4. package/entrypoints/main.mjs +1 -0
  5. package/entrypoints/plugin.cjs +2 -0
  6. package/entrypoints/plugin.mjs +4 -0
  7. package/index.d.ts +6 -816
  8. package/lib/api.js +108 -49
  9. package/lib/assert.js +255 -270
  10. package/lib/chalk.js +9 -14
  11. package/lib/cli.js +118 -112
  12. package/lib/code-excerpt.js +12 -17
  13. package/lib/concordance-options.js +29 -65
  14. package/lib/context-ref.js +3 -6
  15. package/lib/create-chain.js +32 -20
  16. package/lib/environment-variables.js +1 -4
  17. package/lib/eslint-plugin-helper-worker.js +73 -0
  18. package/lib/extensions.js +2 -2
  19. package/lib/fork.js +81 -84
  20. package/lib/glob-helpers.cjs +140 -0
  21. package/lib/globs.js +136 -163
  22. package/lib/{ipc-flow-control.js → ipc-flow-control.cjs} +1 -0
  23. package/lib/is-ci.js +4 -2
  24. package/lib/like-selector.js +7 -13
  25. package/lib/line-numbers.js +11 -18
  26. package/lib/load-config.js +56 -180
  27. package/lib/module-types.js +3 -7
  28. package/lib/node-arguments.js +4 -5
  29. package/lib/{now-and-timers.js → now-and-timers.cjs} +0 -0
  30. package/lib/parse-test-args.js +22 -11
  31. package/lib/pkg.cjs +2 -0
  32. package/lib/plugin-support/shared-worker-loader.js +45 -48
  33. package/lib/plugin-support/shared-workers.js +24 -46
  34. package/lib/provider-manager.js +20 -14
  35. package/lib/reporters/beautify-stack.js +6 -12
  36. package/lib/reporters/colors.js +40 -15
  37. package/lib/reporters/default.js +114 -364
  38. package/lib/reporters/format-serialized-error.js +7 -18
  39. package/lib/reporters/improper-usage-messages.js +8 -9
  40. package/lib/reporters/prefix-title.js +17 -15
  41. package/lib/reporters/tap.js +18 -25
  42. package/lib/run-status.js +29 -23
  43. package/lib/runner.js +157 -172
  44. package/lib/scheduler.js +53 -0
  45. package/lib/serialize-error.js +61 -64
  46. package/lib/snapshot-manager.js +271 -289
  47. package/lib/test.js +135 -291
  48. package/lib/watcher.js +69 -44
  49. package/lib/worker/base.js +208 -0
  50. package/lib/worker/channel.cjs +290 -0
  51. package/lib/worker/dependency-tracker.js +24 -23
  52. package/lib/worker/{ensure-forked.js → guard-environment.cjs} +5 -4
  53. package/lib/worker/line-numbers.js +58 -20
  54. package/lib/worker/main.cjs +12 -0
  55. package/lib/worker/{options.js → options.cjs} +0 -0
  56. package/lib/worker/{plugin.js → plugin.cjs} +30 -21
  57. package/lib/worker/state.cjs +5 -0
  58. package/lib/worker/utils.cjs +6 -0
  59. package/package.json +71 -68
  60. package/plugin.d.ts +51 -53
  61. package/readme.md +5 -13
  62. package/types/assertions.d.ts +327 -0
  63. package/types/subscribable.ts +6 -0
  64. package/types/test-fn.d.ts +231 -0
  65. package/types/try-fn.d.ts +58 -0
  66. package/cli.js +0 -11
  67. package/eslint-plugin-helper.js +0 -201
  68. package/index.js +0 -8
  69. package/lib/worker/ipc.js +0 -201
  70. package/lib/worker/main.js +0 -21
  71. package/lib/worker/subprocess.js +0 -266
  72. package/plugin.js +0 -9
@@ -1,40 +1,17 @@
1
- 'use strict';
2
- const fs = require('fs');
3
- const path = require('path');
4
- const url = require('url');
5
- const vm = require('vm');
6
- const {isPlainObject} = require('is-plain-object');
7
- const pkgConf = require('pkg-conf');
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import process from 'node:process';
4
+ import url from 'node:url';
5
+
6
+ import {isPlainObject} from 'is-plain-object';
7
+ import {packageConfig, packageJsonPath} from 'pkg-conf';
8
8
 
9
9
  const NO_SUCH_FILE = Symbol('no ava.config.js file');
10
10
  const MISSING_DEFAULT_EXPORT = Symbol('missing default export');
11
- const EXPERIMENTS = new Set([
12
- 'configurableModuleFormat',
13
- 'disableNullExpectations',
14
- 'disableSnapshotsInHooks',
15
- 'nextGenConfig',
16
- 'reverseTeardowns',
17
- 'sharedWorkers'
18
- ]);
19
-
20
- // *Very* rudimentary support for loading ava.config.js files containing an `export default` statement.
21
- const evaluateJsConfig = (contents, configFile) => {
22
- const script = new vm.Script(`'use strict';(()=>{let __export__;\n${contents.toString('utf8').replace(/export default/g, '__export__ =')};return __export__;})()`, {
23
- filename: configFile,
24
- lineOffset: -1
25
- });
26
- return script.runInThisContext();
27
- };
11
+ const EXPERIMENTS = new Set();
28
12
 
29
13
  const importConfig = async ({configFile, fileForErrorMessage}) => {
30
- let module;
31
- try {
32
- module = await import(url.pathToFileURL(configFile)); // eslint-disable-line node/no-unsupported-features/es-syntax
33
- } catch (error) {
34
- throw Object.assign(new Error(`Error loading ${fileForErrorMessage}: ${error.message}`), {parent: error});
35
- }
36
-
37
- const {default: config = MISSING_DEFAULT_EXPORT} = module;
14
+ const {default: config = MISSING_DEFAULT_EXPORT} = await import(url.pathToFileURL(configFile)); // eslint-disable-line node/no-unsupported-features/es-syntax
38
15
  if (config === MISSING_DEFAULT_EXPORT) {
39
16
  throw new Error(`${fileForErrorMessage} must have a default export`);
40
17
  }
@@ -42,79 +19,22 @@ const importConfig = async ({configFile, fileForErrorMessage}) => {
42
19
  return config;
43
20
  };
44
21
 
45
- const loadJsConfig = ({projectDir, configFile = path.join(projectDir, 'ava.config.js')}, useImport = false) => {
46
- if (!configFile.endsWith('.js')) {
22
+ const loadConfigFile = async ({projectDir, configFile}) => {
23
+ if (!fs.existsSync(configFile)) {
47
24
  return null;
48
25
  }
49
26
 
50
27
  const fileForErrorMessage = path.relative(projectDir, configFile);
51
-
52
- let config;
53
28
  try {
54
- const contents = fs.readFileSync(configFile);
55
- config = useImport && contents.includes('nonSemVerExperiments') && contents.includes('nextGenConfig') ?
56
- importConfig({configFile, fileForErrorMessage}) :
57
- evaluateJsConfig(contents, configFile) || MISSING_DEFAULT_EXPORT;
29
+ return {config: await importConfig({configFile, fileForErrorMessage}), configFile, fileForErrorMessage};
58
30
  } catch (error) {
59
- if (error.code === 'ENOENT') {
60
- return null;
61
- }
62
-
63
31
  throw Object.assign(new Error(`Error loading ${fileForErrorMessage}: ${error.message}`), {parent: error});
64
32
  }
65
-
66
- if (config === MISSING_DEFAULT_EXPORT) {
67
- throw new Error(`${fileForErrorMessage} must have a default export, using ES module syntax`);
68
- }
69
-
70
- return {config, fileForErrorMessage};
71
- };
72
-
73
- const loadCjsConfig = ({projectDir, configFile = path.join(projectDir, 'ava.config.cjs')}) => {
74
- if (!configFile.endsWith('.cjs')) {
75
- return null;
76
- }
77
-
78
- const fileForErrorMessage = path.relative(projectDir, configFile);
79
- try {
80
- return {config: require(configFile), fileForErrorMessage};
81
- } catch (error) {
82
- if (error.code === 'MODULE_NOT_FOUND') {
83
- return null;
84
- }
85
-
86
- throw Object.assign(new Error(`Error loading ${fileForErrorMessage}`), {parent: error});
87
- }
88
33
  };
89
34
 
90
- const loadMjsConfig = ({projectDir, configFile = path.join(projectDir, 'ava.config.mjs')}, experimentally = false) => {
91
- if (!configFile.endsWith('.mjs')) {
92
- return null;
93
- }
94
-
95
- const fileForErrorMessage = path.relative(projectDir, configFile);
96
- try {
97
- const contents = fs.readFileSync(configFile);
98
- if (experimentally && contents.includes('nonSemVerExperiments') && contents.includes('nextGenConfig')) {
99
- return {config: importConfig({configFile, fileForErrorMessage}), fileForErrorMessage};
100
- }
101
- } catch (error) {
102
- if (error.code === 'ENOENT') {
103
- return null;
104
- }
105
-
106
- throw Object.assign(new Error(`Error loading ${fileForErrorMessage}`), {parent: error});
107
- }
108
-
109
- throw new Error(`AVA cannot yet load ${fileForErrorMessage} files`);
110
- };
111
-
112
- function resolveConfigFile(projectDir, configFile) {
35
+ function resolveConfigFile(configFile) {
113
36
  if (configFile) {
114
37
  configFile = path.resolve(configFile); // Relative to CWD
115
- if (path.basename(configFile) !== path.relative(projectDir, configFile)) {
116
- throw new Error('Config files must be located next to the package.json file');
117
- }
118
38
 
119
39
  if (!configFile.endsWith('.js') && !configFile.endsWith('.cjs') && !configFile.endsWith('.mjs')) {
120
40
  throw new Error('Config files must have .js, .cjs or .mjs extensions');
@@ -124,93 +44,64 @@ function resolveConfigFile(projectDir, configFile) {
124
44
  return configFile;
125
45
  }
126
46
 
127
- function loadConfigSync({configFile, resolveFrom = process.cwd(), defaults = {}} = {}) {
128
- let packageConf = pkgConf.sync('ava', {cwd: resolveFrom});
129
- const filepath = pkgConf.filepath(packageConf);
130
- const projectDir = filepath === null ? resolveFrom : path.dirname(filepath);
47
+ const gitScmFile = process.env.AVA_FAKE_SCM_ROOT || '.git';
131
48
 
132
- configFile = resolveConfigFile(projectDir, configFile);
133
- const allowConflictWithPackageJson = Boolean(configFile);
134
-
135
- let [{config: fileConf, fileForErrorMessage} = {config: NO_SUCH_FILE, fileForErrorMessage: undefined}, ...conflicting] = [
136
- loadJsConfig({projectDir, configFile}),
137
- loadCjsConfig({projectDir, configFile}),
138
- loadMjsConfig({projectDir, configFile})
139
- ].filter(result => result !== null);
49
+ async function findRepoRoot(fromDir) {
50
+ const {root} = path.parse(fromDir);
51
+ let dir = fromDir;
52
+ while (root !== dir) {
53
+ try {
54
+ const stat = await fs.promises.stat(path.join(dir, gitScmFile)); // eslint-disable-line no-await-in-loop
55
+ if (stat.isFile() || stat.isDirectory()) {
56
+ return dir;
57
+ }
58
+ } catch {}
140
59
 
141
- if (conflicting.length > 0) {
142
- throw new Error(`Conflicting configuration in ${fileForErrorMessage} and ${conflicting.map(({fileForErrorMessage}) => fileForErrorMessage).join(' & ')}`);
60
+ dir = path.dirname(dir);
143
61
  }
144
62
 
145
- if (fileConf !== NO_SUCH_FILE) {
146
- if (allowConflictWithPackageJson) {
147
- packageConf = {};
148
- } else if (Object.keys(packageConf).length > 0) {
149
- throw new Error(`Conflicting configuration in ${fileForErrorMessage} and package.json`);
150
- }
63
+ return root;
64
+ }
151
65
 
152
- if (fileConf && typeof fileConf.then === 'function') { // eslint-disable-line promise/prefer-await-to-then
153
- throw new TypeError(`${fileForErrorMessage} must not export a promise`);
154
- }
66
+ export async function loadConfig({configFile, resolveFrom = process.cwd(), defaults = {}} = {}) {
67
+ let packageConf = await packageConfig('ava', {cwd: resolveFrom});
68
+ const filepath = packageJsonPath(packageConf);
69
+ const projectDir = filepath === undefined ? resolveFrom : path.dirname(filepath);
155
70
 
156
- if (!isPlainObject(fileConf) && typeof fileConf !== 'function') {
157
- throw new TypeError(`${fileForErrorMessage} must export a plain object or factory function`);
158
- }
71
+ const repoRoot = await findRepoRoot(projectDir);
159
72
 
160
- if (typeof fileConf === 'function') {
161
- fileConf = fileConf({projectDir});
162
- if (fileConf && typeof fileConf.then === 'function') { // eslint-disable-line promise/prefer-await-to-then
163
- throw new TypeError(`Factory method exported by ${fileForErrorMessage} must not return a promise`);
164
- }
165
-
166
- if (!isPlainObject(fileConf)) {
167
- throw new TypeError(`Factory method exported by ${fileForErrorMessage} must return a plain object`);
168
- }
169
- }
73
+ // Conflicts are only allowed when an explicit config file is provided.
74
+ const allowConflictWithPackageJson = Boolean(configFile);
75
+ configFile = resolveConfigFile(configFile);
170
76
 
171
- if ('ava' in fileConf) {
172
- throw new Error(`Encountered ’ava’ property in ${fileForErrorMessage}; avoid wrapping the configuration`);
77
+ let fileConf = NO_SUCH_FILE;
78
+ let fileForErrorMessage;
79
+ let conflicting = [];
80
+ if (configFile) {
81
+ const loaded = await loadConfigFile({projectDir, configFile});
82
+ if (loaded !== null) {
83
+ ({config: fileConf, fileForErrorMessage} = loaded);
173
84
  }
174
- }
85
+ } else {
86
+ let searchDir = projectDir;
87
+ const stopAt = path.dirname(repoRoot);
88
+ do {
89
+ const results = await Promise.all([ // eslint-disable-line no-await-in-loop
90
+ loadConfigFile({projectDir, configFile: path.join(searchDir, 'ava.config.js')}),
91
+ loadConfigFile({projectDir, configFile: path.join(searchDir, 'ava.config.cjs')}),
92
+ loadConfigFile({projectDir, configFile: path.join(searchDir, 'ava.config.mjs')}),
93
+ ]);
175
94
 
176
- const config = {...defaults, nonSemVerExperiments: {}, ...fileConf, ...packageConf, projectDir};
95
+ [{config: fileConf, fileForErrorMessage, configFile} = {config: NO_SUCH_FILE, fileForErrorMessage: undefined}, ...conflicting] = results.filter(result => result !== null);
177
96
 
178
- const {nonSemVerExperiments: experiments} = config;
179
- if (!isPlainObject(experiments)) {
180
- throw new Error(`nonSemVerExperiments from ${fileForErrorMessage} must be an object`);
97
+ searchDir = path.dirname(searchDir);
98
+ } while (fileConf === NO_SUCH_FILE && searchDir !== stopAt);
181
99
  }
182
100
 
183
- for (const key of Object.keys(experiments)) {
184
- if (!EXPERIMENTS.has(key)) {
185
- throw new Error(`nonSemVerExperiments.${key} from ${fileForErrorMessage} is not a supported experiment`);
186
- }
187
- }
188
-
189
- return config;
190
- }
191
-
192
- exports.loadConfigSync = loadConfigSync;
193
-
194
- async function loadConfig({configFile, resolveFrom = process.cwd(), defaults = {}} = {}) {
195
- let packageConf = await pkgConf('ava', {cwd: resolveFrom});
196
- const filepath = pkgConf.filepath(packageConf);
197
- const projectDir = filepath === null ? resolveFrom : path.dirname(filepath);
198
-
199
- configFile = resolveConfigFile(projectDir, configFile);
200
- const allowConflictWithPackageJson = Boolean(configFile);
201
-
202
- // TODO: Refactor resolution logic to implement https://github.com/avajs/ava/issues/2285.
203
- let [{config: fileConf, fileForErrorMessage} = {config: NO_SUCH_FILE, fileForErrorMessage: undefined}, ...conflicting] = [
204
- loadJsConfig({projectDir, configFile}, true),
205
- loadCjsConfig({projectDir, configFile}),
206
- loadMjsConfig({projectDir, configFile}, true)
207
- ].filter(result => result !== null);
208
-
209
101
  if (conflicting.length > 0) {
210
102
  throw new Error(`Conflicting configuration in ${fileForErrorMessage} and ${conflicting.map(({fileForErrorMessage}) => fileForErrorMessage).join(' & ')}`);
211
103
  }
212
104
 
213
- let sawPromise = false;
214
105
  if (fileConf !== NO_SUCH_FILE) {
215
106
  if (allowConflictWithPackageJson) {
216
107
  packageConf = {};
@@ -218,21 +109,12 @@ async function loadConfig({configFile, resolveFrom = process.cwd(), defaults = {
218
109
  throw new Error(`Conflicting configuration in ${fileForErrorMessage} and package.json`);
219
110
  }
220
111
 
221
- if (fileConf && typeof fileConf.then === 'function') { // eslint-disable-line promise/prefer-await-to-then
222
- sawPromise = true;
223
- fileConf = await fileConf;
224
- }
225
-
226
112
  if (!isPlainObject(fileConf) && typeof fileConf !== 'function') {
227
113
  throw new TypeError(`${fileForErrorMessage} must export a plain object or factory function`);
228
114
  }
229
115
 
230
116
  if (typeof fileConf === 'function') {
231
- fileConf = fileConf({projectDir});
232
- if (fileConf && typeof fileConf.then === 'function') { // eslint-disable-line promise/prefer-await-to-then
233
- sawPromise = true;
234
- fileConf = await fileConf;
235
- }
117
+ fileConf = await fileConf({projectDir});
236
118
 
237
119
  if (!isPlainObject(fileConf)) {
238
120
  throw new TypeError(`Factory method exported by ${fileForErrorMessage} must return a plain object`);
@@ -244,7 +126,7 @@ async function loadConfig({configFile, resolveFrom = process.cwd(), defaults = {
244
126
  }
245
127
  }
246
128
 
247
- const config = {...defaults, nonSemVerExperiments: {}, ...fileConf, ...packageConf, projectDir};
129
+ const config = {...defaults, nonSemVerExperiments: {}, ...fileConf, ...packageConf, projectDir, configFile};
248
130
 
249
131
  const {nonSemVerExperiments: experiments} = config;
250
132
  if (!isPlainObject(experiments)) {
@@ -257,11 +139,5 @@ async function loadConfig({configFile, resolveFrom = process.cwd(), defaults = {
257
139
  }
258
140
  }
259
141
 
260
- if (sawPromise && experiments.nextGenConfig !== true) {
261
- throw new Error(`${fileForErrorMessage} exported a promise or an asynchronous factory function. You must enable the ’asyncConfigurationLoading’ experiment for this to work.`);
262
- }
263
-
264
142
  return config;
265
143
  }
266
-
267
- exports.loadConfig = loadConfig;
@@ -54,12 +54,12 @@ const deriveFromArray = (extensions, defaultModuleType) => {
54
54
  return moduleTypes;
55
55
  };
56
56
 
57
- module.exports = (configuredExtensions, defaultModuleType, experiments) => {
57
+ export default function moduleTypes(configuredExtensions, defaultModuleType) {
58
58
  if (configuredExtensions === undefined) {
59
59
  return {
60
60
  cjs: 'commonjs',
61
61
  mjs: 'module',
62
- js: defaultModuleType
62
+ js: defaultModuleType,
63
63
  };
64
64
  }
65
65
 
@@ -67,9 +67,5 @@ module.exports = (configuredExtensions, defaultModuleType, experiments) => {
67
67
  return deriveFromArray(configuredExtensions, defaultModuleType);
68
68
  }
69
69
 
70
- if (!experiments.configurableModuleFormat) {
71
- throw new Error('You must enable the `configurableModuleFormat` experiment in order to specify module types');
72
- }
73
-
74
70
  return deriveFromObject(configuredExtensions, defaultModuleType);
75
- };
71
+ }
@@ -1,7 +1,8 @@
1
- 'use strict';
2
- const arrgv = require('arrgv');
1
+ import process from 'node:process';
3
2
 
4
- function normalizeNodeArguments(fromConf = [], fromArgv = '') {
3
+ import arrgv from 'arrgv';
4
+
5
+ export default function normalizeNodeArguments(fromConf = [], fromArgv = '') {
5
6
  let parsedArgv = [];
6
7
  if (fromArgv !== '') {
7
8
  try {
@@ -13,5 +14,3 @@ function normalizeNodeArguments(fromConf = [], fromArgv = '') {
13
14
 
14
15
  return [...process.execArgv, ...fromConf, ...parsedArgv];
15
16
  }
16
-
17
- module.exports = normalizeNodeArguments;
File without changes
@@ -1,15 +1,26 @@
1
- 'use strict';
2
- function parseTestArgs(args) {
3
- const rawTitle = typeof args[0] === 'string' ? args.shift() : undefined;
4
- const receivedImplementationArray = Array.isArray(args[0]);
5
- const implementations = receivedImplementationArray ? args.shift() : args.splice(0, 1);
1
+ const buildTitle = (raw, implementation, args) => {
2
+ let value = implementation && implementation.title ? implementation.title(raw, ...args) : raw;
3
+ const isValid = typeof value === 'string';
4
+ if (isValid) {
5
+ value = value.trim().replace(/\s+/g, ' ');
6
+ }
6
7
 
7
- const buildTitle = implementation => {
8
- const title = implementation.title ? implementation.title(rawTitle, ...args) : rawTitle;
9
- return {title, isSet: typeof title !== 'undefined', isValid: typeof title === 'string', isEmpty: !title};
8
+ return {
9
+ raw,
10
+ value,
11
+ isSet: value !== undefined,
12
+ isValid,
13
+ isEmpty: !isValid || value === '',
10
14
  };
15
+ };
11
16
 
12
- return {args, buildTitle, implementations, rawTitle, receivedImplementationArray};
13
- }
17
+ export default function parseTestArgs(args) {
18
+ const rawTitle = typeof args[0] === 'string' ? args.shift() : undefined;
19
+ const implementation = args.shift();
14
20
 
15
- module.exports = parseTestArgs;
21
+ return {
22
+ args,
23
+ implementation: implementation && implementation.exec ? implementation.exec : implementation,
24
+ title: buildTitle(rawTitle, implementation, args),
25
+ };
26
+ }
package/lib/pkg.cjs ADDED
@@ -0,0 +1,2 @@
1
+ 'use strict';
2
+ module.exports = require('../package.json');
@@ -1,20 +1,30 @@
1
- const {EventEmitter, on} = require('events');
2
- const v8 = require('v8');
3
- const {workerData, parentPort} = require('worker_threads');
4
- const pkg = require('../../package.json');
1
+ import {EventEmitter, on} from 'node:events';
2
+ import process from 'node:process';
3
+ import {workerData, parentPort, threadId} from 'node:worker_threads';
5
4
 
6
- // Used to forward messages received over the `parentPort`. Every subscription
7
- // adds a listener, so do not enforce any maximums.
5
+ import pkg from '../pkg.cjs';
6
+
7
+ // Used to forward messages received over the `parentPort` and any direct ports
8
+ // to test workers. Every subscription adds a listener, so do not enforce any
9
+ // maximums.
8
10
  const events = new EventEmitter().setMaxListeners(0);
11
+ const emitMessage = message => {
12
+ // Wait for a turn of the event loop, to allow new subscriptions to be
13
+ // set up in response to the previous message.
14
+ setImmediate(() => events.emit('message', message));
15
+ };
9
16
 
10
17
  // Map of active test workers, used in receiveMessages() to get a reference to
11
18
  // the TestWorker instance, and relevant release functions.
12
19
  const activeTestWorkers = new Map();
13
20
 
21
+ const internalMessagePort = Symbol('Internal MessagePort');
22
+
14
23
  class TestWorker {
15
- constructor(id, file) {
24
+ constructor(id, file, port) {
16
25
  this.id = id;
17
26
  this.file = file;
27
+ this[internalMessagePort] = port;
18
28
  }
19
29
 
20
30
  teardown(fn) {
@@ -47,10 +57,10 @@ class TestWorker {
47
57
  }
48
58
 
49
59
  class ReceivedMessage {
50
- constructor(testWorker, id, serializedData) {
60
+ constructor(testWorker, id, data) {
51
61
  this.testWorker = testWorker;
52
62
  this.id = id;
53
- this.data = v8.deserialize(new Uint8Array(serializedData));
63
+ this.data = data;
54
64
  }
55
65
 
56
66
  reply(data) {
@@ -98,7 +108,7 @@ async function * receiveMessages(fromTestWorker, replyTo) {
98
108
 
99
109
  let received = messageCache.get(message);
100
110
  if (received === undefined) {
101
- received = new ReceivedMessage(active.instance, message.messageId, message.serializedData);
111
+ received = new ReceivedMessage(active.instance, message.messageId, message.data);
102
112
  messageCache.set(message, received);
103
113
  }
104
114
 
@@ -107,59 +117,47 @@ async function * receiveMessages(fromTestWorker, replyTo) {
107
117
  }
108
118
 
109
119
  let messageCounter = 0;
110
- const messageIdPrefix = `${workerData.id}/message`;
120
+ const messageIdPrefix = `${threadId}/message`;
111
121
  const nextMessageId = () => `${messageIdPrefix}/${++messageCounter}`;
112
122
 
113
123
  function publishMessage(testWorker, data, replyTo) {
114
124
  const id = nextMessageId();
115
- parentPort.postMessage({
125
+ testWorker[internalMessagePort].postMessage({
116
126
  type: 'message',
117
127
  messageId: id,
118
- testWorkerId: testWorker.id,
119
- serializedData: [...v8.serialize(data)],
120
- replyTo
128
+ data,
129
+ replyTo,
121
130
  });
122
131
 
123
132
  return {
124
133
  id,
125
134
  async * replies() {
126
135
  yield * receiveMessages(testWorker, id);
127
- }
136
+ },
128
137
  };
129
138
  }
130
139
 
131
140
  function broadcastMessage(data) {
132
141
  const id = nextMessageId();
133
- parentPort.postMessage({
134
- type: 'broadcast',
135
- messageId: id,
136
- serializedData: [...v8.serialize(data)]
137
- });
142
+ for (const trackedWorker of activeTestWorkers.values()) {
143
+ trackedWorker.instance[internalMessagePort].postMessage({
144
+ type: 'message',
145
+ messageId: id,
146
+ data,
147
+ });
148
+ }
138
149
 
139
150
  return {
140
151
  id,
141
152
  async * replies() {
142
153
  yield * receiveMessages(undefined, id);
143
- }
154
+ },
144
155
  };
145
156
  }
146
157
 
147
158
  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
- }
159
+ const {default: factory} = await import(workerData.filename); // eslint-disable-line node/no-unsupported-features/es-syntax
160
+ return factory;
163
161
  }
164
162
 
165
163
  let signalAvailable = () => {
@@ -175,7 +173,7 @@ loadFactory(workerData.filename).then(factory => {
175
173
 
176
174
  factory({
177
175
  negotiateProtocol(supported) {
178
- if (!supported.includes('experimental')) {
176
+ if (!supported.includes('ava-4')) {
179
177
  fatal = new Error(`This version of AVA (${pkg.version}) is not compatible with shared worker plugin at ${workerData.filename}`);
180
178
  throw fatal;
181
179
  }
@@ -184,12 +182,13 @@ loadFactory(workerData.filename).then(factory => {
184
182
 
185
183
  parentPort.on('message', async message => {
186
184
  if (message.type === 'register-test-worker') {
187
- const {id, file} = message;
188
- const instance = new TestWorker(id, file);
185
+ const {id, file, port} = message;
186
+ const instance = new TestWorker(id, file, port);
189
187
 
190
188
  activeTestWorkers.set(id, {instance, teardownFns: new Set()});
191
189
 
192
190
  produceTestWorker(instance);
191
+ port.on('message', message => emitMessage({testWorkerId: id, ...message}));
193
192
  }
194
193
 
195
194
  if (message.type === 'deregister-test-worker') {
@@ -205,18 +204,16 @@ loadFactory(workerData.filename).then(factory => {
205
204
 
206
205
  parentPort.postMessage({
207
206
  type: 'deregistered-test-worker',
208
- id
207
+ id,
209
208
  });
210
- }
211
209
 
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));
210
+ emitMessage(message);
211
+ }
215
212
  });
216
213
 
217
214
  return {
218
215
  initialData: workerData.initialData,
219
- protocol: 'experimental',
216
+ protocol: 'ava-4',
220
217
 
221
218
  ready() {
222
219
  signalAvailable();
@@ -235,9 +232,9 @@ loadFactory(workerData.filename).then(factory => {
235
232
  for await (const [worker] of on(events, 'testWorker')) {
236
233
  yield worker;
237
234
  }
238
- }
235
+ },
239
236
  };
240
- }
237
+ },
241
238
  });
242
239
  }).catch(error => {
243
240
  if (fatal === undefined) {