mocha 8.1.1 → 8.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/runner.js CHANGED
@@ -8,7 +8,6 @@ var util = require('util');
8
8
  var EventEmitter = require('events').EventEmitter;
9
9
  var Pending = require('./pending');
10
10
  var utils = require('./utils');
11
- var inherits = utils.inherits;
12
11
  var debug = require('debug')('mocha:runner');
13
12
  var Runnable = require('./runnable');
14
13
  var Suite = require('./suite');
@@ -24,11 +23,14 @@ var dQuote = utils.dQuote;
24
23
  var sQuote = utils.sQuote;
25
24
  var stackFilter = utils.stackTraceFilter();
26
25
  var stringify = utils.stringify;
27
- var type = utils.type;
28
- var errors = require('./errors');
29
- var createInvalidExceptionError = errors.createInvalidExceptionError;
30
- var createUnsupportedError = errors.createUnsupportedError;
31
- var createFatalError = errors.createFatalError;
26
+
27
+ const {
28
+ createInvalidExceptionError,
29
+ createUnsupportedError,
30
+ createFatalError,
31
+ isMochaError,
32
+ constants: errorConstants
33
+ } = require('./errors');
32
34
 
33
35
  /**
34
36
  * Non-enumerable globals.
@@ -128,54 +130,82 @@ var constants = utils.defineConstants(
128
130
  }
129
131
  );
130
132
 
131
- module.exports = Runner;
132
-
133
- /**
134
- * Initialize a `Runner` at the Root {@link Suite}, which represents a hierarchy of {@link Suite|Suites} and {@link Test|Tests}.
135
- *
136
- * @extends external:EventEmitter
137
- * @public
138
- * @class
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.
143
- */
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
- }
154
- var self = this;
155
- this._globals = [];
156
- this._abort = false;
157
- this.suite = suite;
158
- this._opts = opts;
159
- this.state = constants.STATE_IDLE;
160
- this.total = suite.total();
161
- this.failures = 0;
162
- this._eventListeners = [];
163
- this.on(constants.EVENT_TEST_END, function(test) {
164
- if (test.type === 'test' && test.retriedTest() && test.parent) {
165
- var idx =
166
- test.parent.tests && test.parent.tests.indexOf(test.retriedTest());
167
- if (idx > -1) test.parent.tests[idx] = test;
133
+ class Runner extends EventEmitter {
134
+ /**
135
+ * Initialize a `Runner` at the Root {@link Suite}, which represents a hierarchy of {@link Suite|Suites} and {@link Test|Tests}.
136
+ *
137
+ * @extends external:EventEmitter
138
+ * @public
139
+ * @class
140
+ * @param {Suite} suite - Root suite
141
+ * @param {Object|boolean} [opts] - Options. If `boolean`, whether or not to delay execution of root suite until ready (for backwards compatibility).
142
+ * @param {boolean} [opts.delay] - Whether to delay execution of root suite until ready.
143
+ * @param {boolean} [opts.cleanReferencesAfterRun] - Whether to clean references to test fns and hooks when a suite is done.
144
+ */
145
+ constructor(suite, opts) {
146
+ super();
147
+ if (opts === undefined) {
148
+ opts = {};
168
149
  }
169
- self.checkGlobals(test);
170
- });
171
- this.on(constants.EVENT_HOOK_END, function(hook) {
172
- self.checkGlobals(hook);
173
- });
174
- this._defaultGrep = /.*/;
175
- this.grep(this._defaultGrep);
176
- this.globals(this.globalProps());
177
-
178
- this.uncaught = this._uncaught.bind(this);
150
+ if (typeof opts === 'boolean') {
151
+ // TODO: deprecate this
152
+ this._delay = opts;
153
+ opts = {};
154
+ } else {
155
+ this._delay = opts.delay;
156
+ }
157
+ var self = this;
158
+ this._globals = [];
159
+ this._abort = false;
160
+ this.suite = suite;
161
+ this._opts = opts;
162
+ this.state = constants.STATE_IDLE;
163
+ this.total = suite.total();
164
+ this.failures = 0;
165
+ /**
166
+ * @type {Map<EventEmitter,Map<string,Set<EventListener>>>}
167
+ */
168
+ this._eventListeners = new Map();
169
+ this.on(constants.EVENT_TEST_END, function(test) {
170
+ if (test.type === 'test' && test.retriedTest() && test.parent) {
171
+ var idx =
172
+ test.parent.tests && test.parent.tests.indexOf(test.retriedTest());
173
+ if (idx > -1) test.parent.tests[idx] = test;
174
+ }
175
+ self.checkGlobals(test);
176
+ });
177
+ this.on(constants.EVENT_HOOK_END, function(hook) {
178
+ self.checkGlobals(hook);
179
+ });
180
+ this._defaultGrep = /.*/;
181
+ this.grep(this._defaultGrep);
182
+ this.globals(this.globalProps());
183
+
184
+ this.uncaught = this._uncaught.bind(this);
185
+ this.unhandled = (reason, promise) => {
186
+ if (isMochaError(reason)) {
187
+ debug(
188
+ 'trapped unhandled rejection coming out of Mocha; forwarding to uncaught handler:',
189
+ reason
190
+ );
191
+ this.uncaught(reason);
192
+ } else {
193
+ debug(
194
+ 'trapped unhandled rejection from (probably) user code; re-emitting on process'
195
+ );
196
+ this._removeEventListener(
197
+ process,
198
+ 'unhandledRejection',
199
+ this.unhandled
200
+ );
201
+ try {
202
+ process.emit('unhandledRejection', reason, promise);
203
+ } finally {
204
+ this._addEventListener(process, 'unhandledRejection', this.unhandled);
205
+ }
206
+ }
207
+ };
208
+ }
179
209
  }
180
210
 
181
211
  /**
@@ -186,11 +216,6 @@ function Runner(suite, opts) {
186
216
  */
187
217
  Runner.immediately = global.setImmediate || process.nextTick;
188
218
 
189
- /**
190
- * Inherit from `EventEmitter.prototype`.
191
- */
192
- inherits(Runner, EventEmitter);
193
-
194
219
  /**
195
220
  * Replacement for `target.on(eventName, listener)` that does bookkeeping to remove them when this runner instance is disposed.
196
221
  * @param {EventEmitter} target - The `EventEmitter`
@@ -199,33 +224,62 @@ inherits(Runner, EventEmitter);
199
224
  * @private
200
225
  */
201
226
  Runner.prototype._addEventListener = function(target, eventName, listener) {
227
+ debug(
228
+ '_addEventListener(): adding for event %s; %d current listeners',
229
+ eventName,
230
+ target.listenerCount(eventName)
231
+ );
232
+ /* istanbul ignore next */
233
+ if (
234
+ this._eventListeners.has(target) &&
235
+ this._eventListeners.get(target).has(eventName) &&
236
+ this._eventListeners
237
+ .get(target)
238
+ .get(eventName)
239
+ .has(listener)
240
+ ) {
241
+ debug(
242
+ 'warning: tried to attach duplicate event listener for %s',
243
+ eventName
244
+ );
245
+ return;
246
+ }
202
247
  target.on(eventName, listener);
203
- this._eventListeners.push([target, eventName, listener]);
248
+ const targetListeners = this._eventListeners.has(target)
249
+ ? this._eventListeners.get(target)
250
+ : new Map();
251
+ const targetEventListeners = targetListeners.has(eventName)
252
+ ? targetListeners.get(eventName)
253
+ : new Set();
254
+ targetEventListeners.add(listener);
255
+ targetListeners.set(eventName, targetEventListeners);
256
+ this._eventListeners.set(target, targetListeners);
204
257
  };
205
258
 
206
259
  /**
207
260
  * Replacement for `target.removeListener(eventName, listener)` that also updates the bookkeeping.
208
261
  * @param {EventEmitter} target - The `EventEmitter`
209
- * @param {string} eventName - The event anme
262
+ * @param {string} eventName - The event name
210
263
  * @param {function} listener - Listener function
211
264
  * @private
212
265
  */
213
266
  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;
267
+ target.removeListener(eventName, listener);
268
+
269
+ if (this._eventListeners.has(target)) {
270
+ const targetListeners = this._eventListeners.get(target);
271
+ if (targetListeners.has(eventName)) {
272
+ const targetEventListeners = targetListeners.get(eventName);
273
+ targetEventListeners.delete(listener);
274
+ if (!targetEventListeners.size) {
275
+ targetListeners.delete(eventName);
276
+ }
224
277
  }
225
- }
226
- if (eventListenerIndex !== -1) {
227
- var removedListener = this._eventListeners.splice(eventListenerIndex, 1)[0];
228
- removedListener[0].removeListener(removedListener[1], removedListener[2]);
278
+ if (!targetListeners.size) {
279
+ this._eventListeners.delete(target);
280
+ }
281
+ } else {
282
+ debug('trying to remove listener for untracked object %s', target);
229
283
  }
230
284
  };
231
285
 
@@ -235,12 +289,14 @@ Runner.prototype._removeEventListener = function(target, eventName, listener) {
235
289
  */
236
290
  Runner.prototype.dispose = function() {
237
291
  this.removeAllListeners();
238
- this._eventListeners.forEach(function(eventListenerDescriptor) {
239
- eventListenerDescriptor[0].removeListener(
240
- eventListenerDescriptor[1],
241
- eventListenerDescriptor[2]
242
- );
292
+ this._eventListeners.forEach((targetListeners, target) => {
293
+ targetListeners.forEach((targetEventListeners, eventName) => {
294
+ targetEventListeners.forEach(listener => {
295
+ target.removeListener(eventName, listener);
296
+ });
297
+ });
243
298
  });
299
+ this._eventListeners.clear();
244
300
  };
245
301
 
246
302
  /**
@@ -384,7 +440,7 @@ Runner.prototype.fail = function(test, err, force) {
384
440
  return;
385
441
  }
386
442
  if (this.state === constants.STATE_STOPPED) {
387
- if (err.code === errors.constants.MULTIPLE_DONE) {
443
+ if (err.code === errorConstants.MULTIPLE_DONE) {
388
444
  throw err;
389
445
  }
390
446
  throw createFatalError(
@@ -976,73 +1032,117 @@ Runner.prototype._uncaught = function(err) {
976
1032
  * @memberof Runner
977
1033
  * @param {Function} fn - Callback when finished
978
1034
  * @param {{files: string[], options: Options}} [opts] - For subclasses
979
- * @return {Runner} Runner instance.
1035
+ * @returns {Runner} Runner instance.
980
1036
  */
981
- Runner.prototype.run = function(fn, opts) {
982
- var self = this;
1037
+ Runner.prototype.run = function(fn, opts = {}) {
983
1038
  var rootSuite = this.suite;
1039
+ var options = opts.options || {};
984
1040
 
1041
+ debug('run(): got options: %O', options);
985
1042
  fn = fn || function() {};
986
1043
 
987
- function start() {
1044
+ const end = () => {
1045
+ debug('run(): root suite completed; emitting %s', constants.EVENT_RUN_END);
1046
+ this.emit(constants.EVENT_RUN_END);
1047
+ };
1048
+
1049
+ const begin = () => {
1050
+ debug('run(): emitting %s', constants.EVENT_RUN_BEGIN);
1051
+ this.emit(constants.EVENT_RUN_BEGIN);
1052
+ debug('run(): emitted %s', constants.EVENT_RUN_BEGIN);
1053
+
1054
+ this.runSuite(rootSuite, end);
1055
+ };
1056
+
1057
+ const prepare = () => {
988
1058
  debug('run(): starting');
989
1059
  // If there is an `only` filter
990
1060
  if (rootSuite.hasOnly()) {
991
1061
  rootSuite.filterOnly();
992
1062
  debug('run(): filtered exclusive Runnables');
993
1063
  }
994
- self.state = constants.STATE_RUNNING;
995
- if (self._delay) {
996
- self.emit(constants.EVENT_DELAY_END);
1064
+ this.state = constants.STATE_RUNNING;
1065
+ if (this._delay) {
1066
+ this.emit(constants.EVENT_DELAY_END);
997
1067
  debug('run(): "delay" ended');
998
1068
  }
999
- debug('run(): emitting %s', constants.EVENT_RUN_BEGIN);
1000
- self.emit(constants.EVENT_RUN_BEGIN);
1001
- debug('run(): emitted %s', constants.EVENT_RUN_BEGIN);
1002
1069
 
1003
- self.runSuite(rootSuite, function() {
1004
- debug(
1005
- 'run(): root suite completed; emitting %s',
1006
- constants.EVENT_RUN_END
1007
- );
1008
- self.emit(constants.EVENT_RUN_END);
1009
- debug('run(): emitted %s', constants.EVENT_RUN_END);
1010
- });
1011
- }
1070
+ return begin();
1071
+ };
1012
1072
 
1013
1073
  // references cleanup to avoid memory leaks
1014
1074
  if (this._opts.cleanReferencesAfterRun) {
1015
- this.on(constants.EVENT_SUITE_END, function(suite) {
1075
+ this.on(constants.EVENT_SUITE_END, suite => {
1016
1076
  suite.cleanReferences();
1017
1077
  });
1018
1078
  }
1019
1079
 
1020
1080
  // callback
1021
1081
  this.on(constants.EVENT_RUN_END, function() {
1022
- self.state = constants.STATE_STOPPED;
1023
- debug(constants.EVENT_RUN_END);
1082
+ this.state = constants.STATE_STOPPED;
1024
1083
  debug('run(): emitted %s', constants.EVENT_RUN_END);
1025
- fn(self.failures);
1084
+ fn(this.failures);
1026
1085
  });
1027
1086
 
1028
- self._removeEventListener(process, 'uncaughtException', self.uncaught);
1029
- self._addEventListener(process, 'uncaughtException', self.uncaught);
1087
+ this._removeEventListener(process, 'uncaughtException', this.uncaught);
1088
+ this._removeEventListener(process, 'unhandledRejection', this.unhandled);
1089
+ this._addEventListener(process, 'uncaughtException', this.uncaught);
1090
+ this._addEventListener(process, 'unhandledRejection', this.unhandled);
1030
1091
 
1031
1092
  if (this._delay) {
1032
1093
  // for reporters, I guess.
1033
1094
  // might be nice to debounce some dots while we wait.
1034
1095
  this.emit(constants.EVENT_DELAY_BEGIN, rootSuite);
1035
- rootSuite.once(EVENT_ROOT_SUITE_RUN, start);
1096
+ rootSuite.once(EVENT_ROOT_SUITE_RUN, prepare);
1036
1097
  debug('run(): waiting for green light due to --delay');
1037
1098
  } else {
1038
- Runner.immediately(function() {
1039
- start();
1040
- });
1099
+ Runner.immediately(prepare);
1041
1100
  }
1042
1101
 
1043
1102
  return this;
1044
1103
  };
1045
1104
 
1105
+ /**
1106
+ * Toggle partial object linking behavior; used for building object references from
1107
+ * unique ID's. Does nothing in serial mode, because the object references already exist.
1108
+ * Subclasses can implement this (e.g., `ParallelBufferedRunner`)
1109
+ * @abstract
1110
+ * @param {boolean} [value] - If `true`, enable partial object linking, otherwise disable
1111
+ * @returns {Runner}
1112
+ * @chainable
1113
+ * @public
1114
+ * @example
1115
+ * // this reporter needs proper object references when run in parallel mode
1116
+ * class MyReporter() {
1117
+ * constructor(runner) {
1118
+ * this.runner.linkPartialObjects(true)
1119
+ * .on(EVENT_SUITE_BEGIN, suite => {
1120
+ // this Suite may be the same object...
1121
+ * })
1122
+ * .on(EVENT_TEST_BEGIN, test => {
1123
+ * // ...as the `test.parent` property
1124
+ * });
1125
+ * }
1126
+ * }
1127
+ */
1128
+ Runner.prototype.linkPartialObjects = function(value) {
1129
+ return this;
1130
+ };
1131
+
1132
+ /*
1133
+ * Like {@link Runner#run}, but does not accept a callback and returns a `Promise` instead of a `Runner`.
1134
+ * This function cannot reject; an `unhandledRejection` event will bubble up to the `process` object instead.
1135
+ * @public
1136
+ * @memberof Runner
1137
+ * @param {Object} [opts] - Options for {@link Runner#run}
1138
+ * @returns {Promise<number>} Failure count
1139
+ */
1140
+ Runner.prototype.runAsync = async function runAsync(opts = {}) {
1141
+ return new Promise(resolve => {
1142
+ this.run(resolve, opts);
1143
+ });
1144
+ };
1145
+
1046
1146
  /**
1047
1147
  * Cleanly abort execution.
1048
1148
  *
@@ -1057,6 +1157,31 @@ Runner.prototype.abort = function() {
1057
1157
  return this;
1058
1158
  };
1059
1159
 
1160
+ /**
1161
+ * Returns `true` if Mocha is running in parallel mode. For reporters.
1162
+ *
1163
+ * Subclasses should return an appropriate value.
1164
+ * @public
1165
+ * @returns {false}
1166
+ */
1167
+ Runner.prototype.isParallelMode = function isParallelMode() {
1168
+ return false;
1169
+ };
1170
+
1171
+ /**
1172
+ * Configures an alternate reporter for worker processes to use. Subclasses
1173
+ * using worker processes should implement this.
1174
+ * @public
1175
+ * @param {string} path - Absolute path to alternate reporter for worker processes to use
1176
+ * @returns {Runner}
1177
+ * @throws When in serial mode
1178
+ * @chainable
1179
+ * @abstract
1180
+ */
1181
+ Runner.prototype.workerReporter = function() {
1182
+ throw createUnsupportedError('workerReporter() not supported in serial mode');
1183
+ };
1184
+
1060
1185
  /**
1061
1186
  * Filter leaks with the given globals flagged as `ok`.
1062
1187
  *
@@ -1122,7 +1247,9 @@ function isError(err) {
1122
1247
  */
1123
1248
  function thrown2Error(err) {
1124
1249
  return new Error(
1125
- 'the ' + type(err) + ' ' + stringify(err) + ' was thrown, throw an Error :)'
1250
+ `the ${utils.canonicalType(err)} ${stringify(
1251
+ err
1252
+ )} was thrown, throw an Error :)`
1126
1253
  );
1127
1254
  }
1128
1255
 
@@ -1133,3 +1260,5 @@ Runner.constants = constants;
1133
1260
  * @external EventEmitter
1134
1261
  * @see {@link https://nodejs.org/api/events.html#events_class_eventemitter}
1135
1262
  */
1263
+
1264
+ module.exports = Runner;
package/lib/suite.js CHANGED
@@ -4,14 +4,23 @@
4
4
  * Module dependencies.
5
5
  * @private
6
6
  */
7
- var EventEmitter = require('events').EventEmitter;
8
- var Hook = require('./hook');
9
- var utils = require('./utils');
10
- var inherits = utils.inherits;
11
- var debug = require('debug')('mocha:suite');
12
- var milliseconds = require('ms');
13
- var errors = require('./errors');
14
- var createInvalidArgumentTypeError = errors.createInvalidArgumentTypeError;
7
+ const {EventEmitter} = require('events');
8
+ const Hook = require('./hook');
9
+ var {
10
+ assignNewMochaID,
11
+ clamp,
12
+ constants: utilsConstants,
13
+ createMap,
14
+ defineConstants,
15
+ getMochaID,
16
+ inherits,
17
+ isString
18
+ } = require('./utils');
19
+ const debug = require('debug')('mocha:suite');
20
+ const milliseconds = require('ms');
21
+ const errors = require('./errors');
22
+
23
+ const {MOCHA_ID_PROP_NAME} = utilsConstants;
15
24
 
16
25
  /**
17
26
  * Expose `Suite`.
@@ -47,8 +56,8 @@ Suite.create = function(parent, title) {
47
56
  * @param {boolean} [isRoot=false] - Whether this is the root suite.
48
57
  */
49
58
  function Suite(title, parentContext, isRoot) {
50
- if (!utils.isString(title)) {
51
- throw createInvalidArgumentTypeError(
59
+ if (!isString(title)) {
60
+ throw errors.createInvalidArgumentTypeError(
52
61
  'Suite argument "title" must be a string. Received type "' +
53
62
  typeof title +
54
63
  '"',
@@ -74,11 +83,19 @@ function Suite(title, parentContext, isRoot) {
74
83
  this._bail = false;
75
84
  this._onlyTests = [];
76
85
  this._onlySuites = [];
86
+ assignNewMochaID(this);
87
+
88
+ Object.defineProperty(this, 'id', {
89
+ get() {
90
+ return getMochaID(this);
91
+ }
92
+ });
93
+
77
94
  this.reset();
78
95
 
79
96
  this.on('newListener', function(event) {
80
97
  if (deprecatedEvents[event]) {
81
- utils.deprecate(
98
+ errors.deprecate(
82
99
  'Event "' +
83
100
  event +
84
101
  '" is deprecated. Please let the Mocha team know about your use case: https://git.io/v6Lwm'
@@ -145,7 +162,7 @@ Suite.prototype.timeout = function(ms) {
145
162
  // Clamp to range
146
163
  var INT_MAX = Math.pow(2, 31) - 1;
147
164
  var range = [0, INT_MAX];
148
- ms = utils.clamp(ms, range);
165
+ ms = clamp(ms, range);
149
166
 
150
167
  debug('timeout %d', ms);
151
168
  this._timeout = parseInt(ms, 10);
@@ -579,11 +596,13 @@ Suite.prototype.serialize = function serialize() {
579
596
  $$fullTitle: this.fullTitle(),
580
597
  $$isPending: this.isPending(),
581
598
  root: this.root,
582
- title: this.title
599
+ title: this.title,
600
+ id: this.id,
601
+ parent: this.parent ? {[MOCHA_ID_PROP_NAME]: this.parent.id} : null
583
602
  };
584
603
  };
585
604
 
586
- var constants = utils.defineConstants(
605
+ var constants = defineConstants(
587
606
  /**
588
607
  * {@link Suite}-related constants.
589
608
  * @public
@@ -671,6 +690,6 @@ var deprecatedEvents = Object.keys(constants)
671
690
  .reduce(function(acc, constant) {
672
691
  acc[constants[constant]] = true;
673
692
  return acc;
674
- }, utils.createMap());
693
+ }, createMap());
675
694
 
676
695
  Suite.constants = constants;
package/lib/test.js CHANGED
@@ -5,6 +5,8 @@ var errors = require('./errors');
5
5
  var createInvalidArgumentTypeError = errors.createInvalidArgumentTypeError;
6
6
  var isString = utils.isString;
7
7
 
8
+ const {MOCHA_ID_PROP_NAME} = utils.constants;
9
+
8
10
  module.exports = Test;
9
11
 
10
12
  /**
@@ -98,12 +100,14 @@ Test.prototype.serialize = function serialize() {
98
100
  duration: this.duration,
99
101
  err: this.err,
100
102
  parent: {
101
- $$fullTitle: this.parent.fullTitle()
103
+ $$fullTitle: this.parent.fullTitle(),
104
+ [MOCHA_ID_PROP_NAME]: this.parent.id
102
105
  },
103
106
  speed: this.speed,
104
107
  state: this.state,
105
108
  title: this.title,
106
109
  type: this.type,
107
- file: this.file
110
+ file: this.file,
111
+ [MOCHA_ID_PROP_NAME]: this.id
108
112
  };
109
113
  };