mocha-compat 3.6.4 → 10.8.2

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 (92) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +61 -110
  3. package/bin/_mocha +10 -0
  4. package/bin/mocha.js +142 -0
  5. package/browser-entry.js +61 -22
  6. package/lib/browser/highlight-tags.js +39 -0
  7. package/lib/browser/parse-query.js +24 -0
  8. package/lib/{template.html → browser/template.html} +7 -5
  9. package/lib/cli/cli.js +89 -0
  10. package/lib/cli/collect-files.js +137 -0
  11. package/lib/cli/commands.js +14 -0
  12. package/lib/cli/config.js +100 -0
  13. package/lib/cli/index.js +3 -0
  14. package/lib/cli/init.js +36 -0
  15. package/lib/cli/lookup-files.js +150 -0
  16. package/lib/cli/node-flags.js +85 -0
  17. package/lib/cli/one-and-dones.js +69 -0
  18. package/lib/cli/options.js +284 -0
  19. package/lib/cli/run-helpers.js +304 -0
  20. package/lib/cli/run-option-metadata.js +116 -0
  21. package/lib/cli/run.js +380 -0
  22. package/lib/cli/watch-run.js +377 -0
  23. package/lib/context.js +16 -42
  24. package/lib/errors.js +563 -0
  25. package/lib/hook.js +50 -9
  26. package/lib/interfaces/bdd.js +28 -29
  27. package/lib/interfaces/common.js +56 -21
  28. package/lib/interfaces/exports.js +4 -7
  29. package/lib/interfaces/qunit.js +10 -11
  30. package/lib/interfaces/tdd.js +15 -15
  31. package/lib/mocha.js +1073 -292
  32. package/lib/mocharc.json +10 -0
  33. package/lib/nodejs/buffered-worker-pool.js +188 -0
  34. package/lib/nodejs/esm-utils.js +106 -0
  35. package/lib/nodejs/file-unloader.js +15 -0
  36. package/lib/nodejs/parallel-buffered-runner.js +433 -0
  37. package/lib/nodejs/reporters/parallel-buffered.js +165 -0
  38. package/lib/nodejs/serializer.js +414 -0
  39. package/lib/nodejs/worker.js +151 -0
  40. package/lib/pending.js +3 -3
  41. package/lib/plugin-loader.js +286 -0
  42. package/lib/reporters/base.js +305 -205
  43. package/lib/reporters/doc.js +52 -21
  44. package/lib/reporters/dot.js +31 -18
  45. package/lib/reporters/html.js +153 -81
  46. package/lib/reporters/json-stream.js +53 -24
  47. package/lib/reporters/json.js +88 -18
  48. package/lib/reporters/landing.js +38 -16
  49. package/lib/reporters/list.js +34 -19
  50. package/lib/reporters/markdown.js +28 -15
  51. package/lib/reporters/min.js +22 -8
  52. package/lib/reporters/nyan.js +62 -58
  53. package/lib/reporters/progress.js +32 -19
  54. package/lib/reporters/spec.js +41 -23
  55. package/lib/reporters/tap.js +255 -32
  56. package/lib/reporters/xunit.js +89 -39
  57. package/lib/runnable.js +233 -148
  58. package/lib/runner.js +679 -380
  59. package/lib/stats-collector.js +83 -0
  60. package/lib/suite.js +376 -112
  61. package/lib/test.js +82 -21
  62. package/lib/utils.js +364 -477
  63. package/mocha.css +183 -54
  64. package/mocha.js +19840 -15846
  65. package/mocha.js.map +1 -0
  66. package/package.json +163 -336
  67. package/{lib/to-iso-string → vendor/serialize-javascript}/LICENSE +8 -6
  68. package/vendor/serialize-javascript/README.md +10 -0
  69. package/vendor/serialize-javascript/index.js +349 -0
  70. package/bin/_mocha-compat +0 -572
  71. package/bin/mocha-compat +0 -87
  72. package/bin/options.js +0 -41
  73. package/bower.json +0 -38
  74. package/images/error.png +0 -0
  75. package/images/ok.png +0 -0
  76. package/lib/browser/.eslintrc.yaml +0 -4
  77. package/lib/browser/debug.js +0 -7
  78. package/lib/browser/events.js +0 -195
  79. package/lib/browser/progress.js +0 -119
  80. package/lib/browser/tty.js +0 -13
  81. package/lib/ms.js +0 -130
  82. package/lib/to-iso-string/index.js +0 -37
  83. package/vendor/glob/LICENSE +0 -15
  84. package/vendor/glob/README.md +0 -399
  85. package/vendor/glob/common.js +0 -244
  86. package/vendor/glob/glob.js +0 -788
  87. package/vendor/glob/package.json +0 -55
  88. package/vendor/glob/sync.js +0 -486
  89. package/vendor/inflight/LICENSE +0 -15
  90. package/vendor/inflight/README.md +0 -37
  91. package/vendor/inflight/inflight.js +0 -54
  92. package/vendor/inflight/package.json +0 -29
package/lib/runner.js CHANGED
@@ -2,28 +2,38 @@
2
2
 
3
3
  /**
4
4
  * Module dependencies.
5
+ * @private
5
6
  */
6
-
7
7
  var EventEmitter = require('events').EventEmitter;
8
8
  var Pending = require('./pending');
9
9
  var utils = require('./utils');
10
- var inherits = utils.inherits;
11
10
  var debug = require('debug')('mocha:runner');
12
11
  var Runnable = require('./runnable');
13
- var filter = utils.filter;
14
- var indexOf = utils.indexOf;
15
- var some = utils.some;
16
- var keys = utils.keys;
12
+ var Suite = require('./suite');
13
+ var HOOK_TYPE_BEFORE_EACH = Suite.constants.HOOK_TYPE_BEFORE_EACH;
14
+ var HOOK_TYPE_AFTER_EACH = Suite.constants.HOOK_TYPE_AFTER_EACH;
15
+ var HOOK_TYPE_AFTER_ALL = Suite.constants.HOOK_TYPE_AFTER_ALL;
16
+ var HOOK_TYPE_BEFORE_ALL = Suite.constants.HOOK_TYPE_BEFORE_ALL;
17
+ var EVENT_ROOT_SUITE_RUN = Suite.constants.EVENT_ROOT_SUITE_RUN;
18
+ var STATE_FAILED = Runnable.constants.STATE_FAILED;
19
+ var STATE_PASSED = Runnable.constants.STATE_PASSED;
20
+ var STATE_PENDING = Runnable.constants.STATE_PENDING;
17
21
  var stackFilter = utils.stackTraceFilter();
18
22
  var stringify = utils.stringify;
19
- var type = utils.type;
20
- var undefinedError = utils.undefinedError;
21
- var isArray = utils.isArray;
23
+
24
+ const {
25
+ createInvalidExceptionError,
26
+ createUnsupportedError,
27
+ createFatalError,
28
+ isMochaError,
29
+ constants: errorConstants
30
+ } = require('./errors');
22
31
 
23
32
  /**
24
33
  * Non-enumerable globals.
34
+ * @private
35
+ * @readonly
25
36
  */
26
-
27
37
  var globals = [
28
38
  'setTimeout',
29
39
  'clearTimeout',
@@ -35,81 +45,259 @@ var globals = [
35
45
  'clearImmediate'
36
46
  ];
37
47
 
38
- /**
39
- * Expose `Runner`.
40
- */
41
-
42
- module.exports = Runner;
43
-
44
- /**
45
- * Initialize a `Runner` for the given `suite`.
46
- *
47
- * Events:
48
- *
49
- * - `start` execution started
50
- * - `end` execution complete
51
- * - `suite` (suite) test suite execution started
52
- * - `suite end` (suite) all tests (and sub-suites) have finished
53
- * - `test` (test) test execution started
54
- * - `test end` (test) test completed
55
- * - `hook` (hook) hook execution started
56
- * - `hook end` (hook) hook complete
57
- * - `pass` (test) test passed
58
- * - `fail` (test, err) test failed
59
- * - `pending` (test) test pending
60
- *
61
- * @api public
62
- * @param {Suite} suite Root suite
63
- * @param {boolean} [delay] Whether or not to delay execution of root suite
64
- * until ready.
65
- */
66
- function Runner (suite, delay) {
67
- var self = this;
68
- this._globals = [];
69
- this._abort = false;
70
- this._delay = delay;
71
- this.suite = suite;
72
- this.started = false;
73
- this.total = suite.total();
74
- this.failures = 0;
75
- this.on('test end', function (test) {
76
- self.checkGlobals(test);
77
- });
78
- this.on('hook end', function (hook) {
79
- self.checkGlobals(hook);
80
- });
81
- this._defaultGrep = /.*/;
82
- this.grep(this._defaultGrep);
83
- this.globals(this.globalProps().concat(extraGlobals()));
48
+ var constants = utils.defineConstants(
49
+ /**
50
+ * {@link Runner}-related constants.
51
+ * @public
52
+ * @memberof Runner
53
+ * @readonly
54
+ * @alias constants
55
+ * @static
56
+ * @enum {string}
57
+ */
58
+ {
59
+ /**
60
+ * Emitted when {@link Hook} execution begins
61
+ */
62
+ EVENT_HOOK_BEGIN: 'hook',
63
+ /**
64
+ * Emitted when {@link Hook} execution ends
65
+ */
66
+ EVENT_HOOK_END: 'hook end',
67
+ /**
68
+ * Emitted when Root {@link Suite} execution begins (all files have been parsed and hooks/tests are ready for execution)
69
+ */
70
+ EVENT_RUN_BEGIN: 'start',
71
+ /**
72
+ * Emitted when Root {@link Suite} execution has been delayed via `delay` option
73
+ */
74
+ EVENT_DELAY_BEGIN: 'waiting',
75
+ /**
76
+ * Emitted when delayed Root {@link Suite} execution is triggered by user via `global.run()`
77
+ */
78
+ EVENT_DELAY_END: 'ready',
79
+ /**
80
+ * Emitted when Root {@link Suite} execution ends
81
+ */
82
+ EVENT_RUN_END: 'end',
83
+ /**
84
+ * Emitted when {@link Suite} execution begins
85
+ */
86
+ EVENT_SUITE_BEGIN: 'suite',
87
+ /**
88
+ * Emitted when {@link Suite} execution ends
89
+ */
90
+ EVENT_SUITE_END: 'suite end',
91
+ /**
92
+ * Emitted when {@link Test} execution begins
93
+ */
94
+ EVENT_TEST_BEGIN: 'test',
95
+ /**
96
+ * Emitted when {@link Test} execution ends
97
+ */
98
+ EVENT_TEST_END: 'test end',
99
+ /**
100
+ * Emitted when {@link Test} execution fails
101
+ */
102
+ EVENT_TEST_FAIL: 'fail',
103
+ /**
104
+ * Emitted when {@link Test} execution succeeds
105
+ */
106
+ EVENT_TEST_PASS: 'pass',
107
+ /**
108
+ * Emitted when {@link Test} becomes pending
109
+ */
110
+ EVENT_TEST_PENDING: 'pending',
111
+ /**
112
+ * Emitted when {@link Test} execution has failed, but will retry
113
+ */
114
+ EVENT_TEST_RETRY: 'retry',
115
+ /**
116
+ * Initial state of Runner
117
+ */
118
+ STATE_IDLE: 'idle',
119
+ /**
120
+ * State set to this value when the Runner has started running
121
+ */
122
+ STATE_RUNNING: 'running',
123
+ /**
124
+ * State set to this value when the Runner has stopped
125
+ */
126
+ STATE_STOPPED: 'stopped'
127
+ }
128
+ );
129
+
130
+ class Runner extends EventEmitter {
131
+ /**
132
+ * Initialize a `Runner` at the Root {@link Suite}, which represents a hierarchy of {@link Suite|Suites} and {@link Test|Tests}.
133
+ *
134
+ * @extends external:EventEmitter
135
+ * @public
136
+ * @class
137
+ * @param {Suite} suite - Root suite
138
+ * @param {Object} [opts] - Settings object
139
+ * @param {boolean} [opts.cleanReferencesAfterRun] - Whether to clean references to test fns and hooks when a suite is done.
140
+ * @param {boolean} [opts.delay] - Whether to delay execution of root suite until ready.
141
+ * @param {boolean} [opts.dryRun] - Whether to report tests without running them.
142
+ * @param {boolean} [opts.failZero] - Whether to fail test run if zero tests encountered.
143
+ */
144
+ constructor(suite, opts = {}) {
145
+ super();
146
+
147
+ var self = this;
148
+ this._globals = [];
149
+ this._abort = false;
150
+ this.suite = suite;
151
+ this._opts = opts;
152
+ this.state = constants.STATE_IDLE;
153
+ this.total = suite.total();
154
+ this.failures = 0;
155
+ /**
156
+ * @type {Map<EventEmitter,Map<string,Set<EventListener>>>}
157
+ */
158
+ this._eventListeners = new Map();
159
+ this.on(constants.EVENT_TEST_END, function (test) {
160
+ if (test.type === 'test' && test.retriedTest() && test.parent) {
161
+ var idx =
162
+ test.parent.tests && test.parent.tests.indexOf(test.retriedTest());
163
+ if (idx > -1) test.parent.tests[idx] = test;
164
+ }
165
+ self.checkGlobals(test);
166
+ });
167
+ this.on(constants.EVENT_HOOK_END, function (hook) {
168
+ self.checkGlobals(hook);
169
+ });
170
+ this._defaultGrep = /.*/;
171
+ this.grep(this._defaultGrep);
172
+ this.globals(this.globalProps());
173
+
174
+ this.uncaught = this._uncaught.bind(this);
175
+ this.unhandled = (reason, promise) => {
176
+ if (isMochaError(reason)) {
177
+ debug(
178
+ 'trapped unhandled rejection coming out of Mocha; forwarding to uncaught handler:',
179
+ reason
180
+ );
181
+ this.uncaught(reason);
182
+ } else {
183
+ debug(
184
+ 'trapped unhandled rejection from (probably) user code; re-emitting on process'
185
+ );
186
+ this._removeEventListener(
187
+ process,
188
+ 'unhandledRejection',
189
+ this.unhandled
190
+ );
191
+ try {
192
+ process.emit('unhandledRejection', reason, promise);
193
+ } finally {
194
+ this._addEventListener(process, 'unhandledRejection', this.unhandled);
195
+ }
196
+ }
197
+ };
198
+ }
84
199
  }
85
200
 
86
201
  /**
87
202
  * Wrapper for setImmediate, process.nextTick, or browser polyfill.
88
203
  *
89
204
  * @param {Function} fn
90
- * @api private
205
+ * @private
91
206
  */
92
207
  Runner.immediately = global.setImmediate || process.nextTick;
93
208
 
94
209
  /**
95
- * Inherit from `EventEmitter.prototype`.
210
+ * Replacement for `target.on(eventName, listener)` that does bookkeeping to remove them when this runner instance is disposed.
211
+ * @param {EventEmitter} target - The `EventEmitter`
212
+ * @param {string} eventName - The event name
213
+ * @param {string} fn - Listener function
214
+ * @private
215
+ */
216
+ Runner.prototype._addEventListener = function (target, eventName, listener) {
217
+ debug(
218
+ '_addEventListener(): adding for event %s; %d current listeners',
219
+ eventName,
220
+ target.listenerCount(eventName)
221
+ );
222
+ /* istanbul ignore next */
223
+ if (
224
+ this._eventListeners.has(target) &&
225
+ this._eventListeners.get(target).has(eventName) &&
226
+ this._eventListeners.get(target).get(eventName).has(listener)
227
+ ) {
228
+ debug(
229
+ 'warning: tried to attach duplicate event listener for %s',
230
+ eventName
231
+ );
232
+ return;
233
+ }
234
+ target.on(eventName, listener);
235
+ const targetListeners = this._eventListeners.has(target)
236
+ ? this._eventListeners.get(target)
237
+ : new Map();
238
+ const targetEventListeners = targetListeners.has(eventName)
239
+ ? targetListeners.get(eventName)
240
+ : new Set();
241
+ targetEventListeners.add(listener);
242
+ targetListeners.set(eventName, targetEventListeners);
243
+ this._eventListeners.set(target, targetListeners);
244
+ };
245
+
246
+ /**
247
+ * Replacement for `target.removeListener(eventName, listener)` that also updates the bookkeeping.
248
+ * @param {EventEmitter} target - The `EventEmitter`
249
+ * @param {string} eventName - The event name
250
+ * @param {function} listener - Listener function
251
+ * @private
96
252
  */
97
- inherits(Runner, EventEmitter);
253
+ Runner.prototype._removeEventListener = function (target, eventName, listener) {
254
+ target.removeListener(eventName, listener);
255
+
256
+ if (this._eventListeners.has(target)) {
257
+ const targetListeners = this._eventListeners.get(target);
258
+ if (targetListeners.has(eventName)) {
259
+ const targetEventListeners = targetListeners.get(eventName);
260
+ targetEventListeners.delete(listener);
261
+ if (!targetEventListeners.size) {
262
+ targetListeners.delete(eventName);
263
+ }
264
+ }
265
+ if (!targetListeners.size) {
266
+ this._eventListeners.delete(target);
267
+ }
268
+ } else {
269
+ debug('trying to remove listener for untracked object %s', target);
270
+ }
271
+ };
272
+
273
+ /**
274
+ * Removes all event handlers set during a run on this instance.
275
+ * Remark: this does _not_ clean/dispose the tests or suites themselves.
276
+ */
277
+ Runner.prototype.dispose = function () {
278
+ this.removeAllListeners();
279
+ this._eventListeners.forEach((targetListeners, target) => {
280
+ targetListeners.forEach((targetEventListeners, eventName) => {
281
+ targetEventListeners.forEach(listener => {
282
+ target.removeListener(eventName, listener);
283
+ });
284
+ });
285
+ });
286
+ this._eventListeners.clear();
287
+ };
98
288
 
99
289
  /**
100
290
  * Run tests with full titles matching `re`. Updates runner.total
101
291
  * with number of tests matched.
102
292
  *
103
- * @param {RegExp} re
104
- * @param {Boolean} invert
105
- * @return {Runner} for chaining
106
- * @api public
293
+ * @public
294
+ * @memberof Runner
107
295
  * @param {RegExp} re
108
296
  * @param {boolean} invert
109
297
  * @return {Runner} Runner instance.
110
298
  */
111
299
  Runner.prototype.grep = function (re, invert) {
112
- debug('grep %s', re);
300
+ debug('grep(): setting to %s', re);
113
301
  this._grep = re;
114
302
  this._invert = invert;
115
303
  this.total = this.grepTotal(this.suite);
@@ -120,9 +308,8 @@ Runner.prototype.grep = function (re, invert) {
120
308
  * Returns the number of tests matching the grep search for the
121
309
  * given suite.
122
310
  *
123
- * @param {Suite} suite
124
- * @return {Number}
125
- * @api public
311
+ * @memberof Runner
312
+ * @public
126
313
  * @param {Suite} suite
127
314
  * @return {number}
128
315
  */
@@ -147,14 +334,14 @@ Runner.prototype.grepTotal = function (suite) {
147
334
  * Return a list of global properties.
148
335
  *
149
336
  * @return {Array}
150
- * @api private
337
+ * @private
151
338
  */
152
339
  Runner.prototype.globalProps = function () {
153
- var props = keys(global);
340
+ var props = Object.keys(global);
154
341
 
155
342
  // non-enumerables
156
343
  for (var i = 0; i < globals.length; ++i) {
157
- if (~indexOf(props, globals[i])) {
344
+ if (~props.indexOf(globals[i])) {
158
345
  continue;
159
346
  }
160
347
  props.push(globals[i]);
@@ -166,9 +353,8 @@ Runner.prototype.globalProps = function () {
166
353
  /**
167
354
  * Allow the given `arr` of globals.
168
355
  *
169
- * @param {Array} arr
170
- * @return {Runner} for chaining
171
- * @api public
356
+ * @public
357
+ * @memberof Runner
172
358
  * @param {Array} arr
173
359
  * @return {Runner} Runner instance.
174
360
  */
@@ -176,7 +362,7 @@ Runner.prototype.globals = function (arr) {
176
362
  if (!arguments.length) {
177
363
  return this._globals;
178
364
  }
179
- debug('globals %j', arr);
365
+ debug('globals(): setting to %O', arr);
180
366
  this._globals = this._globals.concat(arr);
181
367
  return this;
182
368
  };
@@ -184,10 +370,10 @@ Runner.prototype.globals = function (arr) {
184
370
  /**
185
371
  * Check for global variable leaks.
186
372
  *
187
- * @api private
373
+ * @private
188
374
  */
189
375
  Runner.prototype.checkGlobals = function (test) {
190
- if (this.ignoreLeaks) {
376
+ if (!this.checkLeaks) {
191
377
  return;
192
378
  }
193
379
  var ok = this._globals;
@@ -207,132 +393,177 @@ Runner.prototype.checkGlobals = function (test) {
207
393
  leaks = filterLeaks(ok, globals);
208
394
  this._globals = this._globals.concat(leaks);
209
395
 
210
- if (leaks.length > 1) {
211
- this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + ''));
212
- } else if (leaks.length) {
213
- this.fail(test, new Error('global leak detected: ' + leaks[0]));
396
+ if (leaks.length) {
397
+ var msg = `global leak(s) detected: ${leaks.map(e => `'${e}'`).join(', ')}`;
398
+ this.fail(test, new Error(msg));
214
399
  }
215
400
  };
216
401
 
217
402
  /**
218
403
  * Fail the given `test`.
219
404
  *
220
- * @api private
221
- * @param {Test} test
222
- * @param {Error} err
223
- */
224
- Runner.prototype.fail = function (test, err) {
225
- if (test.isPending()) {
226
- return;
227
- }
228
-
229
- ++this.failures;
230
- test.state = 'failed';
231
-
232
- if (!(err instanceof Error || err && typeof err.message === 'string')) {
233
- err = new Error('the ' + type(err) + ' ' + stringify(err) + ' was thrown, throw an Error :)');
234
- }
235
-
236
- try {
237
- err.stack = (this.fullStackTrace || !err.stack)
238
- ? err.stack
239
- : stackFilter(err.stack);
240
- } catch (ignored) {
241
- // some environments do not take kindly to monkeying with the stack
242
- }
243
-
244
- this.emit('fail', test, err);
245
- };
246
-
247
- /**
248
- * Fail the given `hook` with `err`.
249
- *
250
- * Hook failures work in the following pattern:
251
- * - If bail, then exit
405
+ * If `test` is a hook, failures work in the following pattern:
406
+ * - If bail, run corresponding `after each` and `after` hooks,
407
+ * then exit
252
408
  * - Failed `before` hook skips all tests in a suite and subsuites,
253
409
  * but jumps to corresponding `after` hook
254
410
  * - Failed `before each` hook skips remaining tests in a
255
411
  * suite and jumps to corresponding `after each` hook,
256
412
  * which is run only once
257
- * - Failed `after` hook does not alter
258
- * execution order
413
+ * - Failed `after` hook does not alter execution order
259
414
  * - Failed `after each` hook skips remaining tests in a
260
415
  * suite and subsuites, but executes other `after each`
261
416
  * hooks
262
417
  *
263
- * @api private
264
- * @param {Hook} hook
418
+ * @private
419
+ * @param {Runnable} test
265
420
  * @param {Error} err
421
+ * @param {boolean} [force=false] - Whether to fail a pending test.
266
422
  */
267
- Runner.prototype.failHook = function (hook, err) {
268
- if (hook.ctx && hook.ctx.currentTest) {
269
- hook.originalTitle = hook.originalTitle || hook.title;
270
- hook.title = hook.originalTitle + ' for "' + hook.ctx.currentTest.title + '"';
423
+ Runner.prototype.fail = function (test, err, force) {
424
+ force = force === true;
425
+ if (test.isPending() && !force) {
426
+ return;
427
+ }
428
+ if (this.state === constants.STATE_STOPPED) {
429
+ if (err.code === errorConstants.MULTIPLE_DONE) {
430
+ throw err;
431
+ }
432
+ throw createFatalError(
433
+ 'Test failed after root suite execution completed!',
434
+ err
435
+ );
436
+ }
437
+
438
+ ++this.failures;
439
+ debug('total number of failures: %d', this.failures);
440
+ test.state = STATE_FAILED;
441
+
442
+ if (!isError(err)) {
443
+ err = thrown2Error(err);
271
444
  }
272
445
 
273
- this.fail(hook, err);
274
- if (this.suite.bail()) {
275
- this.emit('end');
446
+ // Filter the stack traces
447
+ if (!this.fullStackTrace) {
448
+ const alreadyFiltered = new Set();
449
+ let currentErr = err;
450
+
451
+ while (currentErr && currentErr.stack && !alreadyFiltered.has(currentErr)) {
452
+ alreadyFiltered.add(currentErr);
453
+
454
+ try {
455
+ currentErr.stack = stackFilter(currentErr.stack);
456
+ } catch (ignore) {
457
+ // some environments do not take kindly to monkeying with the stack
458
+ }
459
+
460
+ currentErr = currentErr.cause;
461
+ }
276
462
  }
463
+
464
+ this.emit(constants.EVENT_TEST_FAIL, test, err);
277
465
  };
278
466
 
279
467
  /**
280
468
  * Run hook `name` callbacks and then invoke `fn()`.
281
469
  *
282
- * @api private
470
+ * @private
283
471
  * @param {string} name
284
472
  * @param {Function} fn
285
473
  */
286
474
 
287
475
  Runner.prototype.hook = function (name, fn) {
476
+ if (this._opts.dryRun) return fn();
477
+
288
478
  var suite = this.suite;
289
- var hooks = suite['_' + name];
479
+ var hooks = suite.getHooks(name);
290
480
  var self = this;
291
481
 
292
- function next (i) {
482
+ function next(i) {
293
483
  var hook = hooks[i];
294
484
  if (!hook) {
295
485
  return fn();
296
486
  }
297
487
  self.currentRunnable = hook;
298
488
 
299
- hook.ctx.currentTest = self.test;
489
+ if (name === HOOK_TYPE_BEFORE_ALL) {
490
+ hook.ctx.currentTest = hook.parent.tests[0];
491
+ } else if (name === HOOK_TYPE_AFTER_ALL) {
492
+ hook.ctx.currentTest = hook.parent.tests[hook.parent.tests.length - 1];
493
+ } else {
494
+ hook.ctx.currentTest = self.test;
495
+ }
496
+
497
+ setHookTitle(hook);
300
498
 
301
- self.emit('hook', hook);
499
+ hook.allowUncaught = self.allowUncaught;
500
+
501
+ self.emit(constants.EVENT_HOOK_BEGIN, hook);
302
502
 
303
503
  if (!hook.listeners('error').length) {
304
- hook.on('error', function (err) {
305
- self.failHook(hook, err);
504
+ self._addEventListener(hook, 'error', function (err) {
505
+ self.fail(hook, err);
306
506
  });
307
507
  }
308
508
 
309
- hook.run(function (err) {
509
+ hook.run(function cbHookRun(err) {
310
510
  var testError = hook.error();
311
511
  if (testError) {
312
512
  self.fail(self.test, testError);
313
513
  }
314
- if (err) {
315
- if (err instanceof Pending) {
316
- if (name === 'beforeEach' || name === 'afterEach') {
514
+ // conditional skip
515
+ if (hook.pending) {
516
+ if (name === HOOK_TYPE_AFTER_EACH) {
517
+ // TODO define and implement use case
518
+ if (self.test) {
317
519
  self.test.pending = true;
318
- } else {
319
- utils.forEach(suite.tests, function (test) {
320
- test.pending = true;
321
- });
322
- // a pending hook won't be executed twice.
323
- hook.pending = true;
324
520
  }
521
+ } else if (name === HOOK_TYPE_BEFORE_EACH) {
522
+ if (self.test) {
523
+ self.test.pending = true;
524
+ }
525
+ self.emit(constants.EVENT_HOOK_END, hook);
526
+ hook.pending = false; // activates hook for next test
527
+ return fn(new Error('abort hookDown'));
528
+ } else if (name === HOOK_TYPE_BEFORE_ALL) {
529
+ suite.tests.forEach(function (test) {
530
+ test.pending = true;
531
+ });
532
+ suite.suites.forEach(function (suite) {
533
+ suite.pending = true;
534
+ });
535
+ hooks = [];
325
536
  } else {
326
- self.failHook(hook, err);
327
-
328
- // stop executing hooks, notify callee of hook err
329
- return fn(err);
537
+ hook.pending = false;
538
+ var errForbid = createUnsupportedError('`this.skip` forbidden');
539
+ self.fail(hook, errForbid);
540
+ return fn(errForbid);
330
541
  }
542
+ } else if (err) {
543
+ self.fail(hook, err);
544
+ // stop executing hooks, notify callee of hook err
545
+ return fn(err);
331
546
  }
332
- self.emit('hook end', hook);
547
+ self.emit(constants.EVENT_HOOK_END, hook);
333
548
  delete hook.ctx.currentTest;
549
+ setHookTitle(hook);
334
550
  next(++i);
335
551
  });
552
+
553
+ function setHookTitle(hook) {
554
+ hook.originalTitle = hook.originalTitle || hook.title;
555
+ if (hook.ctx && hook.ctx.currentTest) {
556
+ hook.title = `${hook.originalTitle} for "${hook.ctx.currentTest.title}"`;
557
+ } else {
558
+ var parentTitle;
559
+ if (hook.parent.title) {
560
+ parentTitle = hook.parent.title;
561
+ } else {
562
+ parentTitle = hook.parent.root ? '{root}' : '';
563
+ }
564
+ hook.title = `${hook.originalTitle} in "${parentTitle}"`;
565
+ }
566
+ }
336
567
  }
337
568
 
338
569
  Runner.immediately(function () {
@@ -344,7 +575,7 @@ Runner.prototype.hook = function (name, fn) {
344
575
  * Run hook `name` for the given array of `suites`
345
576
  * in order, and callback `fn(err, errSuite)`.
346
577
  *
347
- * @api private
578
+ * @private
348
579
  * @param {string} name
349
580
  * @param {Array} suites
350
581
  * @param {Function} fn
@@ -353,7 +584,7 @@ Runner.prototype.hooks = function (name, suites, fn) {
353
584
  var self = this;
354
585
  var orig = this.suite;
355
586
 
356
- function next (suite) {
587
+ function next(suite) {
357
588
  self.suite = suite;
358
589
 
359
590
  if (!suite) {
@@ -376,11 +607,11 @@ Runner.prototype.hooks = function (name, suites, fn) {
376
607
  };
377
608
 
378
609
  /**
379
- * Run hooks from the top level down.
610
+ * Run 'afterEach' hooks from bottom up.
380
611
  *
381
612
  * @param {String} name
382
613
  * @param {Function} fn
383
- * @api private
614
+ * @private
384
615
  */
385
616
  Runner.prototype.hookUp = function (name, fn) {
386
617
  var suites = [this.suite].concat(this.parents()).reverse();
@@ -388,11 +619,11 @@ Runner.prototype.hookUp = function (name, fn) {
388
619
  };
389
620
 
390
621
  /**
391
- * Run hooks from the bottom up.
622
+ * Run 'beforeEach' hooks from top level down.
392
623
  *
393
624
  * @param {String} name
394
625
  * @param {Function} fn
395
- * @api private
626
+ * @private
396
627
  */
397
628
  Runner.prototype.hookDown = function (name, fn) {
398
629
  var suites = [this.suite].concat(this.parents());
@@ -404,7 +635,7 @@ Runner.prototype.hookDown = function (name, fn) {
404
635
  * closest to furthest.
405
636
  *
406
637
  * @return {Array}
407
- * @api private
638
+ * @private
408
639
  */
409
640
  Runner.prototype.parents = function () {
410
641
  var suite = this.suite;
@@ -420,19 +651,22 @@ Runner.prototype.parents = function () {
420
651
  * Run the current test and callback `fn(err)`.
421
652
  *
422
653
  * @param {Function} fn
423
- * @api private
654
+ * @private
424
655
  */
425
656
  Runner.prototype.runTest = function (fn) {
657
+ if (this._opts.dryRun) return Runner.immediately(fn);
658
+
426
659
  var self = this;
427
660
  var test = this.test;
428
661
 
429
662
  if (!test) {
430
663
  return;
431
664
  }
665
+
432
666
  if (this.asyncOnly) {
433
667
  test.asyncOnly = true;
434
668
  }
435
- test.on('error', function (err) {
669
+ this._addEventListener(test, 'error', function (err) {
436
670
  self.fail(test, err);
437
671
  });
438
672
  if (this.allowUncaught) {
@@ -449,7 +683,7 @@ Runner.prototype.runTest = function (fn) {
449
683
  /**
450
684
  * Run tests in the given `suite` and invoke the callback `fn()` when complete.
451
685
  *
452
- * @api private
686
+ * @private
453
687
  * @param {Suite} suite
454
688
  * @param {Function} fn
455
689
  */
@@ -458,7 +692,7 @@ Runner.prototype.runTests = function (suite, fn) {
458
692
  var tests = suite.tests.slice();
459
693
  var test;
460
694
 
461
- function hookErr (_, errSuite, after) {
695
+ function hookErr(_, errSuite, after) {
462
696
  // before/after Each hook for errSuite failed:
463
697
  var orig = self.suite;
464
698
 
@@ -467,8 +701,7 @@ Runner.prototype.runTests = function (suite, fn) {
467
701
  self.suite = after ? errSuite.parent : errSuite;
468
702
 
469
703
  if (self.suite) {
470
- // call hookUp afterEach
471
- self.hookUp('afterEach', function (err2, errSuite2) {
704
+ self.hookUp(HOOK_TYPE_AFTER_EACH, function (err2, errSuite2) {
472
705
  self.suite = orig;
473
706
  // some hooks may fail even now
474
707
  if (err2) {
@@ -484,10 +717,10 @@ Runner.prototype.runTests = function (suite, fn) {
484
717
  }
485
718
  }
486
719
 
487
- function next (err, errSuite) {
720
+ function next(err, errSuite) {
488
721
  // if we bail after first err
489
722
  if (self.failures && suite._bail) {
490
- return fn();
723
+ tests = [];
491
724
  }
492
725
 
493
726
  if (self._abort) {
@@ -528,19 +761,37 @@ Runner.prototype.runTests = function (suite, fn) {
528
761
  return;
529
762
  }
530
763
 
764
+ // static skip, no hooks are executed
531
765
  if (test.isPending()) {
532
- self.emit('pending', test);
533
- self.emit('test end', test);
766
+ if (self.forbidPending) {
767
+ self.fail(test, new Error('Pending test forbidden'), true);
768
+ } else {
769
+ test.state = STATE_PENDING;
770
+ self.emit(constants.EVENT_TEST_PENDING, test);
771
+ }
772
+ self.emit(constants.EVENT_TEST_END, test);
534
773
  return next();
535
774
  }
536
775
 
537
776
  // execute test and hook(s)
538
- self.emit('test', self.test = test);
539
- self.hookDown('beforeEach', function (err, errSuite) {
777
+ self.emit(constants.EVENT_TEST_BEGIN, (self.test = test));
778
+ self.hookDown(HOOK_TYPE_BEFORE_EACH, function (err, errSuite) {
779
+ // conditional skip within beforeEach
540
780
  if (test.isPending()) {
541
- self.emit('pending', test);
542
- self.emit('test end', test);
543
- return next();
781
+ if (self.forbidPending) {
782
+ self.fail(test, new Error('Pending test forbidden'), true);
783
+ } else {
784
+ test.state = STATE_PENDING;
785
+ self.emit(constants.EVENT_TEST_PENDING, test);
786
+ }
787
+ self.emit(constants.EVENT_TEST_END, test);
788
+ // skip inner afterEach hooks below errSuite level
789
+ var origSuite = self.suite;
790
+ self.suite = errSuite || self.suite;
791
+ return self.hookUp(HOOK_TYPE_AFTER_EACH, function (e, eSuite) {
792
+ self.suite = origSuite;
793
+ next(e, eSuite);
794
+ });
544
795
  }
545
796
  if (err) {
546
797
  return hookErr(err, errSuite, false);
@@ -548,35 +799,39 @@ Runner.prototype.runTests = function (suite, fn) {
548
799
  self.currentRunnable = self.test;
549
800
  self.runTest(function (err) {
550
801
  test = self.test;
551
- if (err) {
802
+ // conditional skip within it
803
+ if (test.pending) {
804
+ if (self.forbidPending) {
805
+ self.fail(test, new Error('Pending test forbidden'), true);
806
+ } else {
807
+ test.state = STATE_PENDING;
808
+ self.emit(constants.EVENT_TEST_PENDING, test);
809
+ }
810
+ self.emit(constants.EVENT_TEST_END, test);
811
+ return self.hookUp(HOOK_TYPE_AFTER_EACH, next);
812
+ } else if (err) {
552
813
  var retry = test.currentRetry();
553
- if (err instanceof Pending) {
554
- test.pending = true;
555
- self.emit('pending', test);
556
- } else if (retry < test.retries()) {
814
+ if (retry < test.retries()) {
557
815
  var clonedTest = test.clone();
558
816
  clonedTest.currentRetry(retry + 1);
559
817
  tests.unshift(clonedTest);
560
818
 
819
+ self.emit(constants.EVENT_TEST_RETRY, test, err);
820
+
561
821
  // Early return + hook trigger so that it doesn't
562
822
  // increment the count wrong
563
- return self.hookUp('afterEach', next);
823
+ return self.hookUp(HOOK_TYPE_AFTER_EACH, next);
564
824
  } else {
565
825
  self.fail(test, err);
566
826
  }
567
- self.emit('test end', test);
568
-
569
- if (err instanceof Pending) {
570
- return next();
571
- }
572
-
573
- return self.hookUp('afterEach', next);
827
+ self.emit(constants.EVENT_TEST_END, test);
828
+ return self.hookUp(HOOK_TYPE_AFTER_EACH, next);
574
829
  }
575
830
 
576
- test.state = 'passed';
577
- self.emit('pass', test);
578
- self.emit('test end', test);
579
- self.hookUp('afterEach', next);
831
+ test.state = STATE_PASSED;
832
+ self.emit(constants.EVENT_TEST_PASS, test);
833
+ self.emit(constants.EVENT_TEST_END, test);
834
+ self.hookUp(HOOK_TYPE_AFTER_EACH, next);
580
835
  });
581
836
  });
582
837
  }
@@ -589,7 +844,7 @@ Runner.prototype.runTests = function (suite, fn) {
589
844
  /**
590
845
  * Run the given `suite` and invoke the callback `fn()` when complete.
591
846
  *
592
- * @api private
847
+ * @private
593
848
  * @param {Suite} suite
594
849
  * @param {Function} fn
595
850
  */
@@ -597,17 +852,17 @@ Runner.prototype.runSuite = function (suite, fn) {
597
852
  var i = 0;
598
853
  var self = this;
599
854
  var total = this.grepTotal(suite);
600
- var afterAllHookCalled = false;
601
855
 
602
- debug('run suite %s', suite.fullTitle());
856
+ debug('runSuite(): running %s', suite.fullTitle());
603
857
 
604
858
  if (!total || (self.failures && suite._bail)) {
859
+ debug('runSuite(): bailing');
605
860
  return fn();
606
861
  }
607
862
 
608
- this.emit('suite', this.suite = suite);
863
+ this.emit(constants.EVENT_SUITE_BEGIN, (this.suite = suite));
609
864
 
610
- function next (errSuite) {
865
+ function next(errSuite) {
611
866
  if (errSuite) {
612
867
  // current suite failed on a hook from errSuite
613
868
  if (errSuite === suite) {
@@ -641,30 +896,22 @@ Runner.prototype.runSuite = function (suite, fn) {
641
896
  }
642
897
  }
643
898
 
644
- function done (errSuite) {
899
+ function done(errSuite) {
645
900
  self.suite = suite;
646
901
  self.nextSuite = next;
647
902
 
648
- if (afterAllHookCalled) {
649
- fn(errSuite);
650
- } else {
651
- // mark that the afterAll block has been called once
652
- // and so can be skipped if there is an error in it.
653
- afterAllHookCalled = true;
654
-
655
- // remove reference to test
656
- delete self.test;
903
+ // remove reference to test
904
+ delete self.test;
657
905
 
658
- self.hook('afterAll', function () {
659
- self.emit('suite end', suite);
660
- fn(errSuite);
661
- });
662
- }
906
+ self.hook(HOOK_TYPE_AFTER_ALL, function () {
907
+ self.emit(constants.EVENT_SUITE_END, suite);
908
+ fn(errSuite);
909
+ });
663
910
  }
664
911
 
665
912
  this.nextSuite = next;
666
913
 
667
- this.hook('beforeAll', function (err) {
914
+ this.hook(HOOK_TYPE_BEFORE_ALL, function (err) {
668
915
  if (err) {
669
916
  return done();
670
917
  }
@@ -673,19 +920,60 @@ Runner.prototype.runSuite = function (suite, fn) {
673
920
  };
674
921
 
675
922
  /**
676
- * Handle uncaught exceptions.
923
+ * Handle uncaught exceptions within runner.
677
924
  *
678
- * @param {Error} err
679
- * @api private
925
+ * This function is bound to the instance as `Runner#uncaught` at instantiation
926
+ * time. It's intended to be listening on the `Process.uncaughtException` event.
927
+ * In order to not leak EE listeners, we need to ensure no more than a single
928
+ * `uncaughtException` listener exists per `Runner`. The only way to do
929
+ * this--because this function needs the context (and we don't have lambdas)--is
930
+ * to use `Function.prototype.bind`. We need strict equality to unregister and
931
+ * _only_ unregister the _one_ listener we set from the
932
+ * `Process.uncaughtException` event; would be poor form to just remove
933
+ * everything. See {@link Runner#run} for where the event listener is registered
934
+ * and unregistered.
935
+ * @param {Error} err - Some uncaught error
936
+ * @private
680
937
  */
681
- Runner.prototype.uncaught = function (err) {
938
+ Runner.prototype._uncaught = function (err) {
939
+ // this is defensive to prevent future developers from mis-calling this function.
940
+ // it's more likely that it'd be called with the incorrect context--say, the global
941
+ // `process` object--than it would to be called with a context that is not a "subclass"
942
+ // of `Runner`.
943
+ if (!(this instanceof Runner)) {
944
+ throw createFatalError(
945
+ 'Runner#uncaught() called with invalid context',
946
+ this
947
+ );
948
+ }
949
+ if (err instanceof Pending) {
950
+ debug('uncaught(): caught a Pending');
951
+ return;
952
+ }
953
+ // browser does not exit script when throwing in global.onerror()
954
+ if (this.allowUncaught && !utils.isBrowser()) {
955
+ debug('uncaught(): bubbling exception due to --allow-uncaught');
956
+ throw err;
957
+ }
958
+
959
+ if (this.state === constants.STATE_STOPPED) {
960
+ debug('uncaught(): throwing after run has completed!');
961
+ throw err;
962
+ }
963
+
682
964
  if (err) {
683
- debug('uncaught exception %s', err === (function () {
684
- return this;
685
- }.call(err)) ? (err.message || err) : err);
965
+ debug('uncaught(): got truthy exception %O', err);
686
966
  } else {
687
- debug('uncaught undefined exception');
688
- err = undefinedError();
967
+ debug('uncaught(): undefined/falsy exception');
968
+ err = createInvalidExceptionError(
969
+ 'Caught falsy/undefined exception which would otherwise be uncaught. No stack trace found; try a debugger',
970
+ err
971
+ );
972
+ }
973
+
974
+ if (!isError(err)) {
975
+ err = thrown2Error(err);
976
+ debug('uncaught(): converted "error" %o to Error', err);
689
977
  }
690
978
  err.uncaught = true;
691
979
 
@@ -693,15 +981,18 @@ Runner.prototype.uncaught = function (err) {
693
981
 
694
982
  if (!runnable) {
695
983
  runnable = new Runnable('Uncaught error outside test suite');
984
+ debug('uncaught(): no current Runnable; created a phony one');
696
985
  runnable.parent = this.suite;
697
986
 
698
- if (this.started) {
987
+ if (this.state === constants.STATE_RUNNING) {
988
+ debug('uncaught(): failing gracefully');
699
989
  this.fail(runnable, err);
700
990
  } else {
701
991
  // Can't recover from this failure
702
- this.emit('start');
992
+ debug('uncaught(): test run has not yet started; unrecoverable');
993
+ this.emit(constants.EVENT_RUN_BEGIN);
703
994
  this.fail(runnable, err);
704
- this.emit('end');
995
+ this.emit(constants.EVENT_RUN_END);
705
996
  }
706
997
 
707
998
  return;
@@ -709,208 +1000,201 @@ Runner.prototype.uncaught = function (err) {
709
1000
 
710
1001
  runnable.clearTimeout();
711
1002
 
712
- // Ignore errors if complete or pending
713
- if (runnable.state || runnable.isPending()) {
1003
+ if (runnable.isFailed()) {
1004
+ debug('uncaught(): Runnable has already failed');
1005
+ // Ignore error if already failed
714
1006
  return;
715
- }
716
- this.fail(runnable, err);
717
-
718
- // recover from test
719
- if (runnable.type === 'test') {
720
- this.emit('test end', runnable);
721
- this.hookUp('afterEach', this.next);
1007
+ } else if (runnable.isPending()) {
1008
+ debug('uncaught(): pending Runnable wound up failing!');
1009
+ // report 'pending test' retrospectively as failed
1010
+ this.fail(runnable, err, true);
722
1011
  return;
723
1012
  }
724
1013
 
725
- // recover from hooks
726
- if (runnable.type === 'hook') {
727
- var errSuite = this.suite;
728
- // if hook failure is in afterEach block
729
- if (runnable.fullTitle().indexOf('after each') > -1) {
730
- return this.hookErr(err, errSuite, true);
731
- }
732
- // if hook failure is in beforeEach block
733
- if (runnable.fullTitle().indexOf('before each') > -1) {
734
- return this.hookErr(err, errSuite, false);
735
- }
736
- // if hook failure is in after or before blocks
737
- return this.nextSuite(errSuite);
1014
+ // we cannot recover gracefully if a Runnable has already passed
1015
+ // then fails asynchronously
1016
+ if (runnable.isPassed()) {
1017
+ debug('uncaught(): Runnable has already passed; bailing gracefully');
1018
+ this.fail(runnable, err);
1019
+ this.abort();
1020
+ } else {
1021
+ debug('uncaught(): forcing Runnable to complete with Error');
1022
+ return runnable.callback(err);
738
1023
  }
739
-
740
- // bail
741
- this.emit('end');
742
1024
  };
743
1025
 
744
- /**
745
- * Cleans up the references to all the deferred functions
746
- * (before/after/beforeEach/afterEach) and tests of a Suite.
747
- * These must be deleted otherwise a memory leak can happen,
748
- * as those functions may reference variables from closures,
749
- * thus those variables can never be garbage collected as long
750
- * as the deferred functions exist.
751
- *
752
- * @param {Suite} suite
753
- */
754
- function cleanSuiteReferences (suite) {
755
- function cleanArrReferences (arr) {
756
- for (var i = 0; i < arr.length; i++) {
757
- delete arr[i].fn;
758
- }
759
- }
760
-
761
- if (isArray(suite._beforeAll)) {
762
- cleanArrReferences(suite._beforeAll);
763
- }
764
-
765
- if (isArray(suite._beforeEach)) {
766
- cleanArrReferences(suite._beforeEach);
767
- }
768
-
769
- if (isArray(suite._afterAll)) {
770
- cleanArrReferences(suite._afterAll);
771
- }
772
-
773
- if (isArray(suite._afterEach)) {
774
- cleanArrReferences(suite._afterEach);
775
- }
776
-
777
- for (var i = 0; i < suite.tests.length; i++) {
778
- delete suite.tests[i].fn;
779
- }
780
- }
781
-
782
1026
  /**
783
1027
  * Run the root suite and invoke `fn(failures)`
784
1028
  * on completion.
785
1029
  *
786
- * @param {Function} fn
787
- * @return {Runner} for chaining
788
- * @api public
789
- * @param {Function} fn
790
- * @return {Runner} Runner instance.
1030
+ * @public
1031
+ * @memberof Runner
1032
+ * @param {Function} fn - Callback when finished
1033
+ * @param {Object} [opts] - For subclasses
1034
+ * @param {string[]} opts.files - Files to run
1035
+ * @param {Options} opts.options - command-line options
1036
+ * @returns {Runner} Runner instance.
791
1037
  */
792
- Runner.prototype.run = function (fn) {
793
- var self = this;
1038
+ Runner.prototype.run = function (fn, opts = {}) {
794
1039
  var rootSuite = this.suite;
1040
+ var options = opts.options || {};
795
1041
 
796
- // If there is an `only` filter
797
- if (this.hasOnly) {
798
- filterOnly(rootSuite);
799
- }
800
-
1042
+ debug('run(): got options: %O', options);
801
1043
  fn = fn || function () {};
802
1044
 
803
- function uncaught (err) {
804
- self.uncaught(err);
805
- }
1045
+ const end = () => {
1046
+ if (!this.total && this._opts.failZero) this.failures = 1;
806
1047
 
807
- function start () {
808
- self.started = true;
809
- self.emit('start');
810
- self.runSuite(rootSuite, function () {
811
- debug('finished running');
812
- self.emit('end');
813
- });
814
- }
1048
+ debug('run(): root suite completed; emitting %s', constants.EVENT_RUN_END);
1049
+ this.emit(constants.EVENT_RUN_END);
1050
+ };
815
1051
 
816
- debug('start');
1052
+ const begin = () => {
1053
+ debug('run(): emitting %s', constants.EVENT_RUN_BEGIN);
1054
+ this.emit(constants.EVENT_RUN_BEGIN);
1055
+ debug('run(): emitted %s', constants.EVENT_RUN_BEGIN);
817
1056
 
818
- // references cleanup to avoid memory leaks
819
- this.on('suite end', cleanSuiteReferences);
1057
+ this.runSuite(rootSuite, end);
1058
+ };
820
1059
 
821
- // callback
822
- this.on('end', function () {
823
- if (self.forbidOnly && self.hasOnly) {
824
- self.failures += self.stats.tests;
1060
+ const prepare = () => {
1061
+ debug('run(): starting');
1062
+ // If there is an `only` filter
1063
+ if (rootSuite.hasOnly()) {
1064
+ rootSuite.filterOnly();
1065
+ debug('run(): filtered exclusive Runnables');
825
1066
  }
826
- if (self.forbidPending) {
827
- self.failures += self.stats.pending;
1067
+ this.state = constants.STATE_RUNNING;
1068
+ if (this._opts.delay) {
1069
+ this.emit(constants.EVENT_DELAY_END);
1070
+ debug('run(): "delay" ended');
828
1071
  }
829
- debug('end');
830
- process.removeListener('uncaughtException', uncaught);
831
- fn(self.failures);
1072
+
1073
+ return begin();
1074
+ };
1075
+
1076
+ // references cleanup to avoid memory leaks
1077
+ if (this._opts.cleanReferencesAfterRun) {
1078
+ this.on(constants.EVENT_SUITE_END, suite => {
1079
+ suite.cleanReferences();
1080
+ });
1081
+ }
1082
+
1083
+ // callback
1084
+ this.on(constants.EVENT_RUN_END, function () {
1085
+ this.state = constants.STATE_STOPPED;
1086
+ debug('run(): emitted %s', constants.EVENT_RUN_END);
1087
+ fn(this.failures);
832
1088
  });
833
1089
 
834
- // uncaught exception
835
- process.on('uncaughtException', uncaught);
1090
+ this._removeEventListener(process, 'uncaughtException', this.uncaught);
1091
+ this._removeEventListener(process, 'unhandledRejection', this.unhandled);
1092
+ this._addEventListener(process, 'uncaughtException', this.uncaught);
1093
+ this._addEventListener(process, 'unhandledRejection', this.unhandled);
836
1094
 
837
- if (this._delay) {
1095
+ if (this._opts.delay) {
838
1096
  // for reporters, I guess.
839
1097
  // might be nice to debounce some dots while we wait.
840
- this.emit('waiting', rootSuite);
841
- rootSuite.once('run', start);
1098
+ this.emit(constants.EVENT_DELAY_BEGIN, rootSuite);
1099
+ rootSuite.once(EVENT_ROOT_SUITE_RUN, prepare);
1100
+ debug('run(): waiting for green light due to --delay');
842
1101
  } else {
843
- start();
1102
+ Runner.immediately(prepare);
844
1103
  }
845
1104
 
846
1105
  return this;
847
1106
  };
848
1107
 
1108
+ /**
1109
+ * Toggle partial object linking behavior; used for building object references from
1110
+ * unique ID's. Does nothing in serial mode, because the object references already exist.
1111
+ * Subclasses can implement this (e.g., `ParallelBufferedRunner`)
1112
+ * @abstract
1113
+ * @param {boolean} [value] - If `true`, enable partial object linking, otherwise disable
1114
+ * @returns {Runner}
1115
+ * @chainable
1116
+ * @public
1117
+ * @example
1118
+ * // this reporter needs proper object references when run in parallel mode
1119
+ * class MyReporter() {
1120
+ * constructor(runner) {
1121
+ * this.runner.linkPartialObjects(true)
1122
+ * .on(EVENT_SUITE_BEGIN, suite => {
1123
+ // this Suite may be the same object...
1124
+ * })
1125
+ * .on(EVENT_TEST_BEGIN, test => {
1126
+ * // ...as the `test.parent` property
1127
+ * });
1128
+ * }
1129
+ * }
1130
+ */
1131
+ Runner.prototype.linkPartialObjects = function (value) {
1132
+ return this;
1133
+ };
1134
+
1135
+ /*
1136
+ * Like {@link Runner#run}, but does not accept a callback and returns a `Promise` instead of a `Runner`.
1137
+ * This function cannot reject; an `unhandledRejection` event will bubble up to the `process` object instead.
1138
+ * @public
1139
+ * @memberof Runner
1140
+ * @param {Object} [opts] - Options for {@link Runner#run}
1141
+ * @returns {Promise<number>} Failure count
1142
+ */
1143
+ Runner.prototype.runAsync = async function runAsync(opts = {}) {
1144
+ return new Promise(resolve => {
1145
+ this.run(resolve, opts);
1146
+ });
1147
+ };
1148
+
849
1149
  /**
850
1150
  * Cleanly abort execution.
851
1151
  *
852
- * @api public
1152
+ * @memberof Runner
1153
+ * @public
853
1154
  * @return {Runner} Runner instance.
854
1155
  */
855
1156
  Runner.prototype.abort = function () {
856
- debug('aborting');
1157
+ debug('abort(): aborting');
857
1158
  this._abort = true;
858
1159
 
859
1160
  return this;
860
1161
  };
861
1162
 
862
1163
  /**
863
- * Filter suites based on `isOnly` logic.
1164
+ * Returns `true` if Mocha is running in parallel mode. For reporters.
864
1165
  *
865
- * @param {Array} suite
866
- * @returns {Boolean}
867
- * @api private
1166
+ * Subclasses should return an appropriate value.
1167
+ * @public
1168
+ * @returns {false}
868
1169
  */
869
- function filterOnly (suite) {
870
- if (suite._onlyTests.length) {
871
- // If the suite contains `only` tests, run those and ignore any nested suites.
872
- suite.tests = suite._onlyTests;
873
- suite.suites = [];
874
- } else {
875
- // Otherwise, do not run any of the tests in this suite.
876
- suite.tests = [];
877
- utils.forEach(suite._onlySuites, function (onlySuite) {
878
- // If there are other `only` tests/suites nested in the current `only` suite, then filter that `only` suite.
879
- // Otherwise, all of the tests on this `only` suite should be run, so don't filter it.
880
- if (hasOnly(onlySuite)) {
881
- filterOnly(onlySuite);
882
- }
883
- });
884
- // Run the `only` suites, as well as any other suites that have `only` tests/suites as descendants.
885
- suite.suites = filter(suite.suites, function (childSuite) {
886
- return indexOf(suite._onlySuites, childSuite) !== -1 || filterOnly(childSuite);
887
- });
888
- }
889
- // Keep the suite only if there is something to run
890
- return suite.tests.length || suite.suites.length;
891
- }
1170
+ Runner.prototype.isParallelMode = function isParallelMode() {
1171
+ return false;
1172
+ };
892
1173
 
893
1174
  /**
894
- * Determines whether a suite has an `only` test or suite as a descendant.
895
- *
896
- * @param {Array} suite
897
- * @returns {Boolean}
898
- * @api private
1175
+ * Configures an alternate reporter for worker processes to use. Subclasses
1176
+ * using worker processes should implement this.
1177
+ * @public
1178
+ * @param {string} path - Absolute path to alternate reporter for worker processes to use
1179
+ * @returns {Runner}
1180
+ * @throws When in serial mode
1181
+ * @chainable
1182
+ * @abstract
899
1183
  */
900
- function hasOnly (suite) {
901
- return suite._onlyTests.length || suite._onlySuites.length || some(suite.suites, hasOnly);
902
- }
1184
+ Runner.prototype.workerReporter = function () {
1185
+ throw createUnsupportedError('workerReporter() not supported in serial mode');
1186
+ };
903
1187
 
904
1188
  /**
905
1189
  * Filter leaks with the given globals flagged as `ok`.
906
1190
  *
907
- * @api private
1191
+ * @private
908
1192
  * @param {Array} ok
909
1193
  * @param {Array} globals
910
1194
  * @return {Array}
911
1195
  */
912
- function filterLeaks (ok, globals) {
913
- return filter(globals, function (key) {
1196
+ function filterLeaks(ok, globals) {
1197
+ return globals.filter(function (key) {
914
1198
  // Firefox and Chrome exposes iframes as index inside the window object
915
1199
  if (/^\d+/.test(key)) {
916
1200
  return false;
@@ -919,13 +1203,13 @@ function filterLeaks (ok, globals) {
919
1203
  // in firefox
920
1204
  // if runner runs in an iframe, this iframe's window.getInterface method
921
1205
  // not init at first it is assigned in some seconds
922
- if (global.navigator && (/^getInterface/).test(key)) {
1206
+ if (global.navigator && /^getInterface/.test(key)) {
923
1207
  return false;
924
1208
  }
925
1209
 
926
1210
  // an iframe could be approached by window[iframeIndex]
927
1211
  // in ie6,7,8 and opera, iframeIndex is enumerable, this could cause leak
928
- if (global.navigator && (/^\d+/).test(key)) {
1212
+ if (global.navigator && /^\d+/.test(key)) {
929
1213
  return false;
930
1214
  }
931
1215
 
@@ -934,7 +1218,7 @@ function filterLeaks (ok, globals) {
934
1218
  return false;
935
1219
  }
936
1220
 
937
- var matched = filter(ok, function (ok) {
1221
+ var matched = ok.filter(function (ok) {
938
1222
  if (~ok.indexOf('*')) {
939
1223
  return key.indexOf(ok.split('*')[0]) === 0;
940
1224
  }
@@ -945,24 +1229,39 @@ function filterLeaks (ok, globals) {
945
1229
  }
946
1230
 
947
1231
  /**
948
- * Array of globals dependent on the environment.
1232
+ * Check if argument is an instance of Error object or a duck-typed equivalent.
949
1233
  *
950
- * @return {Array}
951
- * @api private
1234
+ * @private
1235
+ * @param {Object} err - object to check
1236
+ * @param {string} err.message - error message
1237
+ * @returns {boolean}
952
1238
  */
953
- function extraGlobals () {
954
- if (typeof process === 'object' && typeof process.version === 'string') {
955
- var parts = process.version.split('.');
956
- var nodeVersion = utils.reduce(parts, function (a, v) {
957
- return a << 8 | v;
958
- });
1239
+ function isError(err) {
1240
+ return err instanceof Error || (err && typeof err.message === 'string');
1241
+ }
959
1242
 
960
- // 'errno' was renamed to process._errno in v0.9.11.
1243
+ /**
1244
+ *
1245
+ * Converts thrown non-extensible type into proper Error.
1246
+ *
1247
+ * @private
1248
+ * @param {*} thrown - Non-extensible type thrown by code
1249
+ * @return {Error}
1250
+ */
1251
+ function thrown2Error(err) {
1252
+ return new Error(
1253
+ `the ${utils.canonicalType(err)} ${stringify(
1254
+ err
1255
+ )} was thrown, throw an Error :)`
1256
+ );
1257
+ }
961
1258
 
962
- if (nodeVersion < 0x00090B) {
963
- return ['errno'];
964
- }
965
- }
1259
+ Runner.constants = constants;
966
1260
 
967
- return [];
968
- }
1261
+ /**
1262
+ * Node.js' `EventEmitter`
1263
+ * @external EventEmitter
1264
+ * @see {@link https://nodejs.org/api/events.html#events_class_eventemitter}
1265
+ */
1266
+
1267
+ module.exports = Runner;