ava 3.15.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/entrypoints/cli.mjs +4 -0
- package/entrypoints/eslint-plugin-helper.cjs +109 -0
- package/entrypoints/main.cjs +2 -0
- package/entrypoints/main.mjs +1 -0
- package/entrypoints/plugin.cjs +2 -0
- package/entrypoints/plugin.mjs +4 -0
- package/index.d.ts +6 -816
- package/lib/api.js +108 -49
- package/lib/assert.js +255 -270
- package/lib/chalk.js +9 -14
- package/lib/cli.js +118 -112
- package/lib/code-excerpt.js +12 -17
- package/lib/concordance-options.js +29 -65
- package/lib/context-ref.js +3 -6
- package/lib/create-chain.js +32 -20
- package/lib/environment-variables.js +1 -4
- package/lib/eslint-plugin-helper-worker.js +73 -0
- package/lib/extensions.js +2 -2
- package/lib/fork.js +81 -84
- package/lib/glob-helpers.cjs +140 -0
- package/lib/globs.js +136 -163
- package/lib/{ipc-flow-control.js → ipc-flow-control.cjs} +1 -0
- package/lib/is-ci.js +4 -2
- package/lib/like-selector.js +7 -13
- package/lib/line-numbers.js +11 -18
- package/lib/load-config.js +56 -180
- package/lib/module-types.js +3 -7
- package/lib/node-arguments.js +4 -5
- package/lib/{now-and-timers.js → now-and-timers.cjs} +0 -0
- package/lib/parse-test-args.js +22 -11
- package/lib/pkg.cjs +2 -0
- package/lib/plugin-support/shared-worker-loader.js +45 -48
- package/lib/plugin-support/shared-workers.js +24 -46
- package/lib/provider-manager.js +20 -14
- package/lib/reporters/beautify-stack.js +6 -12
- package/lib/reporters/colors.js +40 -15
- package/lib/reporters/default.js +114 -364
- package/lib/reporters/format-serialized-error.js +7 -18
- package/lib/reporters/improper-usage-messages.js +8 -9
- package/lib/reporters/prefix-title.js +17 -15
- package/lib/reporters/tap.js +18 -25
- package/lib/run-status.js +29 -23
- package/lib/runner.js +157 -172
- package/lib/scheduler.js +53 -0
- package/lib/serialize-error.js +61 -64
- package/lib/snapshot-manager.js +271 -289
- package/lib/test.js +135 -291
- package/lib/watcher.js +69 -44
- package/lib/worker/base.js +208 -0
- package/lib/worker/channel.cjs +290 -0
- package/lib/worker/dependency-tracker.js +24 -23
- package/lib/worker/{ensure-forked.js → guard-environment.cjs} +5 -4
- package/lib/worker/line-numbers.js +58 -20
- package/lib/worker/main.cjs +12 -0
- package/lib/worker/{options.js → options.cjs} +0 -0
- package/lib/worker/{plugin.js → plugin.cjs} +30 -21
- package/lib/worker/state.cjs +5 -0
- package/lib/worker/utils.cjs +6 -0
- package/package.json +71 -68
- package/plugin.d.ts +51 -53
- package/readme.md +5 -13
- package/types/assertions.d.ts +327 -0
- package/types/subscribable.ts +6 -0
- package/types/test-fn.d.ts +231 -0
- package/types/try-fn.d.ts +58 -0
- package/cli.js +0 -11
- package/eslint-plugin-helper.js +0 -201
- package/index.js +0 -8
- package/lib/worker/ipc.js +0 -201
- package/lib/worker/main.js +0 -21
- package/lib/worker/subprocess.js +0 -266
- package/plugin.js +0 -9
package/lib/load-config.js
CHANGED
|
@@ -1,40 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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
|
|
46
|
-
if (!
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
142
|
-
throw new Error(`Conflicting configuration in ${fileForErrorMessage} and ${conflicting.map(({fileForErrorMessage}) => fileForErrorMessage).join(' & ')}`);
|
|
60
|
+
dir = path.dirname(dir);
|
|
143
61
|
}
|
|
144
62
|
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
153
|
-
|
|
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
|
-
|
|
157
|
-
throw new TypeError(`${fileForErrorMessage} must export a plain object or factory function`);
|
|
158
|
-
}
|
|
71
|
+
const repoRoot = await findRepoRoot(projectDir);
|
|
159
72
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
172
|
-
|
|
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
|
-
|
|
95
|
+
[{config: fileConf, fileForErrorMessage, configFile} = {config: NO_SUCH_FILE, fileForErrorMessage: undefined}, ...conflicting] = results.filter(result => result !== null);
|
|
177
96
|
|
|
178
|
-
|
|
179
|
-
|
|
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;
|
package/lib/module-types.js
CHANGED
|
@@ -54,12 +54,12 @@ const deriveFromArray = (extensions, defaultModuleType) => {
|
|
|
54
54
|
return moduleTypes;
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
-
|
|
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
|
+
}
|
package/lib/node-arguments.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
const arrgv = require('arrgv');
|
|
1
|
+
import process from 'node:process';
|
|
3
2
|
|
|
4
|
-
|
|
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
|
package/lib/parse-test-args.js
CHANGED
|
@@ -1,15 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
return {
|
|
9
|
+
raw,
|
|
10
|
+
value,
|
|
11
|
+
isSet: value !== undefined,
|
|
12
|
+
isValid,
|
|
13
|
+
isEmpty: !isValid || value === '',
|
|
10
14
|
};
|
|
15
|
+
};
|
|
11
16
|
|
|
12
|
-
|
|
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
|
-
|
|
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
|
@@ -1,20 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
7
|
-
|
|
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,
|
|
60
|
+
constructor(testWorker, id, data) {
|
|
51
61
|
this.testWorker = testWorker;
|
|
52
62
|
this.id = id;
|
|
53
|
-
this.data =
|
|
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.
|
|
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 = `${
|
|
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
|
-
|
|
125
|
+
testWorker[internalMessagePort].postMessage({
|
|
116
126
|
type: 'message',
|
|
117
127
|
messageId: id,
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
149
|
-
|
|
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('
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
setImmediate(() => events.emit('message', message));
|
|
210
|
+
emitMessage(message);
|
|
211
|
+
}
|
|
215
212
|
});
|
|
216
213
|
|
|
217
214
|
return {
|
|
218
215
|
initialData: workerData.initialData,
|
|
219
|
-
protocol: '
|
|
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) {
|