mocha 7.1.2 → 8.1.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/CHANGELOG.md +143 -2
- package/bin/mocha +24 -4
- package/browser-entry.js +37 -9
- package/lib/browser/growl.js +2 -1
- package/lib/browser/highlight-tags.js +39 -0
- package/lib/browser/parse-query.js +24 -0
- package/lib/browser/progress.js +4 -0
- package/lib/browser/template.html +7 -5
- package/lib/cli/cli.js +6 -3
- package/lib/cli/collect-files.js +17 -10
- package/lib/cli/config.js +6 -6
- package/lib/cli/init.js +1 -2
- package/lib/cli/lookup-files.js +145 -0
- 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 +211 -51
- 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 +21 -10
- package/lib/mocha.js +301 -126
- 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 +295 -0
- package/lib/nodejs/reporters/parallel-buffered.js +133 -0
- package/lib/nodejs/serializer.js +404 -0
- package/lib/nodejs/worker.js +154 -0
- package/lib/pending.js +4 -0
- package/lib/reporters/base.js +25 -12
- 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 +39 -47
- package/lib/runner.js +219 -118
- package/lib/suite.js +61 -27
- package/lib/test.js +48 -3
- package/lib/utils.js +33 -209
- package/mocha.js +25522 -17715
- package/mocha.js.map +1 -0
- package/package.json +87 -68
- package/lib/browser/tty.js +0 -13
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var fs = require('fs');
|
|
4
|
+
var path = require('path');
|
|
5
|
+
var glob = require('glob');
|
|
6
|
+
var {format} = require('util');
|
|
7
|
+
var errors = require('../errors');
|
|
8
|
+
var createNoFilesMatchPatternError = errors.createNoFilesMatchPatternError;
|
|
9
|
+
var createMissingArgumentError = errors.createMissingArgumentError;
|
|
10
|
+
var {sQuote, dQuote} = require('../utils');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Determines if pathname would be a "hidden" file (or directory) on UN*X.
|
|
14
|
+
*
|
|
15
|
+
* @description
|
|
16
|
+
* On UN*X, pathnames beginning with a full stop (aka dot) are hidden during
|
|
17
|
+
* typical usage. Dotfiles, plain-text configuration files, are prime examples.
|
|
18
|
+
*
|
|
19
|
+
* @see {@link http://xahlee.info/UnixResource_dir/writ/unix_origin_of_dot_filename.html|Origin of Dot File Names}
|
|
20
|
+
*
|
|
21
|
+
* @private
|
|
22
|
+
* @param {string} pathname - Pathname to check for match.
|
|
23
|
+
* @return {boolean} whether pathname would be considered a hidden file.
|
|
24
|
+
* @example
|
|
25
|
+
* isHiddenOnUnix('.profile'); // => true
|
|
26
|
+
*/
|
|
27
|
+
function isHiddenOnUnix(pathname) {
|
|
28
|
+
return path.basename(pathname)[0] === '.';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Determines if pathname has a matching file extension.
|
|
33
|
+
*
|
|
34
|
+
* @private
|
|
35
|
+
* @param {string} pathname - Pathname to check for match.
|
|
36
|
+
* @param {string[]} exts - List of file extensions (sans period).
|
|
37
|
+
* @return {boolean} whether file extension matches.
|
|
38
|
+
* @example
|
|
39
|
+
* hasMatchingExtname('foo.html', ['js', 'css']); // => false
|
|
40
|
+
*/
|
|
41
|
+
function hasMatchingExtname(pathname, exts) {
|
|
42
|
+
var suffix = path.extname(pathname).slice(1);
|
|
43
|
+
return exts.some(function(element) {
|
|
44
|
+
return suffix === element;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Lookup file names at the given `path`.
|
|
50
|
+
*
|
|
51
|
+
* @description
|
|
52
|
+
* Filenames are returned in _traversal_ order by the OS/filesystem.
|
|
53
|
+
* **Make no assumption that the names will be sorted in any fashion.**
|
|
54
|
+
*
|
|
55
|
+
* @public
|
|
56
|
+
* @memberof Mocha.utils
|
|
57
|
+
* @param {string} filepath - Base path to start searching from.
|
|
58
|
+
* @param {string[]} [extensions=[]] - File extensions to look for.
|
|
59
|
+
* @param {boolean} [recursive=false] - Whether to recurse into subdirectories.
|
|
60
|
+
* @return {string[]} An array of paths.
|
|
61
|
+
* @throws {Error} if no files match pattern.
|
|
62
|
+
* @throws {TypeError} if `filepath` is directory and `extensions` not provided.
|
|
63
|
+
*/
|
|
64
|
+
module.exports = function lookupFiles(filepath, extensions, recursive) {
|
|
65
|
+
extensions = extensions || [];
|
|
66
|
+
recursive = recursive || false;
|
|
67
|
+
var files = [];
|
|
68
|
+
var stat;
|
|
69
|
+
|
|
70
|
+
if (!fs.existsSync(filepath)) {
|
|
71
|
+
var pattern;
|
|
72
|
+
if (glob.hasMagic(filepath)) {
|
|
73
|
+
// Handle glob as is without extensions
|
|
74
|
+
pattern = filepath;
|
|
75
|
+
} else {
|
|
76
|
+
// glob pattern e.g. 'filepath+(.js|.ts)'
|
|
77
|
+
var strExtensions = extensions
|
|
78
|
+
.map(function(v) {
|
|
79
|
+
return '.' + v;
|
|
80
|
+
})
|
|
81
|
+
.join('|');
|
|
82
|
+
pattern = filepath + '+(' + strExtensions + ')';
|
|
83
|
+
}
|
|
84
|
+
files = glob.sync(pattern, {nodir: true});
|
|
85
|
+
if (!files.length) {
|
|
86
|
+
throw createNoFilesMatchPatternError(
|
|
87
|
+
'Cannot find any files matching pattern ' + dQuote(filepath),
|
|
88
|
+
filepath
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
return files;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Handle file
|
|
95
|
+
try {
|
|
96
|
+
stat = fs.statSync(filepath);
|
|
97
|
+
if (stat.isFile()) {
|
|
98
|
+
return filepath;
|
|
99
|
+
}
|
|
100
|
+
} catch (err) {
|
|
101
|
+
// ignore error
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Handle directory
|
|
106
|
+
fs.readdirSync(filepath).forEach(function(dirent) {
|
|
107
|
+
var pathname = path.join(filepath, dirent);
|
|
108
|
+
var stat;
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
stat = fs.statSync(pathname);
|
|
112
|
+
if (stat.isDirectory()) {
|
|
113
|
+
if (recursive) {
|
|
114
|
+
files = files.concat(lookupFiles(pathname, extensions, recursive));
|
|
115
|
+
}
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
} catch (err) {
|
|
119
|
+
// ignore error
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (!extensions.length) {
|
|
123
|
+
throw createMissingArgumentError(
|
|
124
|
+
format(
|
|
125
|
+
'Argument %s required when argument %s is a directory',
|
|
126
|
+
sQuote('extensions'),
|
|
127
|
+
sQuote('filepath')
|
|
128
|
+
),
|
|
129
|
+
'extensions',
|
|
130
|
+
'array'
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (
|
|
135
|
+
!stat.isFile() ||
|
|
136
|
+
!hasMatchingExtname(pathname, extensions) ||
|
|
137
|
+
isHiddenOnUnix(pathname)
|
|
138
|
+
) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
files.push(pathname);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
return files;
|
|
145
|
+
};
|
package/lib/cli/node-flags.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* @module
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
const nodeFlags =
|
|
9
|
+
const nodeFlags = process.allowedNodeEnvironmentFlags;
|
|
10
10
|
const unparse = require('yargs-unparser');
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -48,7 +48,7 @@ exports.isNodeFlag = (flag, bareword = true) => {
|
|
|
48
48
|
// check actual node flags from `process.allowedNodeEnvironmentFlags`,
|
|
49
49
|
// then historical support for various V8 and non-`NODE_OPTIONS` flags
|
|
50
50
|
// and also any V8 flags with `--v8-` prefix
|
|
51
|
-
(nodeFlags.has(flag) ||
|
|
51
|
+
((nodeFlags && nodeFlags.has(flag)) ||
|
|
52
52
|
debugFlags.has(flag) ||
|
|
53
53
|
/(?:preserve-symlinks(?:-main)?|harmony(?:[_-]|$)|(?:trace[_-].+$)|gc(?:[_-]global)?$|es[_-]staging$|use[_-]strict$|v8[_-](?!options).+?$)/.test(
|
|
54
54
|
flag
|
package/lib/cli/options.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Main entry point for handling filesystem-based configuration,
|
|
5
|
-
* whether that's
|
|
5
|
+
* whether that's a config file or `package.json` or whatever.
|
|
6
6
|
* @module
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -15,7 +15,6 @@ const mocharc = require('../mocharc.json');
|
|
|
15
15
|
const {list} = require('./run-helpers');
|
|
16
16
|
const {loadConfig, findConfig} = require('./config');
|
|
17
17
|
const findUp = require('find-up');
|
|
18
|
-
const {deprecate} = require('../utils');
|
|
19
18
|
const debug = require('debug')('mocha:cli:options');
|
|
20
19
|
const {isNodeFlag} = require('./node-flags');
|
|
21
20
|
|
|
@@ -55,16 +54,19 @@ const configuration = Object.assign({}, YARGS_PARSER_CONFIG, {
|
|
|
55
54
|
|
|
56
55
|
/**
|
|
57
56
|
* This is a really fancy way to:
|
|
58
|
-
* - ensure unique values
|
|
59
|
-
* -
|
|
57
|
+
* - `array`-type options: ensure unique values and evtl. split comma-delimited lists
|
|
58
|
+
* - `boolean`/`number`/`string`- options: use last element when given multiple times
|
|
60
59
|
* This is passed as the `coerce` option to `yargs-parser`
|
|
61
60
|
* @private
|
|
62
61
|
* @ignore
|
|
63
62
|
*/
|
|
63
|
+
const globOptions = ['spec', 'ignore'];
|
|
64
64
|
const coerceOpts = Object.assign(
|
|
65
65
|
types.array.reduce(
|
|
66
66
|
(acc, arg) =>
|
|
67
|
-
Object.assign(acc, {
|
|
67
|
+
Object.assign(acc, {
|
|
68
|
+
[arg]: v => Array.from(new Set(globOptions.includes(arg) ? v : list(v)))
|
|
69
|
+
}),
|
|
68
70
|
{}
|
|
69
71
|
),
|
|
70
72
|
types.boolean
|
|
@@ -143,71 +145,6 @@ const parse = (args = [], defaultValues = {}, ...configObjects) => {
|
|
|
143
145
|
return result.argv;
|
|
144
146
|
};
|
|
145
147
|
|
|
146
|
-
/**
|
|
147
|
-
* - Replaces comments with empty strings
|
|
148
|
-
* - Replaces escaped spaces (e.g., 'xxx\ yyy') with HTML space
|
|
149
|
-
* - Splits on whitespace, creating array of substrings
|
|
150
|
-
* - Filters empty string elements from array
|
|
151
|
-
* - Replaces any HTML space with space
|
|
152
|
-
* @summary Parses options read from run-control file.
|
|
153
|
-
* @private
|
|
154
|
-
* @param {string} content - Content read from run-control file.
|
|
155
|
-
* @returns {string[]} cmdline options (and associated arguments)
|
|
156
|
-
* @ignore
|
|
157
|
-
*/
|
|
158
|
-
const parseMochaOpts = content =>
|
|
159
|
-
content
|
|
160
|
-
.replace(/^#.*$/gm, '')
|
|
161
|
-
.replace(/\\\s/g, '%20')
|
|
162
|
-
.split(/\s/)
|
|
163
|
-
.filter(Boolean)
|
|
164
|
-
.map(value => value.replace(/%20/g, ' '));
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Given filepath in `args.opts`, attempt to load and parse a `mocha.opts` file.
|
|
168
|
-
* @param {Object} [args] - Arguments object
|
|
169
|
-
* @param {string|boolean} [args.opts] - Filepath to mocha.opts; defaults to whatever's in `mocharc.opts`, or `false` to skip
|
|
170
|
-
* @returns {external:yargsParser.Arguments|void} If read, object containing parsed arguments
|
|
171
|
-
* @memberof module:lib/cli/options
|
|
172
|
-
* @see {@link /#mochaopts|mocha.opts}
|
|
173
|
-
* @public
|
|
174
|
-
*/
|
|
175
|
-
const loadMochaOpts = (args = {}) => {
|
|
176
|
-
let result;
|
|
177
|
-
let filepath = args.opts;
|
|
178
|
-
// /dev/null is backwards compat
|
|
179
|
-
if (filepath === false || filepath === '/dev/null') {
|
|
180
|
-
return result;
|
|
181
|
-
}
|
|
182
|
-
filepath = filepath || mocharc.opts;
|
|
183
|
-
result = {};
|
|
184
|
-
let mochaOpts;
|
|
185
|
-
try {
|
|
186
|
-
mochaOpts = fs.readFileSync(filepath, 'utf8');
|
|
187
|
-
debug(`read ${filepath}`);
|
|
188
|
-
} catch (err) {
|
|
189
|
-
if (args.opts) {
|
|
190
|
-
throw new Error(`Unable to read ${filepath}: ${err}`);
|
|
191
|
-
}
|
|
192
|
-
// ignore otherwise. we tried
|
|
193
|
-
debug(`No mocha.opts found at ${filepath}`);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// real args should override `mocha.opts` which should override defaults.
|
|
197
|
-
// if there's an exception to catch here, I'm not sure what it is.
|
|
198
|
-
// by attaching the `no-opts` arg, we avoid re-parsing of `mocha.opts`.
|
|
199
|
-
if (mochaOpts) {
|
|
200
|
-
deprecate(
|
|
201
|
-
'Configuration via mocha.opts is DEPRECATED and will be removed from a future version of Mocha. Use RC files or package.json instead.'
|
|
202
|
-
);
|
|
203
|
-
result = parse(parseMochaOpts(mochaOpts));
|
|
204
|
-
debug(`${filepath} parsed succesfully`);
|
|
205
|
-
}
|
|
206
|
-
return result;
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
module.exports.loadMochaOpts = loadMochaOpts;
|
|
210
|
-
|
|
211
148
|
/**
|
|
212
149
|
* Given path to config file in `args.config`, attempt to load & parse config file.
|
|
213
150
|
* @param {Object} [args] - Arguments object
|
|
@@ -244,16 +181,16 @@ const loadPkgRc = (args = {}) => {
|
|
|
244
181
|
try {
|
|
245
182
|
const pkg = JSON.parse(fs.readFileSync(filepath, 'utf8'));
|
|
246
183
|
if (pkg.mocha) {
|
|
247
|
-
debug(`
|
|
184
|
+
debug('`mocha` prop of package.json parsed: %O', pkg.mocha);
|
|
248
185
|
result = pkg.mocha;
|
|
249
186
|
} else {
|
|
250
|
-
debug(
|
|
187
|
+
debug('no config found in %s', filepath);
|
|
251
188
|
}
|
|
252
189
|
} catch (err) {
|
|
253
190
|
if (args.package) {
|
|
254
191
|
throw new Error(`Unable to read/parse ${filepath}: ${err}`);
|
|
255
192
|
}
|
|
256
|
-
debug(
|
|
193
|
+
debug('failed to read default package.json at %s; ignoring', filepath);
|
|
257
194
|
}
|
|
258
195
|
}
|
|
259
196
|
return result;
|
|
@@ -267,11 +204,10 @@ module.exports.loadPkgRc = loadPkgRc;
|
|
|
267
204
|
* 1. Command-line args
|
|
268
205
|
* 2. RC file (`.mocharc.c?js`, `.mocharc.ya?ml`, `mocharc.json`)
|
|
269
206
|
* 3. `mocha` prop of `package.json`
|
|
270
|
-
* 4. `
|
|
271
|
-
* 5. default configuration (`lib/mocharc.json`)
|
|
207
|
+
* 4. default configuration (`lib/mocharc.json`)
|
|
272
208
|
*
|
|
273
209
|
* If a {@link module:lib/cli/one-and-dones.ONE_AND_DONE_ARGS "one-and-done" option} is present in the `argv` array, no external config files will be read.
|
|
274
|
-
* @summary Parses options read from
|
|
210
|
+
* @summary Parses options read from `.mocharc.*` and `package.json`.
|
|
275
211
|
* @param {string|string[]} [argv] - Arguments to parse
|
|
276
212
|
* @public
|
|
277
213
|
* @memberof module:lib/cli/options
|
|
@@ -279,7 +215,7 @@ module.exports.loadPkgRc = loadPkgRc;
|
|
|
279
215
|
*/
|
|
280
216
|
const loadOptions = (argv = []) => {
|
|
281
217
|
let args = parse(argv);
|
|
282
|
-
// short-circuit: look for a flag that would abort loading of
|
|
218
|
+
// short-circuit: look for a flag that would abort loading of options
|
|
283
219
|
if (
|
|
284
220
|
Array.from(ONE_AND_DONE_ARGS).reduce(
|
|
285
221
|
(acc, arg) => acc || arg in args,
|
|
@@ -291,7 +227,6 @@ const loadOptions = (argv = []) => {
|
|
|
291
227
|
|
|
292
228
|
const rcConfig = loadRc(args);
|
|
293
229
|
const pkgConfig = loadPkgRc(args);
|
|
294
|
-
const optsConfig = loadMochaOpts(args);
|
|
295
230
|
|
|
296
231
|
if (rcConfig) {
|
|
297
232
|
args.config = false;
|
|
@@ -301,19 +236,8 @@ const loadOptions = (argv = []) => {
|
|
|
301
236
|
args.package = false;
|
|
302
237
|
args._ = args._.concat(pkgConfig._ || []);
|
|
303
238
|
}
|
|
304
|
-
if (optsConfig) {
|
|
305
|
-
args.opts = false;
|
|
306
|
-
args._ = args._.concat(optsConfig._ || []);
|
|
307
|
-
}
|
|
308
239
|
|
|
309
|
-
args = parse(
|
|
310
|
-
args._,
|
|
311
|
-
mocharc,
|
|
312
|
-
args,
|
|
313
|
-
rcConfig || {},
|
|
314
|
-
pkgConfig || {},
|
|
315
|
-
optsConfig || {}
|
|
316
|
-
);
|
|
240
|
+
args = parse(args._, mocharc, args, rcConfig || {}, pkgConfig || {});
|
|
317
241
|
|
|
318
242
|
// recombine positional arguments and "spec"
|
|
319
243
|
if (args.spec) {
|
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'],
|