mocha 7.1.1 → 8.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/CHANGELOG.md +110 -2
- package/bin/mocha +24 -4
- package/browser-entry.js +11 -0
- package/lib/browser/growl.js +2 -1
- package/lib/cli/cli.js +6 -3
- package/lib/cli/collect-files.js +14 -8
- package/lib/cli/config.js +6 -6
- package/lib/cli/init.js +1 -2
- package/lib/cli/node-flags.js +2 -2
- package/lib/cli/options.js +14 -90
- package/lib/cli/run-helpers.js +133 -36
- package/lib/cli/run-option-metadata.js +4 -2
- package/lib/cli/run.js +71 -11
- package/lib/cli/watch-run.js +202 -50
- package/lib/context.js +0 -15
- package/lib/errors.js +202 -9
- package/lib/esm-utils.js +11 -6
- package/lib/hook.js +32 -0
- package/lib/interfaces/common.js +16 -10
- package/lib/mocha.js +289 -123
- package/lib/mocharc.json +0 -1
- package/lib/nodejs/buffered-worker-pool.js +174 -0
- package/lib/{growl.js → nodejs/growl.js} +3 -2
- package/lib/nodejs/parallel-buffered-runner.js +293 -0
- package/lib/nodejs/reporters/parallel-buffered.js +133 -0
- package/lib/nodejs/serializer.js +402 -0
- package/lib/nodejs/worker.js +154 -0
- package/lib/reporters/base.js +2 -2
- package/lib/reporters/doc.js +6 -0
- package/lib/reporters/json-stream.js +1 -0
- package/lib/reporters/json.js +1 -0
- package/lib/reporters/landing.js +11 -3
- package/lib/reporters/tap.js +1 -2
- package/lib/reporters/xunit.js +3 -2
- package/lib/runnable.js +38 -72
- package/lib/runner.js +185 -85
- package/lib/suite.js +60 -27
- package/lib/test.js +48 -3
- package/lib/utils.js +32 -40
- package/mocha.js +2418 -2199
- package/package.json +71 -56
package/lib/cli/run-helpers.js
CHANGED
|
@@ -10,10 +10,12 @@
|
|
|
10
10
|
const fs = require('fs');
|
|
11
11
|
const path = require('path');
|
|
12
12
|
const debug = require('debug')('mocha:cli:run:helpers');
|
|
13
|
-
const watchRun = require('./watch-run');
|
|
13
|
+
const {watchRun, watchParallelRun} = require('./watch-run');
|
|
14
14
|
const collectFiles = require('./collect-files');
|
|
15
|
-
|
|
16
|
-
const
|
|
15
|
+
const {type} = require('../utils');
|
|
16
|
+
const {format} = require('util');
|
|
17
|
+
const {createInvalidPluginError, createUnsupportedError} = require('../errors');
|
|
18
|
+
const {requireOrImport} = require('../esm-utils');
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
21
|
* Exits Mocha when tests + code under test has finished execution (default)
|
|
@@ -73,20 +75,66 @@ exports.list = str =>
|
|
|
73
75
|
Array.isArray(str) ? exports.list(str.join(',')) : str.split(/ *, */);
|
|
74
76
|
|
|
75
77
|
/**
|
|
76
|
-
* `require()` the modules as required by `--require <require
|
|
78
|
+
* `require()` the modules as required by `--require <require>`.
|
|
79
|
+
*
|
|
80
|
+
* Returns array of `mochaHooks` exports, if any.
|
|
77
81
|
* @param {string[]} requires - Modules to require
|
|
82
|
+
* @returns {Promise<MochaRootHookObject|MochaRootHookFunction>} Any root hooks
|
|
78
83
|
* @private
|
|
79
84
|
*/
|
|
80
|
-
exports.handleRequires = (requires = []) => {
|
|
81
|
-
|
|
85
|
+
exports.handleRequires = async (requires = []) => {
|
|
86
|
+
const acc = [];
|
|
87
|
+
for (const mod of requires) {
|
|
82
88
|
let modpath = mod;
|
|
83
|
-
|
|
89
|
+
// this is relative to cwd
|
|
90
|
+
if (fs.existsSync(mod) || fs.existsSync(`${mod}.js`)) {
|
|
84
91
|
modpath = path.resolve(mod);
|
|
85
|
-
debug(
|
|
92
|
+
debug('resolved required file %s to %s', mod, modpath);
|
|
86
93
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
94
|
+
const requiredModule = await requireOrImport(modpath);
|
|
95
|
+
if (
|
|
96
|
+
requiredModule &&
|
|
97
|
+
typeof requiredModule === 'object' &&
|
|
98
|
+
requiredModule.mochaHooks
|
|
99
|
+
) {
|
|
100
|
+
const mochaHooksType = type(requiredModule.mochaHooks);
|
|
101
|
+
if (/function$/.test(mochaHooksType) || mochaHooksType === 'object') {
|
|
102
|
+
debug('found root hooks in required file %s', mod);
|
|
103
|
+
acc.push(requiredModule.mochaHooks);
|
|
104
|
+
} else {
|
|
105
|
+
throw createUnsupportedError(
|
|
106
|
+
'mochaHooks must be an object or a function returning (or fulfilling with) an object'
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
debug('loaded required module "%s"', mod);
|
|
111
|
+
}
|
|
112
|
+
return acc;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Loads root hooks as exported via `mochaHooks` from required files.
|
|
117
|
+
* These can be sync/async functions returning objects, or just objects.
|
|
118
|
+
* Flattens to a single object.
|
|
119
|
+
* @param {Array<MochaRootHookObject|MochaRootHookFunction>} rootHooks - Array of root hooks
|
|
120
|
+
* @private
|
|
121
|
+
* @returns {MochaRootHookObject}
|
|
122
|
+
*/
|
|
123
|
+
exports.loadRootHooks = async rootHooks => {
|
|
124
|
+
const rootHookObjects = await Promise.all(
|
|
125
|
+
rootHooks.map(async hook => (/function$/.test(type(hook)) ? hook() : hook))
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
return rootHookObjects.reduce(
|
|
129
|
+
(acc, hook) => {
|
|
130
|
+
acc.beforeAll = acc.beforeAll.concat(hook.beforeAll || []);
|
|
131
|
+
acc.beforeEach = acc.beforeEach.concat(hook.beforeEach || []);
|
|
132
|
+
acc.afterAll = acc.afterAll.concat(hook.afterAll || []);
|
|
133
|
+
acc.afterEach = acc.afterEach.concat(hook.afterEach || []);
|
|
134
|
+
return acc;
|
|
135
|
+
},
|
|
136
|
+
{beforeAll: [], beforeEach: [], afterAll: [], afterEach: []}
|
|
137
|
+
);
|
|
90
138
|
};
|
|
91
139
|
|
|
92
140
|
/**
|
|
@@ -101,32 +149,61 @@ exports.handleRequires = (requires = []) => {
|
|
|
101
149
|
*/
|
|
102
150
|
const singleRun = async (mocha, {exit}, fileCollectParams) => {
|
|
103
151
|
const files = collectFiles(fileCollectParams);
|
|
104
|
-
debug('
|
|
152
|
+
debug('single run with %d file(s)', files.length);
|
|
105
153
|
mocha.files = files;
|
|
106
154
|
|
|
155
|
+
// handles ESM modules
|
|
107
156
|
await mocha.loadFilesAsync();
|
|
108
157
|
return mocha.run(exit ? exitMocha : exitMochaLater);
|
|
109
158
|
};
|
|
110
159
|
|
|
111
160
|
/**
|
|
112
|
-
*
|
|
161
|
+
* Collect files and run tests (using `BufferedRunner`).
|
|
162
|
+
*
|
|
163
|
+
* This is `async` for consistency.
|
|
164
|
+
*
|
|
113
165
|
* @param {Mocha} mocha - Mocha instance
|
|
114
|
-
* @param {
|
|
166
|
+
* @param {Options} options - Command line options
|
|
167
|
+
* @param {Object} fileCollectParams - Parameters that control test
|
|
168
|
+
* file collection. See `lib/cli/collect-files.js`.
|
|
169
|
+
* @returns {Promise<BufferedRunner>}
|
|
170
|
+
* @ignore
|
|
115
171
|
* @private
|
|
116
|
-
|
|
172
|
+
*/
|
|
173
|
+
const parallelRun = async (mocha, options, fileCollectParams) => {
|
|
174
|
+
const files = collectFiles(fileCollectParams);
|
|
175
|
+
debug(
|
|
176
|
+
'executing %d test file(s) across %d concurrent jobs',
|
|
177
|
+
files.length,
|
|
178
|
+
options.jobs
|
|
179
|
+
);
|
|
180
|
+
mocha.files = files;
|
|
181
|
+
|
|
182
|
+
// note that we DO NOT load any files here; this is handled by the worker
|
|
183
|
+
return mocha.run(options.exit ? exitMocha : exitMochaLater);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Actually run tests. Delegates to one of four different functions:
|
|
188
|
+
* - `singleRun`: run tests in serial & exit
|
|
189
|
+
* - `watchRun`: run tests in serial, rerunning as files change
|
|
190
|
+
* - `parallelRun`: run tests in parallel & exit
|
|
191
|
+
* - `watchParallelRun`: run tests in parallel, rerunning as files change
|
|
192
|
+
* @param {Mocha} mocha - Mocha instance
|
|
193
|
+
* @param {Options} opts - Command line options
|
|
194
|
+
* @private
|
|
195
|
+
* @returns {Promise<Runner>}
|
|
117
196
|
*/
|
|
118
197
|
exports.runMocha = async (mocha, options) => {
|
|
119
198
|
const {
|
|
120
199
|
watch = false,
|
|
121
200
|
extension = [],
|
|
122
|
-
exit = false,
|
|
123
201
|
ignore = [],
|
|
124
202
|
file = [],
|
|
203
|
+
parallel = false,
|
|
125
204
|
recursive = false,
|
|
126
205
|
sort = false,
|
|
127
|
-
spec = []
|
|
128
|
-
watchFiles,
|
|
129
|
-
watchIgnore
|
|
206
|
+
spec = []
|
|
130
207
|
} = options;
|
|
131
208
|
|
|
132
209
|
const fileCollectParams = {
|
|
@@ -138,43 +215,63 @@ exports.runMocha = async (mocha, options) => {
|
|
|
138
215
|
spec
|
|
139
216
|
};
|
|
140
217
|
|
|
218
|
+
let run;
|
|
141
219
|
if (watch) {
|
|
142
|
-
|
|
220
|
+
run = parallel ? watchParallelRun : watchRun;
|
|
143
221
|
} else {
|
|
144
|
-
|
|
222
|
+
run = parallel ? parallelRun : singleRun;
|
|
145
223
|
}
|
|
224
|
+
|
|
225
|
+
return run(mocha, options, fileCollectParams);
|
|
146
226
|
};
|
|
147
227
|
|
|
148
228
|
/**
|
|
149
|
-
* Used for `--reporter` and `--ui`. Ensures there's only one, and asserts
|
|
150
|
-
*
|
|
151
|
-
* @
|
|
152
|
-
* interfaces from loading.
|
|
229
|
+
* Used for `--reporter` and `--ui`. Ensures there's only one, and asserts that
|
|
230
|
+
* it actually exists. This must be run _after_ requires are processed (see
|
|
231
|
+
* {@link handleRequires}), as it'll prevent interfaces from loading otherwise.
|
|
153
232
|
* @param {Object} opts - Options object
|
|
154
|
-
* @param {
|
|
155
|
-
* @param {Object} [map] - An object perhaps having key `key
|
|
233
|
+
* @param {"reporter"|"interface"} pluginType - Type of plugin.
|
|
234
|
+
* @param {Object} [map] - An object perhaps having key `key`. Used as a cache
|
|
235
|
+
* of sorts; `Mocha.reporters` is one, where each key corresponds to a reporter
|
|
236
|
+
* name
|
|
156
237
|
* @private
|
|
157
238
|
*/
|
|
158
|
-
exports.validatePlugin = (opts,
|
|
159
|
-
|
|
160
|
-
|
|
239
|
+
exports.validatePlugin = (opts, pluginType, map = {}) => {
|
|
240
|
+
/**
|
|
241
|
+
* This should be a unique identifier; either a string (present in `map`),
|
|
242
|
+
* or a resolvable (via `require.resolve`) module ID/path.
|
|
243
|
+
* @type {string}
|
|
244
|
+
*/
|
|
245
|
+
const pluginId = opts[pluginType];
|
|
246
|
+
|
|
247
|
+
if (Array.isArray(pluginId)) {
|
|
248
|
+
throw createInvalidPluginError(
|
|
249
|
+
`"--${pluginType}" can only be specified once`,
|
|
250
|
+
pluginType
|
|
251
|
+
);
|
|
161
252
|
}
|
|
162
253
|
|
|
163
|
-
const unknownError =
|
|
254
|
+
const unknownError = err =>
|
|
255
|
+
createInvalidPluginError(
|
|
256
|
+
format('Could not load %s "%s":\n\n %O', pluginType, pluginId, err),
|
|
257
|
+
pluginType,
|
|
258
|
+
pluginId
|
|
259
|
+
);
|
|
164
260
|
|
|
165
|
-
if
|
|
261
|
+
// if this exists, then it's already loaded, so nothing more to do.
|
|
262
|
+
if (!map[pluginId]) {
|
|
166
263
|
try {
|
|
167
|
-
opts[
|
|
264
|
+
opts[pluginType] = require(pluginId);
|
|
168
265
|
} catch (err) {
|
|
169
266
|
if (err.code === 'MODULE_NOT_FOUND') {
|
|
170
267
|
// Try to load reporters from a path (absolute or relative)
|
|
171
268
|
try {
|
|
172
|
-
opts[
|
|
269
|
+
opts[pluginType] = require(path.resolve(pluginId));
|
|
173
270
|
} catch (err) {
|
|
174
|
-
throw unknownError();
|
|
271
|
+
throw unknownError(err);
|
|
175
272
|
}
|
|
176
273
|
} else {
|
|
177
|
-
throw unknownError();
|
|
274
|
+
throw unknownError(err);
|
|
178
275
|
}
|
|
179
276
|
}
|
|
180
277
|
}
|
|
@@ -42,16 +42,16 @@ exports.types = {
|
|
|
42
42
|
'list-interfaces',
|
|
43
43
|
'list-reporters',
|
|
44
44
|
'no-colors',
|
|
45
|
+
'parallel',
|
|
45
46
|
'recursive',
|
|
46
47
|
'sort',
|
|
47
48
|
'watch'
|
|
48
49
|
],
|
|
49
|
-
number: ['retries'],
|
|
50
|
+
number: ['retries', 'jobs'],
|
|
50
51
|
string: [
|
|
51
52
|
'config',
|
|
52
53
|
'fgrep',
|
|
53
54
|
'grep',
|
|
54
|
-
'opts',
|
|
55
55
|
'package',
|
|
56
56
|
'reporter',
|
|
57
57
|
'ui',
|
|
@@ -76,7 +76,9 @@ exports.aliases = {
|
|
|
76
76
|
growl: ['G'],
|
|
77
77
|
ignore: ['exclude'],
|
|
78
78
|
invert: ['i'],
|
|
79
|
+
jobs: ['j'],
|
|
79
80
|
'no-colors': ['C'],
|
|
81
|
+
parallel: ['p'],
|
|
80
82
|
reporter: ['R'],
|
|
81
83
|
'reporter-option': ['reporter-options', 'O'],
|
|
82
84
|
require: ['r'],
|
package/lib/cli/run.js
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* @private
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
const symbols = require('log-symbols');
|
|
11
|
+
const ansi = require('ansi-colors');
|
|
10
12
|
const Mocha = require('../mocha');
|
|
11
13
|
const {
|
|
12
14
|
createUnsupportedError,
|
|
@@ -18,6 +20,7 @@ const {
|
|
|
18
20
|
list,
|
|
19
21
|
handleRequires,
|
|
20
22
|
validatePlugin,
|
|
23
|
+
loadRootHooks,
|
|
21
24
|
runMocha
|
|
22
25
|
} = require('./run-helpers');
|
|
23
26
|
const {ONE_AND_DONES, ONE_AND_DONE_ARGS} = require('./one-and-dones');
|
|
@@ -150,6 +153,13 @@ exports.builder = yargs =>
|
|
|
150
153
|
description: 'Inverts --grep and --fgrep matches',
|
|
151
154
|
group: GROUPS.FILTERS
|
|
152
155
|
},
|
|
156
|
+
jobs: {
|
|
157
|
+
description:
|
|
158
|
+
'Number of concurrent jobs for --parallel; use 1 to run in serial',
|
|
159
|
+
defaultDescription: '(number of CPU cores - 1)',
|
|
160
|
+
requiresArg: true,
|
|
161
|
+
group: GROUPS.RULES
|
|
162
|
+
},
|
|
153
163
|
'list-interfaces': {
|
|
154
164
|
conflicts: Array.from(ONE_AND_DONE_ARGS),
|
|
155
165
|
description: 'List built-in user interfaces & exit'
|
|
@@ -163,19 +173,16 @@ exports.builder = yargs =>
|
|
|
163
173
|
group: GROUPS.OUTPUT,
|
|
164
174
|
hidden: true
|
|
165
175
|
},
|
|
166
|
-
opts: {
|
|
167
|
-
default: defaults.opts,
|
|
168
|
-
description: 'Path to `mocha.opts` (DEPRECATED)',
|
|
169
|
-
group: GROUPS.CONFIG,
|
|
170
|
-
normalize: true,
|
|
171
|
-
requiresArg: true
|
|
172
|
-
},
|
|
173
176
|
package: {
|
|
174
177
|
description: 'Path to package.json for config',
|
|
175
178
|
group: GROUPS.CONFIG,
|
|
176
179
|
normalize: true,
|
|
177
180
|
requiresArg: true
|
|
178
181
|
},
|
|
182
|
+
parallel: {
|
|
183
|
+
description: 'Run tests in parallel',
|
|
184
|
+
group: GROUPS.RULES
|
|
185
|
+
},
|
|
179
186
|
recursive: {
|
|
180
187
|
description: 'Look for tests in subdirectories',
|
|
181
188
|
group: GROUPS.FILES
|
|
@@ -278,6 +285,40 @@ exports.builder = yargs =>
|
|
|
278
285
|
);
|
|
279
286
|
}
|
|
280
287
|
|
|
288
|
+
if (argv.parallel) {
|
|
289
|
+
// yargs.conflicts() can't deal with `--file foo.js --no-parallel`, either
|
|
290
|
+
if (argv.file) {
|
|
291
|
+
throw createUnsupportedError(
|
|
292
|
+
'--parallel runs test files in a non-deterministic order, and is mutually exclusive with --file'
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// or this
|
|
297
|
+
if (argv.sort) {
|
|
298
|
+
throw createUnsupportedError(
|
|
299
|
+
'--parallel runs test files in a non-deterministic order, and is mutually exclusive with --sort'
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (argv.reporter === 'progress') {
|
|
304
|
+
throw createUnsupportedError(
|
|
305
|
+
'--reporter=progress is mutually exclusive with --parallel'
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (argv.reporter === 'markdown') {
|
|
310
|
+
throw createUnsupportedError(
|
|
311
|
+
'--reporter=markdown is mutually exclusive with --parallel'
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (argv.reporter === 'json-stream') {
|
|
316
|
+
throw createUnsupportedError(
|
|
317
|
+
'--reporter=json-stream is mutually exclusive with --parallel'
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
281
322
|
if (argv.compilers) {
|
|
282
323
|
throw createUnsupportedError(
|
|
283
324
|
`--compilers is DEPRECATED and no longer supported.
|
|
@@ -285,13 +326,32 @@ exports.builder = yargs =>
|
|
|
285
326
|
);
|
|
286
327
|
}
|
|
287
328
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
329
|
+
if (argv.opts) {
|
|
330
|
+
throw createUnsupportedError(
|
|
331
|
+
`--opts: configuring Mocha via 'mocha.opts' is DEPRECATED and no longer supported.
|
|
332
|
+
Please use a configuration file instead.`
|
|
333
|
+
);
|
|
334
|
+
}
|
|
292
335
|
|
|
293
336
|
return true;
|
|
294
337
|
})
|
|
338
|
+
.middleware(async (argv, yargs) => {
|
|
339
|
+
// currently a failing middleware does not work nicely with yargs' `fail()`.
|
|
340
|
+
try {
|
|
341
|
+
// load requires first, because it can impact "plugin" validation
|
|
342
|
+
const rawRootHooks = await handleRequires(argv.require);
|
|
343
|
+
validatePlugin(argv, 'reporter', Mocha.reporters);
|
|
344
|
+
validatePlugin(argv, 'ui', Mocha.interfaces);
|
|
345
|
+
|
|
346
|
+
if (rawRootHooks && rawRootHooks.length) {
|
|
347
|
+
argv.rootHooks = await loadRootHooks(rawRootHooks);
|
|
348
|
+
}
|
|
349
|
+
} catch (err) {
|
|
350
|
+
// this could be a bad --require, bad reporter, ui, etc.
|
|
351
|
+
console.error(`\n${symbols.error} ${ansi.red('ERROR:')}`, err);
|
|
352
|
+
yargs.exit(1);
|
|
353
|
+
}
|
|
354
|
+
})
|
|
295
355
|
.array(types.array)
|
|
296
356
|
.boolean(types.boolean)
|
|
297
357
|
.string(types.string)
|