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/runner.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  /**
4
4
  * Module dependencies.
5
+ * @private
5
6
  */
6
7
  var util = require('util');
7
8
  var EventEmitter = require('events').EventEmitter;
@@ -18,6 +19,7 @@ var HOOK_TYPE_BEFORE_ALL = Suite.constants.HOOK_TYPE_BEFORE_ALL;
18
19
  var EVENT_ROOT_SUITE_RUN = Suite.constants.EVENT_ROOT_SUITE_RUN;
19
20
  var STATE_FAILED = Runnable.constants.STATE_FAILED;
20
21
  var STATE_PASSED = Runnable.constants.STATE_PASSED;
22
+ var STATE_PENDING = Runnable.constants.STATE_PENDING;
21
23
  var dQuote = utils.dQuote;
22
24
  var sQuote = utils.sQuote;
23
25
  var stackFilter = utils.stackTraceFilter();
@@ -26,9 +28,11 @@ var type = utils.type;
26
28
  var errors = require('./errors');
27
29
  var createInvalidExceptionError = errors.createInvalidExceptionError;
28
30
  var createUnsupportedError = errors.createUnsupportedError;
31
+ var createFatalError = errors.createFatalError;
29
32
 
30
33
  /**
31
34
  * Non-enumerable globals.
35
+ * @private
32
36
  * @readonly
33
37
  */
34
38
  var globals = [
@@ -108,7 +112,19 @@ var constants = utils.defineConstants(
108
112
  /**
109
113
  * Emitted when {@link Test} execution has failed, but will retry
110
114
  */
111
- EVENT_TEST_RETRY: 'retry'
115
+ EVENT_TEST_RETRY: 'retry',
116
+ /**
117
+ * Initial state of Runner
118
+ */
119
+ STATE_IDLE: 'idle',
120
+ /**
121
+ * State set to this value when the Runner has started running
122
+ */
123
+ STATE_RUNNING: 'running',
124
+ /**
125
+ * State set to this value when the Runner has stopped
126
+ */
127
+ STATE_STOPPED: 'stopped'
112
128
  }
113
129
  );
114
130
 
@@ -120,19 +136,30 @@ module.exports = Runner;
120
136
  * @extends external:EventEmitter
121
137
  * @public
122
138
  * @class
123
- * @param {Suite} suite Root suite
124
- * @param {boolean} [delay] Whether or not to delay execution of root suite
125
- * until ready.
139
+ * @param {Suite} suite - Root suite
140
+ * @param {Object|boolean} [opts] - Options. If `boolean`, whether or not to delay execution of root suite until ready (for backwards compatibility).
141
+ * @param {boolean} [opts.delay] - Whether to delay execution of root suite until ready.
142
+ * @param {boolean} [opts.cleanReferencesAfterRun] - Whether to clean references to test fns and hooks when a suite is done.
126
143
  */
127
- function Runner(suite, delay) {
144
+ function Runner(suite, opts) {
145
+ if (opts === undefined) {
146
+ opts = {};
147
+ }
148
+ if (typeof opts === 'boolean') {
149
+ this._delay = opts;
150
+ opts = {};
151
+ } else {
152
+ this._delay = opts.delay;
153
+ }
128
154
  var self = this;
129
155
  this._globals = [];
130
156
  this._abort = false;
131
- this._delay = delay;
132
157
  this.suite = suite;
133
- this.started = false;
158
+ this._opts = opts;
159
+ this.state = constants.STATE_IDLE;
134
160
  this.total = suite.total();
135
161
  this.failures = 0;
162
+ this._eventListeners = [];
136
163
  this.on(constants.EVENT_TEST_END, function(test) {
137
164
  if (test.type === 'test' && test.retriedTest() && test.parent) {
138
165
  var idx =
@@ -147,6 +174,8 @@ function Runner(suite, delay) {
147
174
  this._defaultGrep = /.*/;
148
175
  this.grep(this._defaultGrep);
149
176
  this.globals(this.globalProps());
177
+
178
+ this.uncaught = this._uncaught.bind(this);
150
179
  }
151
180
 
152
181
  /**
@@ -162,6 +191,58 @@ Runner.immediately = global.setImmediate || process.nextTick;
162
191
  */
163
192
  inherits(Runner, EventEmitter);
164
193
 
194
+ /**
195
+ * Replacement for `target.on(eventName, listener)` that does bookkeeping to remove them when this runner instance is disposed.
196
+ * @param {EventEmitter} target - The `EventEmitter`
197
+ * @param {string} eventName - The event name
198
+ * @param {string} fn - Listener function
199
+ * @private
200
+ */
201
+ Runner.prototype._addEventListener = function(target, eventName, listener) {
202
+ target.on(eventName, listener);
203
+ this._eventListeners.push([target, eventName, listener]);
204
+ };
205
+
206
+ /**
207
+ * Replacement for `target.removeListener(eventName, listener)` that also updates the bookkeeping.
208
+ * @param {EventEmitter} target - The `EventEmitter`
209
+ * @param {string} eventName - The event anme
210
+ * @param {function} listener - Listener function
211
+ * @private
212
+ */
213
+ Runner.prototype._removeEventListener = function(target, eventName, listener) {
214
+ var eventListenerIndex = -1;
215
+ for (var i = 0; i < this._eventListeners.length; i++) {
216
+ var eventListenerDescriptor = this._eventListeners[i];
217
+ if (
218
+ eventListenerDescriptor[0] === target &&
219
+ eventListenerDescriptor[1] === eventName &&
220
+ eventListenerDescriptor[2] === listener
221
+ ) {
222
+ eventListenerIndex = i;
223
+ break;
224
+ }
225
+ }
226
+ if (eventListenerIndex !== -1) {
227
+ var removedListener = this._eventListeners.splice(eventListenerIndex, 1)[0];
228
+ removedListener[0].removeListener(removedListener[1], removedListener[2]);
229
+ }
230
+ };
231
+
232
+ /**
233
+ * Removes all event handlers set during a run on this instance.
234
+ * Remark: this does _not_ clean/dispose the tests or suites themselves.
235
+ */
236
+ Runner.prototype.dispose = function() {
237
+ this.removeAllListeners();
238
+ this._eventListeners.forEach(function(eventListenerDescriptor) {
239
+ eventListenerDescriptor[0].removeListener(
240
+ eventListenerDescriptor[1],
241
+ eventListenerDescriptor[2]
242
+ );
243
+ });
244
+ };
245
+
165
246
  /**
166
247
  * Run tests with full titles matching `re`. Updates runner.total
167
248
  * with number of tests matched.
@@ -173,7 +254,7 @@ inherits(Runner, EventEmitter);
173
254
  * @return {Runner} Runner instance.
174
255
  */
175
256
  Runner.prototype.grep = function(re, invert) {
176
- debug('grep %s', re);
257
+ debug('grep(): setting to %s', re);
177
258
  this._grep = re;
178
259
  this._invert = invert;
179
260
  this.total = this.grepTotal(this.suite);
@@ -238,7 +319,7 @@ Runner.prototype.globals = function(arr) {
238
319
  if (!arguments.length) {
239
320
  return this._globals;
240
321
  }
241
- debug('globals %j', arr);
322
+ debug('globals(): setting to %O', arr);
242
323
  this._globals = this._globals.concat(arr);
243
324
  return this;
244
325
  };
@@ -279,16 +360,41 @@ Runner.prototype.checkGlobals = function(test) {
279
360
  /**
280
361
  * Fail the given `test`.
281
362
  *
363
+ * If `test` is a hook, failures work in the following pattern:
364
+ * - If bail, run corresponding `after each` and `after` hooks,
365
+ * then exit
366
+ * - Failed `before` hook skips all tests in a suite and subsuites,
367
+ * but jumps to corresponding `after` hook
368
+ * - Failed `before each` hook skips remaining tests in a
369
+ * suite and jumps to corresponding `after each` hook,
370
+ * which is run only once
371
+ * - Failed `after` hook does not alter execution order
372
+ * - Failed `after each` hook skips remaining tests in a
373
+ * suite and subsuites, but executes other `after each`
374
+ * hooks
375
+ *
282
376
  * @private
283
- * @param {Test} test
377
+ * @param {Runnable} test
284
378
  * @param {Error} err
379
+ * @param {boolean} [force=false] - Whether to fail a pending test.
285
380
  */
286
- Runner.prototype.fail = function(test, err) {
287
- if (test.isPending()) {
381
+ Runner.prototype.fail = function(test, err, force) {
382
+ force = force === true;
383
+ if (test.isPending() && !force) {
288
384
  return;
289
385
  }
386
+ if (this.state === constants.STATE_STOPPED) {
387
+ if (err.code === errors.constants.MULTIPLE_DONE) {
388
+ throw err;
389
+ }
390
+ throw createFatalError(
391
+ 'Test failed after root suite execution completed!',
392
+ err
393
+ );
394
+ }
290
395
 
291
396
  ++this.failures;
397
+ debug('total number of failures: %d', this.failures);
292
398
  test.state = STATE_FAILED;
293
399
 
294
400
  if (!isError(err)) {
@@ -305,44 +411,6 @@ Runner.prototype.fail = function(test, err) {
305
411
  this.emit(constants.EVENT_TEST_FAIL, test, err);
306
412
  };
307
413
 
308
- /**
309
- * Fail the given `hook` with `err`.
310
- *
311
- * Hook failures work in the following pattern:
312
- * - If bail, run corresponding `after each` and `after` hooks,
313
- * then exit
314
- * - Failed `before` hook skips all tests in a suite and subsuites,
315
- * but jumps to corresponding `after` hook
316
- * - Failed `before each` hook skips remaining tests in a
317
- * suite and jumps to corresponding `after each` hook,
318
- * which is run only once
319
- * - Failed `after` hook does not alter execution order
320
- * - Failed `after each` hook skips remaining tests in a
321
- * suite and subsuites, but executes other `after each`
322
- * hooks
323
- *
324
- * @private
325
- * @param {Hook} hook
326
- * @param {Error} err
327
- */
328
- Runner.prototype.failHook = function(hook, err) {
329
- hook.originalTitle = hook.originalTitle || hook.title;
330
- if (hook.ctx && hook.ctx.currentTest) {
331
- hook.title =
332
- hook.originalTitle + ' for ' + dQuote(hook.ctx.currentTest.title);
333
- } else {
334
- var parentTitle;
335
- if (hook.parent.title) {
336
- parentTitle = hook.parent.title;
337
- } else {
338
- parentTitle = hook.parent.root ? '{root}' : '';
339
- }
340
- hook.title = hook.originalTitle + ' in ' + dQuote(parentTitle);
341
- }
342
-
343
- this.fail(hook, err);
344
- };
345
-
346
414
  /**
347
415
  * Run hook `name` callbacks and then invoke `fn()`.
348
416
  *
@@ -371,17 +439,19 @@ Runner.prototype.hook = function(name, fn) {
371
439
  hook.ctx.currentTest = self.test;
372
440
  }
373
441
 
442
+ setHookTitle(hook);
443
+
374
444
  hook.allowUncaught = self.allowUncaught;
375
445
 
376
446
  self.emit(constants.EVENT_HOOK_BEGIN, hook);
377
447
 
378
448
  if (!hook.listeners('error').length) {
379
- hook.on('error', function(err) {
380
- self.failHook(hook, err);
449
+ self._addEventListener(hook, 'error', function(err) {
450
+ self.fail(hook, err);
381
451
  });
382
452
  }
383
453
 
384
- hook.run(function(err) {
454
+ hook.run(function cbHookRun(err) {
385
455
  var testError = hook.error();
386
456
  if (testError) {
387
457
  self.fail(self.test, testError);
@@ -407,21 +477,39 @@ Runner.prototype.hook = function(name, fn) {
407
477
  suite.suites.forEach(function(suite) {
408
478
  suite.pending = true;
409
479
  });
480
+ hooks = [];
410
481
  } else {
411
482
  hook.pending = false;
412
483
  var errForbid = createUnsupportedError('`this.skip` forbidden');
413
- self.failHook(hook, errForbid);
484
+ self.fail(hook, errForbid);
414
485
  return fn(errForbid);
415
486
  }
416
487
  } else if (err) {
417
- self.failHook(hook, err);
488
+ self.fail(hook, err);
418
489
  // stop executing hooks, notify callee of hook err
419
490
  return fn(err);
420
491
  }
421
492
  self.emit(constants.EVENT_HOOK_END, hook);
422
493
  delete hook.ctx.currentTest;
494
+ setHookTitle(hook);
423
495
  next(++i);
424
496
  });
497
+
498
+ function setHookTitle(hook) {
499
+ hook.originalTitle = hook.originalTitle || hook.title;
500
+ if (hook.ctx && hook.ctx.currentTest) {
501
+ hook.title =
502
+ hook.originalTitle + ' for ' + dQuote(hook.ctx.currentTest.title);
503
+ } else {
504
+ var parentTitle;
505
+ if (hook.parent.title) {
506
+ parentTitle = hook.parent.title;
507
+ } else {
508
+ parentTitle = hook.parent.root ? '{root}' : '';
509
+ }
510
+ hook.title = hook.originalTitle + ' in ' + dQuote(parentTitle);
511
+ }
512
+ }
425
513
  }
426
514
 
427
515
  Runner.immediately(function() {
@@ -519,18 +607,10 @@ Runner.prototype.runTest = function(fn) {
519
607
  return;
520
608
  }
521
609
 
522
- var suite = this.parents().reverse()[0] || this.suite;
523
- if (this.forbidOnly && suite.hasOnly()) {
524
- fn(new Error('`.only` forbidden'));
525
- return;
526
- }
527
610
  if (this.asyncOnly) {
528
611
  test.asyncOnly = true;
529
612
  }
530
- test.on('error', function(err) {
531
- if (err instanceof Pending) {
532
- return;
533
- }
613
+ this._addEventListener(test, 'error', function(err) {
534
614
  self.fail(test, err);
535
615
  });
536
616
  if (this.allowUncaught) {
@@ -629,10 +709,9 @@ Runner.prototype.runTests = function(suite, fn) {
629
709
  // static skip, no hooks are executed
630
710
  if (test.isPending()) {
631
711
  if (self.forbidPending) {
632
- test.isPending = alwaysFalse;
633
- self.fail(test, new Error('Pending test forbidden'));
634
- delete test.isPending;
712
+ self.fail(test, new Error('Pending test forbidden'), true);
635
713
  } else {
714
+ test.state = STATE_PENDING;
636
715
  self.emit(constants.EVENT_TEST_PENDING, test);
637
716
  }
638
717
  self.emit(constants.EVENT_TEST_END, test);
@@ -645,10 +724,9 @@ Runner.prototype.runTests = function(suite, fn) {
645
724
  // conditional skip within beforeEach
646
725
  if (test.isPending()) {
647
726
  if (self.forbidPending) {
648
- test.isPending = alwaysFalse;
649
- self.fail(test, new Error('Pending test forbidden'));
650
- delete test.isPending;
727
+ self.fail(test, new Error('Pending test forbidden'), true);
651
728
  } else {
729
+ test.state = STATE_PENDING;
652
730
  self.emit(constants.EVENT_TEST_PENDING, test);
653
731
  }
654
732
  self.emit(constants.EVENT_TEST_END, test);
@@ -669,10 +747,9 @@ Runner.prototype.runTests = function(suite, fn) {
669
747
  // conditional skip within it
670
748
  if (test.pending) {
671
749
  if (self.forbidPending) {
672
- test.isPending = alwaysFalse;
673
- self.fail(test, new Error('Pending test forbidden'));
674
- delete test.isPending;
750
+ self.fail(test, new Error('Pending test forbidden'), true);
675
751
  } else {
752
+ test.state = STATE_PENDING;
676
753
  self.emit(constants.EVENT_TEST_PENDING, test);
677
754
  }
678
755
  self.emit(constants.EVENT_TEST_END, test);
@@ -709,10 +786,6 @@ Runner.prototype.runTests = function(suite, fn) {
709
786
  next();
710
787
  };
711
788
 
712
- function alwaysFalse() {
713
- return false;
714
- }
715
-
716
789
  /**
717
790
  * Run the given `suite` and invoke the callback `fn()` when complete.
718
791
  *
@@ -725,9 +798,10 @@ Runner.prototype.runSuite = function(suite, fn) {
725
798
  var self = this;
726
799
  var total = this.grepTotal(suite);
727
800
 
728
- debug('run suite %s', suite.fullTitle());
801
+ debug('runSuite(): running %s', suite.fullTitle());
729
802
 
730
803
  if (!total || (self.failures && suite._bail)) {
804
+ debug('runSuite(): bailing');
731
805
  return fn();
732
806
  }
733
807
 
@@ -793,22 +867,49 @@ Runner.prototype.runSuite = function(suite, fn) {
793
867
  /**
794
868
  * Handle uncaught exceptions within runner.
795
869
  *
796
- * @param {Error} err
870
+ * This function is bound to the instance as `Runner#uncaught` at instantiation
871
+ * time. It's intended to be listening on the `Process.uncaughtException` event.
872
+ * In order to not leak EE listeners, we need to ensure no more than a single
873
+ * `uncaughtException` listener exists per `Runner`. The only way to do
874
+ * this--because this function needs the context (and we don't have lambdas)--is
875
+ * to use `Function.prototype.bind`. We need strict equality to unregister and
876
+ * _only_ unregister the _one_ listener we set from the
877
+ * `Process.uncaughtException` event; would be poor form to just remove
878
+ * everything. See {@link Runner#run} for where the event listener is registered
879
+ * and unregistered.
880
+ * @param {Error} err - Some uncaught error
797
881
  * @private
798
882
  */
799
- Runner.prototype.uncaught = function(err) {
883
+ Runner.prototype._uncaught = function(err) {
884
+ // this is defensive to prevent future developers from mis-calling this function.
885
+ // it's more likely that it'd be called with the incorrect context--say, the global
886
+ // `process` object--than it would to be called with a context that is not a "subclass"
887
+ // of `Runner`.
888
+ if (!(this instanceof Runner)) {
889
+ throw createFatalError(
890
+ 'Runner#uncaught() called with invalid context',
891
+ this
892
+ );
893
+ }
800
894
  if (err instanceof Pending) {
895
+ debug('uncaught(): caught a Pending');
801
896
  return;
802
897
  }
803
898
  // browser does not exit script when throwing in global.onerror()
804
- if (this.allowUncaught && !process.browser) {
899
+ if (this.allowUncaught && !utils.isBrowser()) {
900
+ debug('uncaught(): bubbling exception due to --allow-uncaught');
901
+ throw err;
902
+ }
903
+
904
+ if (this.state === constants.STATE_STOPPED) {
905
+ debug('uncaught(): throwing after run has completed!');
805
906
  throw err;
806
907
  }
807
908
 
808
909
  if (err) {
809
- debug('uncaught exception %O', err);
910
+ debug('uncaught(): got truthy exception %O', err);
810
911
  } else {
811
- debug('uncaught undefined/falsy exception');
912
+ debug('uncaught(): undefined/falsy exception');
812
913
  err = createInvalidExceptionError(
813
914
  'Caught falsy/undefined exception which would otherwise be uncaught. No stack trace found; try a debugger',
814
915
  err
@@ -817,6 +918,7 @@ Runner.prototype.uncaught = function(err) {
817
918
 
818
919
  if (!isError(err)) {
819
920
  err = thrown2Error(err);
921
+ debug('uncaught(): converted "error" %o to Error', err);
820
922
  }
821
923
  err.uncaught = true;
822
924
 
@@ -824,12 +926,15 @@ Runner.prototype.uncaught = function(err) {
824
926
 
825
927
  if (!runnable) {
826
928
  runnable = new Runnable('Uncaught error outside test suite');
929
+ debug('uncaught(): no current Runnable; created a phony one');
827
930
  runnable.parent = this.suite;
828
931
 
829
- if (this.started) {
932
+ if (this.state === constants.STATE_RUNNING) {
933
+ debug('uncaught(): failing gracefully');
830
934
  this.fail(runnable, err);
831
935
  } else {
832
936
  // Can't recover from this failure
937
+ debug('uncaught(): test run has not yet started; unrecoverable');
833
938
  this.emit(constants.EVENT_RUN_BEGIN);
834
939
  this.fail(runnable, err);
835
940
  this.emit(constants.EVENT_RUN_END);
@@ -841,98 +946,94 @@ Runner.prototype.uncaught = function(err) {
841
946
  runnable.clearTimeout();
842
947
 
843
948
  if (runnable.isFailed()) {
949
+ debug('uncaught(): Runnable has already failed');
844
950
  // Ignore error if already failed
845
951
  return;
846
952
  } else if (runnable.isPending()) {
953
+ debug('uncaught(): pending Runnable wound up failing!');
847
954
  // report 'pending test' retrospectively as failed
848
- runnable.isPending = alwaysFalse;
849
- this.fail(runnable, err);
850
- delete runnable.isPending;
955
+ this.fail(runnable, err, true);
851
956
  return;
852
957
  }
853
958
 
854
959
  // we cannot recover gracefully if a Runnable has already passed
855
960
  // then fails asynchronously
856
961
  if (runnable.isPassed()) {
962
+ debug('uncaught(): Runnable has already passed; bailing gracefully');
857
963
  this.fail(runnable, err);
858
964
  this.abort();
859
965
  } else {
860
- debug(runnable);
966
+ debug('uncaught(): forcing Runnable to complete with Error');
861
967
  return runnable.callback(err);
862
968
  }
863
969
  };
864
970
 
865
- /**
866
- * Handle uncaught exceptions after runner's end event.
867
- *
868
- * @param {Error} err
869
- * @private
870
- */
871
- Runner.prototype.uncaughtEnd = function uncaughtEnd(err) {
872
- if (err instanceof Pending) return;
873
- throw err;
874
- };
875
-
876
971
  /**
877
972
  * Run the root suite and invoke `fn(failures)`
878
973
  * on completion.
879
974
  *
880
975
  * @public
881
976
  * @memberof Runner
882
- * @param {Function} fn
977
+ * @param {Function} fn - Callback when finished
978
+ * @param {{files: string[], options: Options}} [opts] - For subclasses
883
979
  * @return {Runner} Runner instance.
884
980
  */
885
- Runner.prototype.run = function(fn) {
981
+ Runner.prototype.run = function(fn, opts) {
886
982
  var self = this;
887
983
  var rootSuite = this.suite;
888
984
 
889
985
  fn = fn || function() {};
890
986
 
891
- function uncaught(err) {
892
- self.uncaught(err);
893
- }
894
-
895
987
  function start() {
988
+ debug('run(): starting');
896
989
  // If there is an `only` filter
897
990
  if (rootSuite.hasOnly()) {
898
991
  rootSuite.filterOnly();
992
+ debug('run(): filtered exclusive Runnables');
899
993
  }
900
- self.started = true;
994
+ self.state = constants.STATE_RUNNING;
901
995
  if (self._delay) {
902
996
  self.emit(constants.EVENT_DELAY_END);
997
+ debug('run(): "delay" ended');
903
998
  }
999
+ debug('run(): emitting %s', constants.EVENT_RUN_BEGIN);
904
1000
  self.emit(constants.EVENT_RUN_BEGIN);
1001
+ debug('run(): emitted %s', constants.EVENT_RUN_BEGIN);
905
1002
 
906
1003
  self.runSuite(rootSuite, function() {
907
- debug('finished running');
1004
+ debug(
1005
+ 'run(): root suite completed; emitting %s',
1006
+ constants.EVENT_RUN_END
1007
+ );
908
1008
  self.emit(constants.EVENT_RUN_END);
1009
+ debug('run(): emitted %s', constants.EVENT_RUN_END);
909
1010
  });
910
1011
  }
911
1012
 
912
- debug(constants.EVENT_RUN_BEGIN);
913
-
914
1013
  // references cleanup to avoid memory leaks
915
- this.on(constants.EVENT_SUITE_END, function(suite) {
916
- suite.cleanReferences();
917
- });
1014
+ if (this._opts.cleanReferencesAfterRun) {
1015
+ this.on(constants.EVENT_SUITE_END, function(suite) {
1016
+ suite.cleanReferences();
1017
+ });
1018
+ }
918
1019
 
919
1020
  // callback
920
1021
  this.on(constants.EVENT_RUN_END, function() {
1022
+ self.state = constants.STATE_STOPPED;
921
1023
  debug(constants.EVENT_RUN_END);
922
- process.removeListener('uncaughtException', uncaught);
923
- process.on('uncaughtException', self.uncaughtEnd);
1024
+ debug('run(): emitted %s', constants.EVENT_RUN_END);
924
1025
  fn(self.failures);
925
1026
  });
926
1027
 
927
- // uncaught exception
928
- process.removeListener('uncaughtException', self.uncaughtEnd);
929
- process.on('uncaughtException', uncaught);
1028
+ self._removeEventListener(process, 'uncaughtException', self.uncaught);
1029
+ self._addEventListener(process, 'uncaughtException', self.uncaught);
930
1030
 
931
1031
  if (this._delay) {
932
1032
  // for reporters, I guess.
933
1033
  // might be nice to debounce some dots while we wait.
934
1034
  this.emit(constants.EVENT_DELAY_BEGIN, rootSuite);
935
1035
  rootSuite.once(EVENT_ROOT_SUITE_RUN, start);
1036
+ debug('run(): waiting for green light due to --delay');
936
1037
  } else {
937
1038
  Runner.immediately(function() {
938
1039
  start();
@@ -950,7 +1051,7 @@ Runner.prototype.run = function(fn) {
950
1051
  * @return {Runner} Runner instance.
951
1052
  */
952
1053
  Runner.prototype.abort = function() {
953
- debug('aborting');
1054
+ debug('abort(): aborting');
954
1055
  this._abort = true;
955
1056
 
956
1057
  return this;