mocha 7.2.0 → 8.1.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +116 -0
  2. package/bin/mocha +17 -2
  3. package/browser-entry.js +26 -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 +2 -2
  10. package/lib/cli/collect-files.js +15 -9
  11. package/lib/cli/config.js +0 -1
  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 +11 -87
  16. package/lib/cli/run-helpers.js +54 -16
  17. package/lib/cli/run-option-metadata.js +4 -2
  18. package/lib/cli/run.js +61 -14
  19. package/lib/cli/watch-run.js +211 -51
  20. package/lib/context.js +0 -15
  21. package/lib/errors.js +26 -3
  22. package/lib/esm-utils.js +11 -6
  23. package/lib/hook.js +24 -0
  24. package/lib/interfaces/common.js +19 -11
  25. package/lib/mocha.js +137 -121
  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/landing.js +3 -3
  36. package/lib/reporters/tap.js +1 -2
  37. package/lib/reporters/xunit.js +3 -2
  38. package/lib/runnable.js +18 -30
  39. package/lib/runner.js +58 -64
  40. package/lib/suite.js +32 -24
  41. package/lib/test.js +28 -1
  42. package/lib/utils.js +19 -206
  43. package/mocha.js +25522 -18248
  44. package/mocha.js.map +1 -0
  45. package/package.json +52 -42
  46. package/lib/browser/tty.js +0 -13
package/lib/mocha.js CHANGED
@@ -9,12 +9,14 @@
9
9
  var escapeRe = require('escape-string-regexp');
10
10
  var path = require('path');
11
11
  var builtinReporters = require('./reporters');
12
- var growl = require('./growl');
12
+ var growl = require('./nodejs/growl');
13
13
  var utils = require('./utils');
14
14
  var mocharc = require('./mocharc.json');
15
15
  var errors = require('./errors');
16
16
  var Suite = require('./suite');
17
- var esmUtils = utils.supportsEsModules() ? require('./esm-utils') : undefined;
17
+ var esmUtils = utils.supportsEsModules(true)
18
+ ? require('./esm-utils')
19
+ : undefined;
18
20
  var createStatsCollector = require('./stats-collector');
19
21
  var createInvalidReporterError = errors.createInvalidReporterError;
20
22
  var createInvalidInterfaceError = errors.createInvalidInterfaceError;
@@ -26,29 +28,35 @@ var EVENT_FILE_PRE_REQUIRE = Suite.constants.EVENT_FILE_PRE_REQUIRE;
26
28
  var EVENT_FILE_POST_REQUIRE = Suite.constants.EVENT_FILE_POST_REQUIRE;
27
29
  var EVENT_FILE_REQUIRE = Suite.constants.EVENT_FILE_REQUIRE;
28
30
  var sQuote = utils.sQuote;
31
+ var debug = require('debug')('mocha:mocha');
29
32
 
30
33
  exports = module.exports = Mocha;
31
34
 
32
35
  /**
33
36
  * A Mocha instance is a finite state machine.
34
37
  * These are the states it can be in.
38
+ * @private
35
39
  */
36
40
  var mochaStates = utils.defineConstants({
37
41
  /**
38
42
  * Initial state of the mocha instance
43
+ * @private
39
44
  */
40
45
  INIT: 'init',
41
46
  /**
42
47
  * Mocha instance is running tests
48
+ * @private
43
49
  */
44
50
  RUNNING: 'running',
45
51
  /**
46
52
  * Mocha instance is done running tests and references to test functions and hooks are cleaned.
47
53
  * You can reset this state by unloading the test files.
54
+ * @private
48
55
  */
49
56
  REFERENCES_CLEANED: 'referencesCleaned',
50
57
  /**
51
58
  * Mocha instance is disposed and can no longer be used.
59
+ * @private
52
60
  */
53
61
  DISPOSED: 'disposed'
54
62
  });
@@ -57,20 +65,16 @@ var mochaStates = utils.defineConstants({
57
65
  * To require local UIs and reporters when running in node.
58
66
  */
59
67
 
60
- if (!process.browser && typeof module.paths !== 'undefined') {
68
+ if (!utils.isBrowser() && typeof module.paths !== 'undefined') {
61
69
  var cwd = utils.cwd();
62
70
  module.paths.push(cwd, path.join(cwd, 'node_modules'));
63
71
  }
64
72
 
65
73
  /**
66
74
  * Expose internals.
75
+ * @private
67
76
  */
68
77
 
69
- /**
70
- * @public
71
- * @class utils
72
- * @memberof Mocha
73
- */
74
78
  exports.utils = utils;
75
79
  exports.interfaces = require('./interfaces');
76
80
  /**
@@ -118,8 +122,11 @@ exports.Test = require('./test');
118
122
  * @param {number} [options.slow] - Slow threshold value.
119
123
  * @param {number|string} [options.timeout] - Timeout threshold value.
120
124
  * @param {string} [options.ui] - Interface name.
125
+ * @param {boolean} [options.parallel] - Run jobs in parallel
126
+ * @param {number} [options.jobs] - Max number of worker processes for parallel runs
121
127
  * @param {MochaRootHookObject} [options.rootHooks] - Hooks to bootstrap the root
122
128
  * suite with
129
+ * @param {boolean} [options.isWorker] - Should be `true` if `Mocha` process is running in a worker process.
123
130
  */
124
131
  function Mocha(options) {
125
132
  options = utils.assign({}, mocharc, options || {});
@@ -128,6 +135,7 @@ function Mocha(options) {
128
135
  // root suite
129
136
  this.suite = new exports.Suite('', new exports.Context(), true);
130
137
  this._cleanReferencesAfterRun = true;
138
+ this._state = mochaStates.INIT;
131
139
 
132
140
  this.grep(options.grep)
133
141
  .fgrep(options.fgrep)
@@ -171,6 +179,39 @@ function Mocha(options) {
171
179
  if (options.rootHooks) {
172
180
  this.rootHooks(options.rootHooks);
173
181
  }
182
+
183
+ /**
184
+ * The class which we'll instantiate in {@link Mocha#run}. Defaults to
185
+ * {@link Runner} in serial mode; changes in parallel mode.
186
+ * @memberof Mocha
187
+ * @private
188
+ */
189
+ this._runnerClass = exports.Runner;
190
+
191
+ /**
192
+ * Whether or not to call {@link Mocha#loadFiles} implicitly when calling
193
+ * {@link Mocha#run}. If this is `true`, then it's up to the consumer to call
194
+ * {@link Mocha#loadFiles} _or_ {@link Mocha#loadFilesAsync}.
195
+ * @private
196
+ * @memberof Mocha
197
+ */
198
+ this._lazyLoadFiles = false;
199
+
200
+ /**
201
+ * It's useful for a Mocha instance to know if it's running in a worker process.
202
+ * We could derive this via other means, but it's helpful to have a flag to refer to.
203
+ * @memberof Mocha
204
+ * @private
205
+ */
206
+ this.isWorker = Boolean(options.isWorker);
207
+
208
+ if (
209
+ options.parallel &&
210
+ (typeof options.jobs === 'undefined' || options.jobs > 1)
211
+ ) {
212
+ debug('attempting to enable parallel mode');
213
+ this.parallelMode(true);
214
+ }
174
215
  }
175
216
 
176
217
  /**
@@ -211,7 +252,7 @@ Mocha.prototype.addFile = function(file) {
211
252
  * @public
212
253
  * @see [CLI option](../#-reporter-name-r-name)
213
254
  * @see [Reporters](../#reporters)
214
- * @param {String|Function} reporter - Reporter name or constructor.
255
+ * @param {String|Function} reporterName - Reporter name or constructor.
215
256
  * @param {Object} [reporterOptions] - Options used to configure the reporter.
216
257
  * @returns {Mocha} this
217
258
  * @chainable
@@ -221,20 +262,20 @@ Mocha.prototype.addFile = function(file) {
221
262
  * // Use XUnit reporter and direct its output to file
222
263
  * mocha.reporter('xunit', { output: '/path/to/testspec.xunit.xml' });
223
264
  */
224
- Mocha.prototype.reporter = function(reporter, reporterOptions) {
225
- if (typeof reporter === 'function') {
226
- this._reporter = reporter;
265
+ Mocha.prototype.reporter = function(reporterName, reporterOptions) {
266
+ if (typeof reporterName === 'function') {
267
+ this._reporter = reporterName;
227
268
  } else {
228
- reporter = reporter || 'spec';
229
- var _reporter;
269
+ reporterName = reporterName || 'spec';
270
+ var reporter;
230
271
  // Try to load a built-in reporter.
231
- if (builtinReporters[reporter]) {
232
- _reporter = builtinReporters[reporter];
272
+ if (builtinReporters[reporterName]) {
273
+ reporter = builtinReporters[reporterName];
233
274
  }
234
275
  // Try to load reporters from process.cwd() and node_modules
235
- if (!_reporter) {
276
+ if (!reporter) {
236
277
  try {
237
- _reporter = require(reporter);
278
+ reporter = require(reporterName);
238
279
  } catch (err) {
239
280
  if (
240
281
  err.code === 'MODULE_NOT_FOUND' ||
@@ -242,31 +283,31 @@ Mocha.prototype.reporter = function(reporter, reporterOptions) {
242
283
  ) {
243
284
  // Try to load reporters from a path (absolute or relative)
244
285
  try {
245
- _reporter = require(path.resolve(utils.cwd(), reporter));
286
+ reporter = require(path.resolve(utils.cwd(), reporterName));
246
287
  } catch (_err) {
247
288
  _err.code === 'MODULE_NOT_FOUND' ||
248
289
  _err.message.indexOf('Cannot find module') >= 0
249
- ? utils.warn(sQuote(reporter) + ' reporter not found')
290
+ ? utils.warn(sQuote(reporterName) + ' reporter not found')
250
291
  : utils.warn(
251
- sQuote(reporter) +
292
+ sQuote(reporterName) +
252
293
  ' reporter blew up with error:\n' +
253
294
  err.stack
254
295
  );
255
296
  }
256
297
  } else {
257
298
  utils.warn(
258
- sQuote(reporter) + ' reporter blew up with error:\n' + err.stack
299
+ sQuote(reporterName) + ' reporter blew up with error:\n' + err.stack
259
300
  );
260
301
  }
261
302
  }
262
303
  }
263
- if (!_reporter) {
304
+ if (!reporter) {
264
305
  throw createInvalidReporterError(
265
- 'invalid reporter ' + sQuote(reporter),
266
- reporter
306
+ 'invalid reporter ' + sQuote(reporterName),
307
+ reporterName
267
308
  );
268
309
  }
269
- this._reporter = _reporter;
310
+ this._reporter = reporter;
270
311
  }
271
312
  this.options.reporterOption = reporterOptions;
272
313
  // alias option name is used in public reporters xunit/tap/progress
@@ -375,7 +416,7 @@ Mocha.prototype.loadFiles = function(fn) {
375
416
  Mocha.prototype.loadFilesAsync = function() {
376
417
  var self = this;
377
418
  var suite = this.suite;
378
- this.loadAsync = true;
419
+ this.lazyLoadFiles(true);
379
420
 
380
421
  if (!esmUtils) {
381
422
  return new Promise(function(resolve) {
@@ -520,24 +561,6 @@ Mocha.prototype.invert = function() {
520
561
  return this;
521
562
  };
522
563
 
523
- /**
524
- * Enables or disables ignoring global leaks.
525
- *
526
- * @deprecated since v7.0.0
527
- * @public
528
- * @see {@link Mocha#checkLeaks}
529
- * @param {boolean} [ignoreLeaks=false] - Whether to ignore global leaks.
530
- * @return {Mocha} this
531
- * @chainable
532
- */
533
- Mocha.prototype.ignoreLeaks = function(ignoreLeaks) {
534
- utils.deprecate(
535
- '"ignoreLeaks()" is DEPRECATED, please use "checkLeaks()" instead.'
536
- );
537
- this.options.checkLeaks = !ignoreLeaks;
538
- return this;
539
- };
540
-
541
564
  /**
542
565
  * Enables or disables checking for global variables leaked while running tests.
543
566
  *
@@ -609,7 +632,7 @@ Mocha.prototype.fullTrace = function(fullTrace) {
609
632
  Mocha.prototype.growl = function() {
610
633
  this.options.growl = this.isGrowlCapable();
611
634
  if (!this.options.growl) {
612
- var detail = process.browser
635
+ var detail = utils.isBrowser()
613
636
  ? 'notification support not available in this browser...'
614
637
  : 'notification support prerequisites not installed...';
615
638
  console.error(detail + ' cannot enable!');
@@ -667,24 +690,6 @@ Mocha.prototype.global = function(global) {
667
690
  // for backwards compability, 'globals' is an alias of 'global'
668
691
  Mocha.prototype.globals = Mocha.prototype.global;
669
692
 
670
- /**
671
- * Enables or disables TTY color output by screen-oriented reporters.
672
- *
673
- * @deprecated since v7.0.0
674
- * @public
675
- * @see {@link Mocha#color}
676
- * @param {boolean} colors - Whether to enable color output.
677
- * @return {Mocha} this
678
- * @chainable
679
- */
680
- Mocha.prototype.useColors = function(colors) {
681
- utils.deprecate('"useColors()" is DEPRECATED, please use "color()" instead.');
682
- if (colors !== undefined) {
683
- this.options.color = colors;
684
- }
685
- return this;
686
- };
687
-
688
693
  /**
689
694
  * Enables or disables TTY color output by screen-oriented reporters.
690
695
  *
@@ -699,25 +704,6 @@ Mocha.prototype.color = function(color) {
699
704
  return this;
700
705
  };
701
706
 
702
- /**
703
- * Determines if reporter should use inline diffs (rather than +/-)
704
- * in test failure output.
705
- *
706
- * @deprecated since v7.0.0
707
- * @public
708
- * @see {@link Mocha#inlineDiffs}
709
- * @param {boolean} [inlineDiffs=false] - Whether to use inline diffs.
710
- * @return {Mocha} this
711
- * @chainable
712
- */
713
- Mocha.prototype.useInlineDiffs = function(inlineDiffs) {
714
- utils.deprecate(
715
- '"useInlineDiffs()" is DEPRECATED, please use "inlineDiffs()" instead.'
716
- );
717
- this.options.inlineDiffs = inlineDiffs !== undefined && inlineDiffs;
718
- return this;
719
- };
720
-
721
707
  /**
722
708
  * Enables or disables reporter to use inline diffs (rather than +/-)
723
709
  * in test failure output.
@@ -733,22 +719,6 @@ Mocha.prototype.inlineDiffs = function(inlineDiffs) {
733
719
  return this;
734
720
  };
735
721
 
736
- /**
737
- * Determines if reporter should include diffs in test failure output.
738
- *
739
- * @deprecated since v7.0.0
740
- * @public
741
- * @see {@link Mocha#diff}
742
- * @param {boolean} [hideDiff=false] - Whether to hide diffs.
743
- * @return {Mocha} this
744
- * @chainable
745
- */
746
- Mocha.prototype.hideDiff = function(hideDiff) {
747
- utils.deprecate('"hideDiff()" is DEPRECATED, please use "diff()" instead.');
748
- this.options.diff = !(hideDiff === true);
749
- return this;
750
- };
751
-
752
722
  /**
753
723
  * Enables or disables reporter to include diff in test failure output.
754
724
  *
@@ -774,7 +744,6 @@ Mocha.prototype.diff = function(diff) {
774
744
  * @public
775
745
  * @see [CLI option](../#-timeout-ms-t-ms)
776
746
  * @see [Timeouts](../#timeouts)
777
- * @see {@link Mocha#enableTimeouts}
778
747
  * @param {number|string} msecs - Timeout threshold value.
779
748
  * @return {Mocha} this
780
749
  * @chainable
@@ -806,8 +775,8 @@ Mocha.prototype.timeout = function(msecs) {
806
775
  * // Allow any failed test to retry one more time
807
776
  * mocha.retries(1);
808
777
  */
809
- Mocha.prototype.retries = function(n) {
810
- this.suite.retries(n);
778
+ Mocha.prototype.retries = function(retry) {
779
+ this.suite.retries(retry);
811
780
  return this;
812
781
  };
813
782
 
@@ -833,22 +802,6 @@ Mocha.prototype.slow = function(msecs) {
833
802
  return this;
834
803
  };
835
804
 
836
- /**
837
- * Enables or disables timeouts.
838
- *
839
- * @public
840
- * @see [CLI option](../#-timeout-ms-t-ms)
841
- * @param {boolean} enableTimeouts - Whether to enable timeouts.
842
- * @return {Mocha} this
843
- * @chainable
844
- */
845
- Mocha.prototype.enableTimeouts = function(enableTimeouts) {
846
- this.suite.enableTimeouts(
847
- arguments.length && enableTimeouts !== undefined ? enableTimeouts : true
848
- );
849
- return this;
850
- };
851
-
852
805
  /**
853
806
  * Forces all tests to either accept a `done` callback or return a promise.
854
807
  *
@@ -936,6 +889,7 @@ Mocha.prototype.forbidPending = function(forbidPending) {
936
889
 
937
890
  /**
938
891
  * Throws an error if mocha is in the wrong state to be able to transition to a "running" state.
892
+ * @private
939
893
  */
940
894
  Mocha.prototype._guardRunningStateTransition = function() {
941
895
  if (this._state === mochaStates.RUNNING) {
@@ -973,6 +927,7 @@ Object.defineProperty(Mocha.prototype, 'version', {
973
927
  /**
974
928
  * Callback to be invoked when test execution is complete.
975
929
  *
930
+ * @private
976
931
  * @callback DoneCB
977
932
  * @param {number} failures - Number of failures that occurred.
978
933
  */
@@ -1002,14 +957,14 @@ Mocha.prototype.run = function(fn) {
1002
957
  this._previousRunner.dispose();
1003
958
  this.suite.reset();
1004
959
  }
1005
- if (this.files.length && !this.loadAsync) {
960
+ if (this.files.length && !this._lazyLoadFiles) {
1006
961
  this.loadFiles();
1007
962
  }
1008
963
  var self = this;
1009
964
  var suite = this.suite;
1010
965
  var options = this.options;
1011
966
  options.files = this.files;
1012
- var runner = new exports.Runner(suite, {
967
+ var runner = new this._runnerClass(suite, {
1013
968
  delay: options.delay,
1014
969
  cleanReferencesAfterRun: this._cleanReferencesAfterRun
1015
970
  });
@@ -1051,7 +1006,7 @@ Mocha.prototype.run = function(fn) {
1051
1006
  }
1052
1007
  }
1053
1008
 
1054
- return runner.run(done);
1009
+ return runner.run(done, {files: this.files, options: options});
1055
1010
  };
1056
1011
 
1057
1012
  /**
@@ -1082,8 +1037,68 @@ Mocha.prototype.rootHooks = function rootHooks(hooks) {
1082
1037
  return this;
1083
1038
  };
1084
1039
 
1040
+ /**
1041
+ * Toggles parallel mode.
1042
+ *
1043
+ * Must be run before calling {@link Mocha#run}. Changes the `Runner` class to
1044
+ * use; also enables lazy file loading if not already done so.
1045
+ * @param {boolean} [enable] - If `true`, enable; otherwise disable.
1046
+ * @throws If run in browser
1047
+ * @throws If Mocha not in "INIT" state
1048
+ * @returns {Mocha}
1049
+ * @chainable
1050
+ * @public
1051
+ */
1052
+ Mocha.prototype.parallelMode = function parallelMode(enable) {
1053
+ if (utils.isBrowser()) {
1054
+ throw errors.createUnsupportedError(
1055
+ 'parallel mode is only supported in Node.js'
1056
+ );
1057
+ }
1058
+ var parallel = enable === true;
1059
+ if (
1060
+ parallel === this.options.parallel &&
1061
+ this._lazyLoadFiles &&
1062
+ this._runnerClass !== exports.Runner
1063
+ ) {
1064
+ return this;
1065
+ }
1066
+ if (this._state !== mochaStates.INIT) {
1067
+ throw errors.createUnsupportedError(
1068
+ 'cannot change parallel mode after having called run()'
1069
+ );
1070
+ }
1071
+ this.options.parallel = parallel;
1072
+
1073
+ // swap Runner class
1074
+ this._runnerClass = parallel
1075
+ ? require('./nodejs/parallel-buffered-runner')
1076
+ : exports.Runner;
1077
+
1078
+ // lazyLoadFiles may have been set `true` otherwise (for ESM loading),
1079
+ // so keep `true` if so.
1080
+ return this.lazyLoadFiles(this._lazyLoadFiles || parallel);
1081
+ };
1082
+
1083
+ /**
1084
+ * Disables implicit call to {@link Mocha#loadFiles} in {@link Mocha#run}. This
1085
+ * setting is used by watch mode, parallel mode, and for loading ESM files.
1086
+ * @todo This should throw if we've already loaded files; such behavior
1087
+ * necessitates adding a new state.
1088
+ * @param {boolean} [enable] - If `true`, disable eager loading of files in
1089
+ * {@link Mocha#run}
1090
+ * @chainable
1091
+ * @public
1092
+ */
1093
+ Mocha.prototype.lazyLoadFiles = function lazyLoadFiles(enable) {
1094
+ this._lazyLoadFiles = enable === true;
1095
+ debug('set lazy load to %s', enable);
1096
+ return this;
1097
+ };
1098
+
1085
1099
  /**
1086
1100
  * An alternative way to define root hooks that works with parallel runs.
1101
+ * @private
1087
1102
  * @typedef {Object} MochaRootHookObject
1088
1103
  * @property {Function|Function[]} [beforeAll] - "Before all" hook(s)
1089
1104
  * @property {Function|Function[]} [beforeEach] - "Before each" hook(s)
@@ -1093,6 +1108,7 @@ Mocha.prototype.rootHooks = function rootHooks(hooks) {
1093
1108
 
1094
1109
  /**
1095
1110
  * An function that returns a {@link MochaRootHookObject}, either sync or async.
1111
+ * @private
1096
1112
  * @callback MochaRootHookFunction
1097
1113
  * @returns {MochaRootHookObject|Promise<MochaRootHookObject>}
1098
1114
  */
package/lib/mocharc.json CHANGED
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "diff": true,
3
3
  "extension": ["js", "cjs", "mjs"],
4
- "opts": "./test/mocha.opts",
5
4
  "package": "./package.json",
6
5
  "reporter": "spec",
7
6
  "slow": 75,
@@ -0,0 +1,174 @@
1
+ /**
2
+ * A wrapper around a third-party child process worker pool implementation.
3
+ * Used by {@link module:buffered-runner}.
4
+ * @private
5
+ * @module buffered-worker-pool
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const serializeJavascript = require('serialize-javascript');
11
+ const workerpool = require('workerpool');
12
+ const {deserialize} = require('./serializer');
13
+ const debug = require('debug')('mocha:parallel:buffered-worker-pool');
14
+ const {createInvalidArgumentTypeError} = require('../errors');
15
+
16
+ const WORKER_PATH = require.resolve('./worker.js');
17
+
18
+ /**
19
+ * A mapping of Mocha `Options` objects to serialized values.
20
+ *
21
+ * This is helpful because we tend to same the same options over and over
22
+ * over IPC.
23
+ * @type {WeakMap<Options,string>}
24
+ */
25
+ let optionsCache = new WeakMap();
26
+
27
+ /**
28
+ * These options are passed into the [workerpool](https://npm.im/workerpool) module.
29
+ * @type {Partial<WorkerPoolOptions>}
30
+ */
31
+ const WORKER_POOL_DEFAULT_OPTS = {
32
+ // use child processes, not worker threads!
33
+ workerType: 'process',
34
+ // ensure the same flags sent to `node` for this `mocha` invocation are passed
35
+ // along to children
36
+ forkOpts: {execArgv: process.execArgv},
37
+ maxWorkers: workerpool.cpus - 1
38
+ };
39
+
40
+ /**
41
+ * A wrapper around a third-party worker pool implementation.
42
+ * @private
43
+ */
44
+ class BufferedWorkerPool {
45
+ /**
46
+ * Creates an underlying worker pool instance; determines max worker count
47
+ * @param {Partial<WorkerPoolOptions>} [opts] - Options
48
+ */
49
+ constructor(opts = {}) {
50
+ const maxWorkers = Math.max(
51
+ 1,
52
+ typeof opts.maxWorkers === 'undefined'
53
+ ? WORKER_POOL_DEFAULT_OPTS.maxWorkers
54
+ : opts.maxWorkers
55
+ );
56
+
57
+ /* istanbul ignore next */
58
+ if (workerpool.cpus < 2) {
59
+ // TODO: decide whether we should warn
60
+ debug(
61
+ 'not enough CPU cores available to run multiple jobs; avoid --parallel on this machine'
62
+ );
63
+ } else if (maxWorkers >= workerpool.cpus) {
64
+ // TODO: decide whether we should warn
65
+ debug(
66
+ '%d concurrent job(s) requested, but only %d core(s) available',
67
+ maxWorkers,
68
+ workerpool.cpus
69
+ );
70
+ }
71
+ /* istanbul ignore next */
72
+ debug(
73
+ 'run(): starting worker pool of max size %d, using node args: %s',
74
+ maxWorkers,
75
+ process.execArgv.join(' ')
76
+ );
77
+
78
+ this.options = Object.assign({}, WORKER_POOL_DEFAULT_OPTS, opts, {
79
+ maxWorkers
80
+ });
81
+ this._pool = workerpool.pool(WORKER_PATH, this.options);
82
+ }
83
+
84
+ /**
85
+ * Terminates all workers in the pool.
86
+ * @param {boolean} [force] - Whether to force-kill workers. By default, lets workers finish their current task before termination.
87
+ * @private
88
+ * @returns {Promise<void>}
89
+ */
90
+ async terminate(force = false) {
91
+ /* istanbul ignore next */
92
+ debug('terminate(): terminating with force = %s', force);
93
+ return this._pool.terminate(force);
94
+ }
95
+
96
+ /**
97
+ * Adds a test file run to the worker pool queue for execution by a worker process.
98
+ *
99
+ * Handles serialization/deserialization.
100
+ *
101
+ * @param {string} filepath - Filepath of test
102
+ * @param {Options} [options] - Options for Mocha instance
103
+ * @private
104
+ * @returns {Promise<SerializedWorkerResult>}
105
+ */
106
+ async run(filepath, options = {}) {
107
+ if (!filepath || typeof filepath !== 'string') {
108
+ throw createInvalidArgumentTypeError(
109
+ 'Expected a non-empty filepath',
110
+ 'filepath',
111
+ 'string'
112
+ );
113
+ }
114
+ const serializedOptions = BufferedWorkerPool.serializeOptions(options);
115
+ const result = await this._pool.exec('run', [filepath, serializedOptions]);
116
+ return deserialize(result);
117
+ }
118
+
119
+ /**
120
+ * Returns stats about the state of the worker processes in the pool.
121
+ *
122
+ * Used for debugging.
123
+ *
124
+ * @private
125
+ */
126
+ stats() {
127
+ return this._pool.stats();
128
+ }
129
+
130
+ /**
131
+ * Instantiates a {@link WorkerPool}.
132
+ * @private
133
+ */
134
+ static create(...args) {
135
+ return new BufferedWorkerPool(...args);
136
+ }
137
+
138
+ /**
139
+ * Given Mocha options object `opts`, serialize into a format suitable for
140
+ * transmission over IPC.
141
+ *
142
+ * @param {Options} [opts] - Mocha options
143
+ * @private
144
+ * @returns {string} Serialized options
145
+ */
146
+ static serializeOptions(opts = {}) {
147
+ if (!optionsCache.has(opts)) {
148
+ const serialized = serializeJavascript(opts, {
149
+ unsafe: true, // this means we don't care about XSS
150
+ ignoreFunction: true // do not serialize functions
151
+ });
152
+ optionsCache.set(opts, serialized);
153
+ /* istanbul ignore next */
154
+ debug(
155
+ 'serializeOptions(): serialized options %O to: %s',
156
+ opts,
157
+ serialized
158
+ );
159
+ }
160
+ return optionsCache.get(opts);
161
+ }
162
+
163
+ /**
164
+ * Resets internal cache of serialized options objects.
165
+ *
166
+ * For testing/debugging
167
+ * @private
168
+ */
169
+ static resetOptionsCache() {
170
+ optionsCache = new WeakMap();
171
+ }
172
+ }
173
+
174
+ exports.BufferedWorkerPool = BufferedWorkerPool;
@@ -8,7 +8,8 @@
8
8
  const os = require('os');
9
9
  const path = require('path');
10
10
  const {sync: which} = require('which');
11
- const {EVENT_RUN_END} = require('./runner').constants;
11
+ const {EVENT_RUN_END} = require('../runner').constants;
12
+ const {isBrowser} = require('../utils');
12
13
 
13
14
  /**
14
15
  * @summary
@@ -25,7 +26,7 @@ const {EVENT_RUN_END} = require('./runner').constants;
25
26
  * @return {boolean} whether Growl notification support can be expected
26
27
  */
27
28
  exports.isCapable = () => {
28
- if (!process.browser) {
29
+ if (!isBrowser()) {
29
30
  return getSupportBinaries().reduce(
30
31
  (acc, binary) => acc || Boolean(which(binary, {nothrow: true})),
31
32
  false