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.
- package/CHANGELOG.md +143 -2
- package/bin/mocha +24 -4
- package/browser-entry.js +37 -9
- package/lib/browser/growl.js +2 -1
- package/lib/browser/highlight-tags.js +39 -0
- package/lib/browser/parse-query.js +24 -0
- package/lib/browser/progress.js +4 -0
- package/lib/browser/template.html +7 -5
- package/lib/cli/cli.js +6 -3
- package/lib/cli/collect-files.js +17 -10
- package/lib/cli/config.js +6 -6
- package/lib/cli/init.js +1 -2
- package/lib/cli/lookup-files.js +145 -0
- package/lib/cli/node-flags.js +2 -2
- package/lib/cli/options.js +14 -90
- package/lib/cli/run-helpers.js +133 -36
- package/lib/cli/run-option-metadata.js +4 -2
- package/lib/cli/run.js +71 -11
- package/lib/cli/watch-run.js +211 -51
- package/lib/context.js +0 -15
- package/lib/errors.js +202 -9
- package/lib/esm-utils.js +11 -6
- package/lib/hook.js +32 -0
- package/lib/interfaces/common.js +21 -10
- package/lib/mocha.js +301 -126
- package/lib/mocharc.json +0 -1
- package/lib/nodejs/buffered-worker-pool.js +174 -0
- package/lib/{growl.js → nodejs/growl.js} +3 -2
- package/lib/nodejs/parallel-buffered-runner.js +295 -0
- package/lib/nodejs/reporters/parallel-buffered.js +133 -0
- package/lib/nodejs/serializer.js +404 -0
- package/lib/nodejs/worker.js +154 -0
- package/lib/pending.js +4 -0
- package/lib/reporters/base.js +25 -12
- package/lib/reporters/doc.js +6 -0
- package/lib/reporters/json-stream.js +1 -0
- package/lib/reporters/json.js +1 -0
- package/lib/reporters/landing.js +11 -3
- package/lib/reporters/tap.js +1 -2
- package/lib/reporters/xunit.js +3 -2
- package/lib/runnable.js +39 -47
- package/lib/runner.js +219 -118
- package/lib/suite.js +61 -27
- package/lib/test.js +48 -3
- package/lib/utils.js +33 -209
- package/mocha.js +25522 -17715
- package/mocha.js.map +1 -0
- package/package.json +87 -68
- 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} [
|
|
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,
|
|
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.
|
|
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 %
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
380
|
-
self.
|
|
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.
|
|
484
|
+
self.fail(hook, errForbid);
|
|
414
485
|
return fn(errForbid);
|
|
415
486
|
}
|
|
416
487
|
} else if (err) {
|
|
417
|
-
self.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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('
|
|
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
|
-
*
|
|
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.
|
|
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 && !
|
|
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.
|
|
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
|
|
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(
|
|
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.
|
|
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(
|
|
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.
|
|
916
|
-
|
|
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
|
-
|
|
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
|
-
|
|
928
|
-
|
|
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;
|