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.
Files changed (49) hide show
  1. package/CHANGELOG.md +143 -2
  2. package/bin/mocha +24 -4
  3. package/browser-entry.js +37 -9
  4. package/lib/browser/growl.js +2 -1
  5. package/lib/browser/highlight-tags.js +39 -0
  6. package/lib/browser/parse-query.js +24 -0
  7. package/lib/browser/progress.js +4 -0
  8. package/lib/browser/template.html +7 -5
  9. package/lib/cli/cli.js +6 -3
  10. package/lib/cli/collect-files.js +17 -10
  11. package/lib/cli/config.js +6 -6
  12. package/lib/cli/init.js +1 -2
  13. package/lib/cli/lookup-files.js +145 -0
  14. package/lib/cli/node-flags.js +2 -2
  15. package/lib/cli/options.js +14 -90
  16. package/lib/cli/run-helpers.js +133 -36
  17. package/lib/cli/run-option-metadata.js +4 -2
  18. package/lib/cli/run.js +71 -11
  19. package/lib/cli/watch-run.js +211 -51
  20. package/lib/context.js +0 -15
  21. package/lib/errors.js +202 -9
  22. package/lib/esm-utils.js +11 -6
  23. package/lib/hook.js +32 -0
  24. package/lib/interfaces/common.js +21 -10
  25. package/lib/mocha.js +301 -126
  26. package/lib/mocharc.json +0 -1
  27. package/lib/nodejs/buffered-worker-pool.js +174 -0
  28. package/lib/{growl.js → nodejs/growl.js} +3 -2
  29. package/lib/nodejs/parallel-buffered-runner.js +295 -0
  30. package/lib/nodejs/reporters/parallel-buffered.js +133 -0
  31. package/lib/nodejs/serializer.js +404 -0
  32. package/lib/nodejs/worker.js +154 -0
  33. package/lib/pending.js +4 -0
  34. package/lib/reporters/base.js +25 -12
  35. package/lib/reporters/doc.js +6 -0
  36. package/lib/reporters/json-stream.js +1 -0
  37. package/lib/reporters/json.js +1 -0
  38. package/lib/reporters/landing.js +11 -3
  39. package/lib/reporters/tap.js +1 -2
  40. package/lib/reporters/xunit.js +3 -2
  41. package/lib/runnable.js +39 -47
  42. package/lib/runner.js +219 -118
  43. package/lib/suite.js +61 -27
  44. package/lib/test.js +48 -3
  45. package/lib/utils.js +33 -209
  46. package/mocha.js +25522 -17715
  47. package/mocha.js.map +1 -0
  48. package/package.json +87 -68
  49. package/lib/browser/tty.js +0 -13
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
- // load requires first, because it can impact "plugin" validation
289
- handleRequires(argv.require);
290
- validatePlugin(argv, 'reporter', Mocha.reporters);
291
- validatePlugin(argv, 'ui', Mocha.interfaces);
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)
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const debug = require('debug')('mocha:cli:watch');
3
4
  const path = require('path');
4
5
  const chokidar = require('chokidar');
5
6
  const Context = require('../context');
@@ -12,37 +13,164 @@ const collectFiles = require('./collect-files');
12
13
  * @private
13
14
  */
14
15
 
16
+ /**
17
+ * Run Mocha in parallel "watch" mode
18
+ * @param {Mocha} mocha - Mocha instance
19
+ * @param {Object} opts - Options
20
+ * @param {string[]} [opts.watchFiles] - List of paths and patterns to
21
+ * watch. If not provided all files with an extension included in
22
+ * `fileCollectionParams.extension` are watched. See first argument of
23
+ * `chokidar.watch`.
24
+ * @param {string[]} opts.watchIgnore - List of paths and patterns to
25
+ * exclude from watching. See `ignored` option of `chokidar`.
26
+ * @param {FileCollectionOptions} fileCollectParams - Parameters that control test
27
+ * @private
28
+ */
29
+ exports.watchParallelRun = (
30
+ mocha,
31
+ {watchFiles, watchIgnore},
32
+ fileCollectParams
33
+ ) => {
34
+ debug('creating parallel watcher');
35
+ return createWatcher(mocha, {
36
+ watchFiles,
37
+ watchIgnore,
38
+ beforeRun({mocha}) {
39
+ // I don't know why we're cloning the root suite.
40
+ const rootSuite = mocha.suite.clone();
41
+
42
+ // this `require` is needed because the require cache has been cleared. the dynamic
43
+ // exports set via the below call to `mocha.ui()` won't work properly if a
44
+ // test depends on this module (see `required-tokens.spec.js`).
45
+ const Mocha = require('../mocha');
46
+
47
+ // ... and now that we've gotten a new module, we need to use it again due
48
+ // to `mocha.ui()` call
49
+ const newMocha = new Mocha(mocha.options);
50
+ // don't know why this is needed
51
+ newMocha.suite = rootSuite;
52
+ // nor this
53
+ newMocha.suite.ctx = new Context();
54
+
55
+ // reset the list of files
56
+ newMocha.files = collectFiles(fileCollectParams);
57
+
58
+ // because we've swapped out the root suite (see the `run` inner function
59
+ // in `createRerunner`), we need to call `mocha.ui()` again to set up the context/globals.
60
+ newMocha.ui(newMocha.options.ui);
61
+
62
+ // we need to call `newMocha.rootHooks` to set up rootHooks for the new
63
+ // suite
64
+ newMocha.rootHooks(newMocha.options.rootHooks);
65
+
66
+ // in parallel mode, the main Mocha process doesn't actually load the
67
+ // files. this flag prevents `mocha.run()` from autoloading.
68
+ newMocha.lazyLoadFiles(true);
69
+ return newMocha;
70
+ },
71
+ afterRun({watcher}) {
72
+ blastCache(watcher);
73
+ },
74
+ fileCollectParams
75
+ });
76
+ };
77
+
15
78
  /**
16
79
  * Run Mocha in "watch" mode
17
80
  * @param {Mocha} mocha - Mocha instance
18
81
  * @param {Object} opts - Options
19
82
  * @param {string[]} [opts.watchFiles] - List of paths and patterns to
20
83
  * watch. If not provided all files with an extension included in
21
- * `fileColletionParams.extension` are watched. See first argument of
84
+ * `fileCollectionParams.extension` are watched. See first argument of
22
85
  * `chokidar.watch`.
23
86
  * @param {string[]} opts.watchIgnore - List of paths and patterns to
24
87
  * exclude from watching. See `ignored` option of `chokidar`.
25
- * @param {Object} fileCollectParams - Parameters that control test
88
+ * @param {FileCollectionOptions} fileCollectParams - Parameters that control test
26
89
  * file collection. See `lib/cli/collect-files.js`.
27
- * @param {string[]} fileCollectParams.extension - List of extensions
28
- * to watch if `opts.watchFiles` is not given.
29
90
  * @private
30
91
  */
31
- module.exports = (mocha, {watchFiles, watchIgnore}, fileCollectParams) => {
92
+ exports.watchRun = (mocha, {watchFiles, watchIgnore}, fileCollectParams) => {
93
+ debug('creating serial watcher');
94
+ // list of all test files
95
+
96
+ return createWatcher(mocha, {
97
+ watchFiles,
98
+ watchIgnore,
99
+ beforeRun({mocha}) {
100
+ mocha.unloadFiles();
101
+
102
+ // I don't know why we're cloning the root suite.
103
+ const rootSuite = mocha.suite.clone();
104
+
105
+ // this `require` is needed because the require cache has been cleared. the dynamic
106
+ // exports set via the below call to `mocha.ui()` won't work properly if a
107
+ // test depends on this module (see `required-tokens.spec.js`).
108
+ const Mocha = require('../mocha');
109
+
110
+ // ... and now that we've gotten a new module, we need to use it again due
111
+ // to `mocha.ui()` call
112
+ const newMocha = new Mocha(mocha.options);
113
+ // don't know why this is needed
114
+ newMocha.suite = rootSuite;
115
+ // nor this
116
+ newMocha.suite.ctx = new Context();
117
+
118
+ // reset the list of files
119
+ newMocha.files = collectFiles(fileCollectParams);
120
+
121
+ // because we've swapped out the root suite (see the `run` inner function
122
+ // in `createRerunner`), we need to call `mocha.ui()` again to set up the context/globals.
123
+ newMocha.ui(newMocha.options.ui);
124
+
125
+ // we need to call `newMocha.rootHooks` to set up rootHooks for the new
126
+ // suite
127
+ newMocha.rootHooks(newMocha.options.rootHooks);
128
+
129
+ return newMocha;
130
+ },
131
+ afterRun({watcher}) {
132
+ blastCache(watcher);
133
+ },
134
+ fileCollectParams
135
+ });
136
+ };
137
+
138
+ /**
139
+ * Bootstraps a chokidar watcher. Handles keyboard input & signals
140
+ * @param {Mocha} mocha - Mocha instance
141
+ * @param {Object} opts
142
+ * @param {BeforeWatchRun} [opts.beforeRun] - Function to call before
143
+ * `mocha.run()`
144
+ * @param {AfterWatchRun} [opts.afterRun] - Function to call after `mocha.run()`
145
+ * @param {string[]} [opts.watchFiles] - List of paths and patterns to watch. If
146
+ * not provided all files with an extension included in
147
+ * `fileCollectionParams.extension` are watched. See first argument of
148
+ * `chokidar.watch`.
149
+ * @param {string[]} [opts.watchIgnore] - List of paths and patterns to exclude
150
+ * from watching. See `ignored` option of `chokidar`.
151
+ * @param {FileCollectionOptions} opts.fileCollectParams - List of extensions to watch if `opts.watchFiles` is not given.
152
+ * @returns {FSWatcher}
153
+ * @ignore
154
+ * @private
155
+ */
156
+ const createWatcher = (
157
+ mocha,
158
+ {watchFiles, watchIgnore, beforeRun, afterRun, fileCollectParams}
159
+ ) => {
32
160
  if (!watchFiles) {
33
161
  watchFiles = fileCollectParams.extension.map(ext => `**/*.${ext}`);
34
162
  }
35
163
 
164
+ debug('ignoring files matching: %s', watchIgnore);
165
+
36
166
  const watcher = chokidar.watch(watchFiles, {
37
167
  ignored: watchIgnore,
38
168
  ignoreInitial: true
39
169
  });
40
170
 
41
- const rerunner = createRerunner(mocha, () => {
42
- getWatchedFiles(watcher).forEach(file => {
43
- delete require.cache[file];
44
- });
45
- mocha.files = collectFiles(fileCollectParams);
171
+ const rerunner = createRerunner(mocha, watcher, {
172
+ beforeRun,
173
+ afterRun
46
174
  });
47
175
 
48
176
  watcher.on('ready', () => {
@@ -53,7 +181,6 @@ module.exports = (mocha, {watchFiles, watchIgnore}, fileCollectParams) => {
53
181
  rerunner.scheduleRun();
54
182
  });
55
183
 
56
- console.log();
57
184
  hideCursor();
58
185
  process.on('exit', () => {
59
186
  showCursor();
@@ -74,36 +201,43 @@ module.exports = (mocha, {watchFiles, watchIgnore}, fileCollectParams) => {
74
201
  .toLowerCase();
75
202
  if (str === 'rs') rerunner.scheduleRun();
76
203
  });
204
+
205
+ return watcher;
77
206
  };
78
207
 
79
208
  /**
80
- * Create an object that allows you to rerun tests on the mocha
81
- * instance. `beforeRun` is called everytime before `mocha.run()` is
82
- * called.
209
+ * Create an object that allows you to rerun tests on the mocha instance.
83
210
  *
84
211
  * @param {Mocha} mocha - Mocha instance
85
- * @param {function} beforeRun - Called just before `mocha.run()`
212
+ * @param {FSWatcher} watcher - chokidar `FSWatcher` instance
213
+ * @param {Object} [opts] - Options!
214
+ * @param {BeforeWatchRun} [opts.beforeRun] - Function to call before `mocha.run()`
215
+ * @param {AfterWatchRun} [opts.afterRun] - Function to call after `mocha.run()`
216
+ * @returns {Rerunner}
217
+ * @ignore
218
+ * @private
86
219
  */
87
- const createRerunner = (mocha, beforeRun) => {
220
+ const createRerunner = (mocha, watcher, {beforeRun, afterRun} = {}) => {
88
221
  // Set to a `Runner` when mocha is running. Set to `null` when mocha is not
89
222
  // running.
90
223
  let runner = null;
91
224
 
225
+ // true if a file has changed during a test run
92
226
  let rerunScheduled = false;
93
227
 
94
228
  const run = () => {
95
- try {
96
- beforeRun();
97
- resetMocha(mocha);
98
- runner = mocha.run(() => {
99
- runner = null;
100
- if (rerunScheduled) {
101
- rerun();
102
- }
103
- });
104
- } catch (e) {
105
- console.log(e.stack);
106
- }
229
+ mocha = beforeRun ? beforeRun({mocha, watcher}) : mocha;
230
+
231
+ runner = mocha.run(() => {
232
+ debug('finished watch run');
233
+ runner = null;
234
+ afterRun && afterRun({mocha, watcher});
235
+ if (rerunScheduled) {
236
+ rerun();
237
+ } else {
238
+ debug('waiting for changes...');
239
+ }
240
+ });
107
241
  };
108
242
 
109
243
  const scheduleRun = () => {
@@ -136,32 +270,18 @@ const createRerunner = (mocha, beforeRun) => {
136
270
  *
137
271
  * @param watcher - Instance of a chokidar watcher
138
272
  * @return {string[]} - List of absolute paths
273
+ * @ignore
274
+ * @private
139
275
  */
140
276
  const getWatchedFiles = watcher => {
141
277
  const watchedDirs = watcher.getWatched();
142
- let watchedFiles = [];
143
- Object.keys(watchedDirs).forEach(dir => {
144
- watchedFiles = watchedFiles.concat(
145
- watchedDirs[dir].map(file => path.join(dir, file))
146
- );
147
- });
148
- return watchedFiles;
149
- };
150
-
151
- /**
152
- * Reset the internal state of the mocha instance so that tests can be rerun.
153
- *
154
- * @param {Mocha} mocha - Mocha instance
155
- * @private
156
- */
157
- const resetMocha = mocha => {
158
- mocha.unloadFiles();
159
- mocha.suite = mocha.suite.clone();
160
- mocha.suite.ctx = new Context();
161
- // Registers a callback on `mocha.suite` that wires new context to the DSL
162
- // (e.g. `describe`) that is exposed as globals when the test files are
163
- // reloaded.
164
- mocha.ui(mocha.options.ui);
278
+ return Object.keys(watchedDirs).reduce(
279
+ (acc, dir) => [
280
+ ...acc,
281
+ ...watchedDirs[dir].map(file => path.join(dir, file))
282
+ ],
283
+ []
284
+ );
165
285
  };
166
286
 
167
287
  /**
@@ -189,3 +309,43 @@ const showCursor = () => {
189
309
  const eraseLine = () => {
190
310
  process.stdout.write('\u001b[2K');
191
311
  };
312
+
313
+ /**
314
+ * Blast all of the watched files out of `require.cache`
315
+ * @param {FSWatcher} watcher - chokidar FSWatcher
316
+ * @ignore
317
+ * @private
318
+ */
319
+ const blastCache = watcher => {
320
+ const files = getWatchedFiles(watcher);
321
+ files.forEach(file => {
322
+ delete require.cache[file];
323
+ });
324
+ debug('deleted %d file(s) from the require cache', files.length);
325
+ };
326
+
327
+ /**
328
+ * Callback to be run before `mocha.run()` is called.
329
+ * Optionally, it can return a new `Mocha` instance.
330
+ * @callback BeforeWatchRun
331
+ * @private
332
+ * @param {{mocha: Mocha, watcher: FSWatcher}} options
333
+ * @returns {Mocha}
334
+ */
335
+
336
+ /**
337
+ * Callback to be run after `mocha.run()` completes. Typically used to clear
338
+ * require cache.
339
+ * @callback AfterWatchRun
340
+ * @private
341
+ * @param {{mocha: Mocha, watcher: FSWatcher}} options
342
+ * @returns {void}
343
+ */
344
+
345
+ /**
346
+ * Object containing run control methods
347
+ * @typedef {Object} Rerunner
348
+ * @private
349
+ * @property {Function} run - Calls `mocha.run()`
350
+ * @property {Function} scheduleRun - Schedules another call to `run`
351
+ */
package/lib/context.js CHANGED
@@ -45,21 +45,6 @@ Context.prototype.timeout = function(ms) {
45
45
  return this;
46
46
  };
47
47
 
48
- /**
49
- * Set test timeout `enabled`.
50
- *
51
- * @private
52
- * @param {boolean} enabled
53
- * @return {Context} self
54
- */
55
- Context.prototype.enableTimeouts = function(enabled) {
56
- if (!arguments.length) {
57
- return this.runnable().enableTimeouts();
58
- }
59
- this.runnable().enableTimeouts(enabled);
60
- return this;
61
- };
62
-
63
48
  /**
64
49
  * Set or get test slowness threshold `ms`.
65
50
  *