mocha 7.1.2 → 7.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/runner.js CHANGED
@@ -26,6 +26,7 @@ var type = utils.type;
26
26
  var errors = require('./errors');
27
27
  var createInvalidExceptionError = errors.createInvalidExceptionError;
28
28
  var createUnsupportedError = errors.createUnsupportedError;
29
+ var createFatalError = errors.createFatalError;
29
30
 
30
31
  /**
31
32
  * Non-enumerable globals.
@@ -108,7 +109,19 @@ var constants = utils.defineConstants(
108
109
  /**
109
110
  * Emitted when {@link Test} execution has failed, but will retry
110
111
  */
111
- EVENT_TEST_RETRY: 'retry'
112
+ EVENT_TEST_RETRY: 'retry',
113
+ /**
114
+ * Initial state of Runner
115
+ */
116
+ STATE_IDLE: 'idle',
117
+ /**
118
+ * State set to this value when the Runner has started running
119
+ */
120
+ STATE_RUNNING: 'running',
121
+ /**
122
+ * State set to this value when the Runner has stopped
123
+ */
124
+ STATE_STOPPED: 'stopped'
112
125
  }
113
126
  );
114
127
 
@@ -120,19 +133,30 @@ module.exports = Runner;
120
133
  * @extends external:EventEmitter
121
134
  * @public
122
135
  * @class
123
- * @param {Suite} suite Root suite
124
- * @param {boolean} [delay] Whether or not to delay execution of root suite
125
- * until ready.
136
+ * @param {Suite} suite - Root suite
137
+ * @param {Object|boolean} [opts] - Options. If `boolean`, whether or not to delay execution of root suite until ready (for backwards compatibility).
138
+ * @param {boolean} [opts.delay] - Whether to delay execution of root suite until ready.
139
+ * @param {boolean} [opts.cleanReferencesAfterRun] - Whether to clean references to test fns and hooks when a suite is done.
126
140
  */
127
- function Runner(suite, delay) {
141
+ function Runner(suite, opts) {
142
+ if (opts === undefined) {
143
+ opts = {};
144
+ }
145
+ if (typeof opts === 'boolean') {
146
+ this._delay = opts;
147
+ opts = {};
148
+ } else {
149
+ this._delay = opts.delay;
150
+ }
128
151
  var self = this;
129
152
  this._globals = [];
130
153
  this._abort = false;
131
- this._delay = delay;
132
154
  this.suite = suite;
133
- this.started = false;
155
+ this._opts = opts;
156
+ this.state = constants.STATE_IDLE;
134
157
  this.total = suite.total();
135
158
  this.failures = 0;
159
+ this._eventListeners = [];
136
160
  this.on(constants.EVENT_TEST_END, function(test) {
137
161
  if (test.type === 'test' && test.retriedTest() && test.parent) {
138
162
  var idx =
@@ -147,6 +171,8 @@ function Runner(suite, delay) {
147
171
  this._defaultGrep = /.*/;
148
172
  this.grep(this._defaultGrep);
149
173
  this.globals(this.globalProps());
174
+
175
+ this.uncaught = this._uncaught.bind(this);
150
176
  }
151
177
 
152
178
  /**
@@ -162,6 +188,56 @@ Runner.immediately = global.setImmediate || process.nextTick;
162
188
  */
163
189
  inherits(Runner, EventEmitter);
164
190
 
191
+ /**
192
+ * Replacement for `target.on(eventName, listener)` that does bookkeeping to remove them when this runner instance is disposed.
193
+ * @param {EventEmitter} target - The `EventEmitter`
194
+ * @param {string} eventName - The event name
195
+ * @param {string} fn - Listener function
196
+ */
197
+ Runner.prototype._addEventListener = function(target, eventName, listener) {
198
+ target.on(eventName, listener);
199
+ this._eventListeners.push([target, eventName, listener]);
200
+ };
201
+
202
+ /**
203
+ * Replacement for `target.removeListener(eventName, listener)` that also updates the bookkeeping.
204
+ * @param {EventEmitter} target - The `EventEmitter`
205
+ * @param {string} eventName - The event anme
206
+ * @param {function} listener - Listener function
207
+ */
208
+ Runner.prototype._removeEventListener = function(target, eventName, listener) {
209
+ var eventListenerIndex = -1;
210
+ for (var i = 0; i < this._eventListeners.length; i++) {
211
+ var eventListenerDescriptor = this._eventListeners[i];
212
+ if (
213
+ eventListenerDescriptor[0] === target &&
214
+ eventListenerDescriptor[1] === eventName &&
215
+ eventListenerDescriptor[2] === listener
216
+ ) {
217
+ eventListenerIndex = i;
218
+ break;
219
+ }
220
+ }
221
+ if (eventListenerIndex !== -1) {
222
+ var removedListener = this._eventListeners.splice(eventListenerIndex, 1)[0];
223
+ removedListener[0].removeListener(removedListener[1], removedListener[2]);
224
+ }
225
+ };
226
+
227
+ /**
228
+ * Removes all event handlers set during a run on this instance.
229
+ * Remark: this does _not_ clean/dispose the tests or suites themselves.
230
+ */
231
+ Runner.prototype.dispose = function() {
232
+ this.removeAllListeners();
233
+ this._eventListeners.forEach(function(eventListenerDescriptor) {
234
+ eventListenerDescriptor[0].removeListener(
235
+ eventListenerDescriptor[1],
236
+ eventListenerDescriptor[2]
237
+ );
238
+ });
239
+ };
240
+
165
241
  /**
166
242
  * Run tests with full titles matching `re`. Updates runner.total
167
243
  * with number of tests matched.
@@ -173,7 +249,7 @@ inherits(Runner, EventEmitter);
173
249
  * @return {Runner} Runner instance.
174
250
  */
175
251
  Runner.prototype.grep = function(re, invert) {
176
- debug('grep %s', re);
252
+ debug('grep(): setting to %s', re);
177
253
  this._grep = re;
178
254
  this._invert = invert;
179
255
  this.total = this.grepTotal(this.suite);
@@ -238,7 +314,7 @@ Runner.prototype.globals = function(arr) {
238
314
  if (!arguments.length) {
239
315
  return this._globals;
240
316
  }
241
- debug('globals %j', arr);
317
+ debug('globals(): setting to %O', arr);
242
318
  this._globals = this._globals.concat(arr);
243
319
  return this;
244
320
  };
@@ -287,8 +363,18 @@ Runner.prototype.fail = function(test, err) {
287
363
  if (test.isPending()) {
288
364
  return;
289
365
  }
366
+ if (this.state === constants.STATE_STOPPED) {
367
+ if (err.code === errors.constants.MULTIPLE_DONE) {
368
+ throw err;
369
+ }
370
+ throw createFatalError(
371
+ 'Test failed after root suite execution completed!',
372
+ err
373
+ );
374
+ }
290
375
 
291
376
  ++this.failures;
377
+ debug('total number of failures: %d', this.failures);
292
378
  test.state = STATE_FAILED;
293
379
 
294
380
  if (!isError(err)) {
@@ -376,7 +462,7 @@ Runner.prototype.hook = function(name, fn) {
376
462
  self.emit(constants.EVENT_HOOK_BEGIN, hook);
377
463
 
378
464
  if (!hook.listeners('error').length) {
379
- hook.on('error', function(err) {
465
+ self._addEventListener(hook, 'error', function(err) {
380
466
  self.failHook(hook, err);
381
467
  });
382
468
  }
@@ -519,18 +605,10 @@ Runner.prototype.runTest = function(fn) {
519
605
  return;
520
606
  }
521
607
 
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
608
  if (this.asyncOnly) {
528
609
  test.asyncOnly = true;
529
610
  }
530
- test.on('error', function(err) {
531
- if (err instanceof Pending) {
532
- return;
533
- }
611
+ this._addEventListener(test, 'error', function(err) {
534
612
  self.fail(test, err);
535
613
  });
536
614
  if (this.allowUncaught) {
@@ -725,9 +803,10 @@ Runner.prototype.runSuite = function(suite, fn) {
725
803
  var self = this;
726
804
  var total = this.grepTotal(suite);
727
805
 
728
- debug('run suite %s', suite.fullTitle());
806
+ debug('runSuite(): running %s', suite.fullTitle());
729
807
 
730
808
  if (!total || (self.failures && suite._bail)) {
809
+ debug('runSuite(): bailing');
731
810
  return fn();
732
811
  }
733
812
 
@@ -793,22 +872,49 @@ Runner.prototype.runSuite = function(suite, fn) {
793
872
  /**
794
873
  * Handle uncaught exceptions within runner.
795
874
  *
796
- * @param {Error} err
875
+ * This function is bound to the instance as `Runner#uncaught` at instantiation
876
+ * time. It's intended to be listening on the `Process.uncaughtException` event.
877
+ * In order to not leak EE listeners, we need to ensure no more than a single
878
+ * `uncaughtException` listener exists per `Runner`. The only way to do
879
+ * this--because this function needs the context (and we don't have lambdas)--is
880
+ * to use `Function.prototype.bind`. We need strict equality to unregister and
881
+ * _only_ unregister the _one_ listener we set from the
882
+ * `Process.uncaughtException` event; would be poor form to just remove
883
+ * everything. See {@link Runner#run} for where the event listener is registered
884
+ * and unregistered.
885
+ * @param {Error} err - Some uncaught error
797
886
  * @private
798
887
  */
799
- Runner.prototype.uncaught = function(err) {
888
+ Runner.prototype._uncaught = function(err) {
889
+ // this is defensive to prevent future developers from mis-calling this function.
890
+ // it's more likely that it'd be called with the incorrect context--say, the global
891
+ // `process` object--than it would to be called with a context that is not a "subclass"
892
+ // of `Runner`.
893
+ if (!(this instanceof Runner)) {
894
+ throw createFatalError(
895
+ 'Runner#uncaught() called with invalid context',
896
+ this
897
+ );
898
+ }
800
899
  if (err instanceof Pending) {
900
+ debug('uncaught(): caught a Pending');
801
901
  return;
802
902
  }
803
903
  // browser does not exit script when throwing in global.onerror()
804
904
  if (this.allowUncaught && !process.browser) {
905
+ debug('uncaught(): bubbling exception due to --allow-uncaught');
906
+ throw err;
907
+ }
908
+
909
+ if (this.state === constants.STATE_STOPPED) {
910
+ debug('uncaught(): throwing after run has completed!');
805
911
  throw err;
806
912
  }
807
913
 
808
914
  if (err) {
809
- debug('uncaught exception %O', err);
915
+ debug('uncaught(): got truthy exception %O', err);
810
916
  } else {
811
- debug('uncaught undefined/falsy exception');
917
+ debug('uncaught(): undefined/falsy exception');
812
918
  err = createInvalidExceptionError(
813
919
  'Caught falsy/undefined exception which would otherwise be uncaught. No stack trace found; try a debugger',
814
920
  err
@@ -817,6 +923,7 @@ Runner.prototype.uncaught = function(err) {
817
923
 
818
924
  if (!isError(err)) {
819
925
  err = thrown2Error(err);
926
+ debug('uncaught(): converted "error" %o to Error', err);
820
927
  }
821
928
  err.uncaught = true;
822
929
 
@@ -824,12 +931,15 @@ Runner.prototype.uncaught = function(err) {
824
931
 
825
932
  if (!runnable) {
826
933
  runnable = new Runnable('Uncaught error outside test suite');
934
+ debug('uncaught(): no current Runnable; created a phony one');
827
935
  runnable.parent = this.suite;
828
936
 
829
- if (this.started) {
937
+ if (this.state === constants.STATE_RUNNING) {
938
+ debug('uncaught(): failing gracefully');
830
939
  this.fail(runnable, err);
831
940
  } else {
832
941
  // Can't recover from this failure
942
+ debug('uncaught(): test run has not yet started; unrecoverable');
833
943
  this.emit(constants.EVENT_RUN_BEGIN);
834
944
  this.fail(runnable, err);
835
945
  this.emit(constants.EVENT_RUN_END);
@@ -841,9 +951,11 @@ Runner.prototype.uncaught = function(err) {
841
951
  runnable.clearTimeout();
842
952
 
843
953
  if (runnable.isFailed()) {
954
+ debug('uncaught(): Runnable has already failed');
844
955
  // Ignore error if already failed
845
956
  return;
846
957
  } else if (runnable.isPending()) {
958
+ debug('uncaught(): pending Runnable wound up failing!');
847
959
  // report 'pending test' retrospectively as failed
848
960
  runnable.isPending = alwaysFalse;
849
961
  this.fail(runnable, err);
@@ -854,25 +966,15 @@ Runner.prototype.uncaught = function(err) {
854
966
  // we cannot recover gracefully if a Runnable has already passed
855
967
  // then fails asynchronously
856
968
  if (runnable.isPassed()) {
969
+ debug('uncaught(): Runnable has already passed; bailing gracefully');
857
970
  this.fail(runnable, err);
858
971
  this.abort();
859
972
  } else {
860
- debug(runnable);
973
+ debug('uncaught(): forcing Runnable to complete with Error');
861
974
  return runnable.callback(err);
862
975
  }
863
976
  };
864
977
 
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
978
  /**
877
979
  * Run the root suite and invoke `fn(failures)`
878
980
  * on completion.
@@ -888,51 +990,56 @@ Runner.prototype.run = function(fn) {
888
990
 
889
991
  fn = fn || function() {};
890
992
 
891
- function uncaught(err) {
892
- self.uncaught(err);
893
- }
894
-
895
993
  function start() {
994
+ debug('run(): starting');
896
995
  // If there is an `only` filter
897
996
  if (rootSuite.hasOnly()) {
898
997
  rootSuite.filterOnly();
998
+ debug('run(): filtered exclusive Runnables');
899
999
  }
900
- self.started = true;
1000
+ self.state = constants.STATE_RUNNING;
901
1001
  if (self._delay) {
902
1002
  self.emit(constants.EVENT_DELAY_END);
1003
+ debug('run(): "delay" ended');
903
1004
  }
1005
+ debug('run(): emitting %s', constants.EVENT_RUN_BEGIN);
904
1006
  self.emit(constants.EVENT_RUN_BEGIN);
1007
+ debug('run(): emitted %s', constants.EVENT_RUN_BEGIN);
905
1008
 
906
1009
  self.runSuite(rootSuite, function() {
907
- debug('finished running');
1010
+ debug(
1011
+ 'run(): root suite completed; emitting %s',
1012
+ constants.EVENT_RUN_END
1013
+ );
908
1014
  self.emit(constants.EVENT_RUN_END);
1015
+ debug('run(): emitted %s', constants.EVENT_RUN_END);
909
1016
  });
910
1017
  }
911
1018
 
912
- debug(constants.EVENT_RUN_BEGIN);
913
-
914
1019
  // references cleanup to avoid memory leaks
915
- this.on(constants.EVENT_SUITE_END, function(suite) {
916
- suite.cleanReferences();
917
- });
1020
+ if (this._opts.cleanReferencesAfterRun) {
1021
+ this.on(constants.EVENT_SUITE_END, function(suite) {
1022
+ suite.cleanReferences();
1023
+ });
1024
+ }
918
1025
 
919
1026
  // callback
920
1027
  this.on(constants.EVENT_RUN_END, function() {
1028
+ self.state = constants.STATE_STOPPED;
921
1029
  debug(constants.EVENT_RUN_END);
922
- process.removeListener('uncaughtException', uncaught);
923
- process.on('uncaughtException', self.uncaughtEnd);
1030
+ debug('run(): emitted %s', constants.EVENT_RUN_END);
924
1031
  fn(self.failures);
925
1032
  });
926
1033
 
927
- // uncaught exception
928
- process.removeListener('uncaughtException', self.uncaughtEnd);
929
- process.on('uncaughtException', uncaught);
1034
+ self._removeEventListener(process, 'uncaughtException', self.uncaught);
1035
+ self._addEventListener(process, 'uncaughtException', self.uncaught);
930
1036
 
931
1037
  if (this._delay) {
932
1038
  // for reporters, I guess.
933
1039
  // might be nice to debounce some dots while we wait.
934
1040
  this.emit(constants.EVENT_DELAY_BEGIN, rootSuite);
935
1041
  rootSuite.once(EVENT_ROOT_SUITE_RUN, start);
1042
+ debug('run(): waiting for green light due to --delay');
936
1043
  } else {
937
1044
  Runner.immediately(function() {
938
1045
  start();
@@ -950,7 +1057,7 @@ Runner.prototype.run = function(fn) {
950
1057
  * @return {Runner} Runner instance.
951
1058
  */
952
1059
  Runner.prototype.abort = function() {
953
- debug('aborting');
1060
+ debug('abort(): aborting');
954
1061
  this._abort = true;
955
1062
 
956
1063
  return this;
package/lib/suite.js CHANGED
@@ -61,20 +61,20 @@ function Suite(title, parentContext, isRoot) {
61
61
  this.ctx = new Context();
62
62
  this.suites = [];
63
63
  this.tests = [];
64
+ this.root = isRoot === true;
64
65
  this.pending = false;
66
+ this._retries = -1;
65
67
  this._beforeEach = [];
66
68
  this._beforeAll = [];
67
69
  this._afterEach = [];
68
70
  this._afterAll = [];
69
- this.root = isRoot === true;
70
71
  this._timeout = 2000;
71
72
  this._enableTimeouts = true;
72
73
  this._slow = 75;
73
74
  this._bail = false;
74
- this._retries = -1;
75
75
  this._onlyTests = [];
76
76
  this._onlySuites = [];
77
- this.delayed = false;
77
+ this.reset();
78
78
 
79
79
  this.on('newListener', function(event) {
80
80
  if (deprecatedEvents[event]) {
@@ -92,6 +92,22 @@ function Suite(title, parentContext, isRoot) {
92
92
  */
93
93
  inherits(Suite, EventEmitter);
94
94
 
95
+ /**
96
+ * Resets the state initially or for a next run.
97
+ */
98
+ Suite.prototype.reset = function() {
99
+ this.delayed = false;
100
+ function doReset(thingToReset) {
101
+ thingToReset.reset();
102
+ }
103
+ this.suites.forEach(doReset);
104
+ this.tests.forEach(doReset);
105
+ this._beforeEach.forEach(doReset);
106
+ this._afterEach.forEach(doReset);
107
+ this._beforeAll.forEach(doReset);
108
+ this._afterAll.forEach(doReset);
109
+ };
110
+
95
111
  /**
96
112
  * Return a clone of this `Suite`.
97
113
  *
@@ -511,6 +527,16 @@ Suite.prototype.getHooks = function getHooks(name) {
511
527
  return this['_' + name];
512
528
  };
513
529
 
530
+ /**
531
+ * cleans all references from this suite and all child suites.
532
+ */
533
+ Suite.prototype.dispose = function() {
534
+ this.suites.forEach(function(suite) {
535
+ suite.dispose();
536
+ });
537
+ this.cleanReferences();
538
+ };
539
+
514
540
  /**
515
541
  * Cleans up the references to all the deferred functions
516
542
  * (before/after/beforeEach/afterEach) and tests of a Suite.
package/lib/test.js CHANGED
@@ -26,9 +26,9 @@ function Test(title, fn) {
26
26
  'string'
27
27
  );
28
28
  }
29
- Runnable.call(this, title, fn);
30
- this.pending = !fn;
31
29
  this.type = 'test';
30
+ Runnable.call(this, title, fn);
31
+ this.reset();
32
32
  }
33
33
 
34
34
  /**
@@ -36,6 +36,15 @@ function Test(title, fn) {
36
36
  */
37
37
  utils.inherits(Test, Runnable);
38
38
 
39
+ /**
40
+ * Resets the state initially or for a next run.
41
+ */
42
+ Test.prototype.reset = function() {
43
+ Runnable.prototype.reset.call(this);
44
+ this.pending = !this.fn;
45
+ delete this.state;
46
+ };
47
+
39
48
  /**
40
49
  * Set or get retried test
41
50
  *
@@ -48,6 +57,15 @@ Test.prototype.retriedTest = function(n) {
48
57
  this._retriedTest = n;
49
58
  };
50
59
 
60
+ /**
61
+ * Add test to the list of tests marked `only`.
62
+ *
63
+ * @private
64
+ */
65
+ Test.prototype.markOnly = function() {
66
+ this.parent.appendOnlyTest(this);
67
+ };
68
+
51
69
  Test.prototype.clone = function() {
52
70
  var test = new Test(this.title, this.fn);
53
71
  test.timeout(this.timeout());
package/lib/utils.js CHANGED
@@ -63,8 +63,9 @@ exports.isString = function(obj) {
63
63
  exports.slug = function(str) {
64
64
  return str
65
65
  .toLowerCase()
66
- .replace(/ +/g, '-')
67
- .replace(/[^-\w]/g, '');
66
+ .replace(/\s+/g, '-')
67
+ .replace(/[^-\w]/g, '')
68
+ .replace(/-{2,}/g, '-');
68
69
  };
69
70
 
70
71
  /**
@@ -637,7 +638,7 @@ exports.stackTraceFilter = function() {
637
638
  var slash = path.sep;
638
639
  var cwd;
639
640
  if (is.node) {
640
- cwd = process.cwd() + slash;
641
+ cwd = exports.cwd() + slash;
641
642
  } else {
642
643
  cwd = (typeof location === 'undefined'
643
644
  ? window.location
@@ -821,3 +822,13 @@ exports.supportsEsModules = function() {
821
822
  }
822
823
  }
823
824
  };
825
+
826
+ /**
827
+ * Returns current working directory
828
+ *
829
+ * Wrapper around `process.cwd()` for isolation
830
+ * @private
831
+ */
832
+ exports.cwd = function cwd() {
833
+ return process.cwd();
834
+ };