UNPKG

24.7 kBJavaScriptView Raw
1'use strict';
2
3/**
4 * Module dependencies.
5 */
6var util = require('util');
7var EventEmitter = require('events').EventEmitter;
8var Pending = require('./pending');
9var utils = require('./utils');
10var inherits = utils.inherits;
11var debug = require('debug')('mocha:runner');
12var Runnable = require('./runnable');
13var Suite = require('./suite');
14var HOOK_TYPE_BEFORE_EACH = Suite.constants.HOOK_TYPE_BEFORE_EACH;
15var HOOK_TYPE_AFTER_EACH = Suite.constants.HOOK_TYPE_AFTER_EACH;
16var HOOK_TYPE_AFTER_ALL = Suite.constants.HOOK_TYPE_AFTER_ALL;
17var HOOK_TYPE_BEFORE_ALL = Suite.constants.HOOK_TYPE_BEFORE_ALL;
18var EVENT_ROOT_SUITE_RUN = Suite.constants.EVENT_ROOT_SUITE_RUN;
19var STATE_FAILED = Runnable.constants.STATE_FAILED;
20var STATE_PASSED = Runnable.constants.STATE_PASSED;
21var dQuote = utils.dQuote;
22var ngettext = utils.ngettext;
23var sQuote = utils.sQuote;
24var stackFilter = utils.stackTraceFilter();
25var stringify = utils.stringify;
26var type = utils.type;
27var createInvalidExceptionError = require('./errors')
28 .createInvalidExceptionError;
29
30/**
31 * Non-enumerable globals.
32 * @readonly
33 */
34var globals = [
35 'setTimeout',
36 'clearTimeout',
37 'setInterval',
38 'clearInterval',
39 'XMLHttpRequest',
40 'Date',
41 'setImmediate',
42 'clearImmediate'
43];
44
45var constants = utils.defineConstants(
46 /**
47 * {@link Runner}-related constants.
48 * @public
49 * @memberof Runner
50 * @readonly
51 * @alias constants
52 * @static
53 * @enum {string}
54 */
55 {
56 /**
57 * Emitted when {@link Hook} execution begins
58 */
59 EVENT_HOOK_BEGIN: 'hook',
60 /**
61 * Emitted when {@link Hook} execution ends
62 */
63 EVENT_HOOK_END: 'hook end',
64 /**
65 * Emitted when Root {@link Suite} execution begins (all files have been parsed and hooks/tests are ready for execution)
66 */
67 EVENT_RUN_BEGIN: 'start',
68 /**
69 * Emitted when Root {@link Suite} execution has been delayed via `delay` option
70 */
71 EVENT_DELAY_BEGIN: 'waiting',
72 /**
73 * Emitted when delayed Root {@link Suite} execution is triggered by user via `global.run()`
74 */
75 EVENT_DELAY_END: 'ready',
76 /**
77 * Emitted when Root {@link Suite} execution ends
78 */
79 EVENT_RUN_END: 'end',
80 /**
81 * Emitted when {@link Suite} execution begins
82 */
83 EVENT_SUITE_BEGIN: 'suite',
84 /**
85 * Emitted when {@link Suite} execution ends
86 */
87 EVENT_SUITE_END: 'suite end',
88 /**
89 * Emitted when {@link Test} execution begins
90 */
91 EVENT_TEST_BEGIN: 'test',
92 /**
93 * Emitted when {@link Test} execution ends
94 */
95 EVENT_TEST_END: 'test end',
96 /**
97 * Emitted when {@link Test} execution fails
98 */
99 EVENT_TEST_FAIL: 'fail',
100 /**
101 * Emitted when {@link Test} execution succeeds
102 */
103 EVENT_TEST_PASS: 'pass',
104 /**
105 * Emitted when {@link Test} becomes pending
106 */
107 EVENT_TEST_PENDING: 'pending',
108 /**
109 * Emitted when {@link Test} execution has failed, but will retry
110 */
111 EVENT_TEST_RETRY: 'retry'
112 }
113);
114
115module.exports = Runner;
116
117/**
118 * Initialize a `Runner` at the Root {@link Suite}, which represents a hierarchy of {@link Suite|Suites} and {@link Test|Tests}.
119 *
120 * @extends external:EventEmitter
121 * @public
122 * @class
123 * @param {Suite} suite Root suite
124 * @param {boolean} [delay] Whether or not to delay execution of root suite
125 * until ready.
126 */
127function Runner(suite, delay) {
128 var self = this;
129 this._globals = [];
130 this._abort = false;
131 this._delay = delay;
132 this.suite = suite;
133 this.started = false;
134 this.total = suite.total();
135 this.failures = 0;
136 this.on(constants.EVENT_TEST_END, function(test) {
137 self.checkGlobals(test);
138 });
139 this.on(constants.EVENT_HOOK_END, function(hook) {
140 self.checkGlobals(hook);
141 });
142 this._defaultGrep = /.*/;
143 this.grep(this._defaultGrep);
144 this.globals(this.globalProps());
145}
146
147/**
148 * Wrapper for setImmediate, process.nextTick, or browser polyfill.
149 *
150 * @param {Function} fn
151 * @private
152 */
153Runner.immediately = global.setImmediate || process.nextTick;
154
155/**
156 * Inherit from `EventEmitter.prototype`.
157 */
158inherits(Runner, EventEmitter);
159
160/**
161 * Run tests with full titles matching `re`. Updates runner.total
162 * with number of tests matched.
163 *
164 * @public
165 * @memberof Runner
166 * @param {RegExp} re
167 * @param {boolean} invert
168 * @return {Runner} Runner instance.
169 */
170Runner.prototype.grep = function(re, invert) {
171 debug('grep %s', re);
172 this._grep = re;
173 this._invert = invert;
174 this.total = this.grepTotal(this.suite);
175 return this;
176};
177
178/**
179 * Returns the number of tests matching the grep search for the
180 * given suite.
181 *
182 * @memberof Runner
183 * @public
184 * @param {Suite} suite
185 * @return {number}
186 */
187Runner.prototype.grepTotal = function(suite) {
188 var self = this;
189 var total = 0;
190
191 suite.eachTest(function(test) {
192 var match = self._grep.test(test.fullTitle());
193 if (self._invert) {
194 match = !match;
195 }
196 if (match) {
197 total++;
198 }
199 });
200
201 return total;
202};
203
204/**
205 * Return a list of global properties.
206 *
207 * @return {Array}
208 * @private
209 */
210Runner.prototype.globalProps = function() {
211 var props = Object.keys(global);
212
213 // non-enumerables
214 for (var i = 0; i < globals.length; ++i) {
215 if (~props.indexOf(globals[i])) {
216 continue;
217 }
218 props.push(globals[i]);
219 }
220
221 return props;
222};
223
224/**
225 * Allow the given `arr` of globals.
226 *
227 * @public
228 * @memberof Runner
229 * @param {Array} arr
230 * @return {Runner} Runner instance.
231 */
232Runner.prototype.globals = function(arr) {
233 if (!arguments.length) {
234 return this._globals;
235 }
236 debug('globals %j', arr);
237 this._globals = this._globals.concat(arr);
238 return this;
239};
240
241/**
242 * Check for global variable leaks.
243 *
244 * @private
245 */
246Runner.prototype.checkGlobals = function(test) {
247 if (this.ignoreLeaks) {
248 return;
249 }
250 var ok = this._globals;
251
252 var globals = this.globalProps();
253 var leaks;
254
255 if (test) {
256 ok = ok.concat(test._allowedGlobals || []);
257 }
258
259 if (this.prevGlobalsLength === globals.length) {
260 return;
261 }
262 this.prevGlobalsLength = globals.length;
263
264 leaks = filterLeaks(ok, globals);
265 this._globals = this._globals.concat(leaks);
266
267 if (leaks.length) {
268 var format = ngettext(
269 leaks.length,
270 'global leak detected: %s',
271 'global leaks detected: %s'
272 );
273 var error = new Error(util.format(format, leaks.map(sQuote).join(', ')));
274 this.fail(test, error);
275 }
276};
277
278/**
279 * Fail the given `test`.
280 *
281 * @private
282 * @param {Test} test
283 * @param {Error} err
284 */
285Runner.prototype.fail = function(test, err) {
286 if (test.isPending()) {
287 return;
288 }
289
290 ++this.failures;
291 test.state = STATE_FAILED;
292
293 if (!isError(err)) {
294 err = thrown2Error(err);
295 }
296
297 try {
298 err.stack =
299 this.fullStackTrace || !err.stack ? err.stack : stackFilter(err.stack);
300 } catch (ignore) {
301 // some environments do not take kindly to monkeying with the stack
302 }
303
304 this.emit(constants.EVENT_TEST_FAIL, test, err);
305};
306
307/**
308 * Fail the given `hook` with `err`.
309 *
310 * Hook failures work in the following pattern:
311 * - If bail, run corresponding `after each` and `after` hooks,
312 * then exit
313 * - Failed `before` hook skips all tests in a suite and subsuites,
314 * but jumps to corresponding `after` hook
315 * - Failed `before each` hook skips remaining tests in a
316 * suite and jumps to corresponding `after each` hook,
317 * which is run only once
318 * - Failed `after` hook does not alter
319 * 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 */
328Runner.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/**
347 * Run hook `name` callbacks and then invoke `fn()`.
348 *
349 * @private
350 * @param {string} name
351 * @param {Function} fn
352 */
353
354Runner.prototype.hook = function(name, fn) {
355 var suite = this.suite;
356 var hooks = suite.getHooks(name);
357 var self = this;
358
359 function next(i) {
360 var hook = hooks[i];
361 if (!hook) {
362 return fn();
363 }
364 self.currentRunnable = hook;
365
366 if (name === HOOK_TYPE_BEFORE_ALL) {
367 hook.ctx.currentTest = hook.parent.tests[0];
368 } else if (name === HOOK_TYPE_AFTER_ALL) {
369 hook.ctx.currentTest = hook.parent.tests[hook.parent.tests.length - 1];
370 } else {
371 hook.ctx.currentTest = self.test;
372 }
373
374 hook.allowUncaught = self.allowUncaught;
375
376 self.emit(constants.EVENT_HOOK_BEGIN, hook);
377
378 if (!hook.listeners('error').length) {
379 hook.on('error', function(err) {
380 self.failHook(hook, err);
381 });
382 }
383
384 hook.run(function(err) {
385 var testError = hook.error();
386 if (testError) {
387 self.fail(self.test, testError);
388 }
389 if (err) {
390 if (err instanceof Pending) {
391 if (name === HOOK_TYPE_AFTER_ALL) {
392 utils.deprecate(
393 'Skipping a test within an "after all" hook is DEPRECATED and will throw an exception in a future version of Mocha. ' +
394 'Use a return statement or other means to abort hook execution.'
395 );
396 }
397 if (name === HOOK_TYPE_BEFORE_EACH || name === HOOK_TYPE_AFTER_EACH) {
398 if (self.test) {
399 self.test.pending = true;
400 }
401 } else {
402 suite.tests.forEach(function(test) {
403 test.pending = true;
404 });
405 suite.suites.forEach(function(suite) {
406 suite.pending = true;
407 });
408 // a pending hook won't be executed twice.
409 hook.pending = true;
410 }
411 } else {
412 self.failHook(hook, err);
413
414 // stop executing hooks, notify callee of hook err
415 return fn(err);
416 }
417 }
418 self.emit(constants.EVENT_HOOK_END, hook);
419 delete hook.ctx.currentTest;
420 next(++i);
421 });
422 }
423
424 Runner.immediately(function() {
425 next(0);
426 });
427};
428
429/**
430 * Run hook `name` for the given array of `suites`
431 * in order, and callback `fn(err, errSuite)`.
432 *
433 * @private
434 * @param {string} name
435 * @param {Array} suites
436 * @param {Function} fn
437 */
438Runner.prototype.hooks = function(name, suites, fn) {
439 var self = this;
440 var orig = this.suite;
441
442 function next(suite) {
443 self.suite = suite;
444
445 if (!suite) {
446 self.suite = orig;
447 return fn();
448 }
449
450 self.hook(name, function(err) {
451 if (err) {
452 var errSuite = self.suite;
453 self.suite = orig;
454 return fn(err, errSuite);
455 }
456
457 next(suites.pop());
458 });
459 }
460
461 next(suites.pop());
462};
463
464/**
465 * Run hooks from the top level down.
466 *
467 * @param {String} name
468 * @param {Function} fn
469 * @private
470 */
471Runner.prototype.hookUp = function(name, fn) {
472 var suites = [this.suite].concat(this.parents()).reverse();
473 this.hooks(name, suites, fn);
474};
475
476/**
477 * Run hooks from the bottom up.
478 *
479 * @param {String} name
480 * @param {Function} fn
481 * @private
482 */
483Runner.prototype.hookDown = function(name, fn) {
484 var suites = [this.suite].concat(this.parents());
485 this.hooks(name, suites, fn);
486};
487
488/**
489 * Return an array of parent Suites from
490 * closest to furthest.
491 *
492 * @return {Array}
493 * @private
494 */
495Runner.prototype.parents = function() {
496 var suite = this.suite;
497 var suites = [];
498 while (suite.parent) {
499 suite = suite.parent;
500 suites.push(suite);
501 }
502 return suites;
503};
504
505/**
506 * Run the current test and callback `fn(err)`.
507 *
508 * @param {Function} fn
509 * @private
510 */
511Runner.prototype.runTest = function(fn) {
512 var self = this;
513 var test = this.test;
514
515 if (!test) {
516 return;
517 }
518
519 var suite = this.parents().reverse()[0] || this.suite;
520 if (this.forbidOnly && suite.hasOnly()) {
521 fn(new Error('`.only` forbidden'));
522 return;
523 }
524 if (this.asyncOnly) {
525 test.asyncOnly = true;
526 }
527 test.on('error', function(err) {
528 self.fail(test, err);
529 });
530 if (this.allowUncaught) {
531 test.allowUncaught = true;
532 return test.run(fn);
533 }
534 try {
535 test.run(fn);
536 } catch (err) {
537 fn(err);
538 }
539};
540
541/**
542 * Run tests in the given `suite` and invoke the callback `fn()` when complete.
543 *
544 * @private
545 * @param {Suite} suite
546 * @param {Function} fn
547 */
548Runner.prototype.runTests = function(suite, fn) {
549 var self = this;
550 var tests = suite.tests.slice();
551 var test;
552
553 function hookErr(_, errSuite, after) {
554 // before/after Each hook for errSuite failed:
555 var orig = self.suite;
556
557 // for failed 'after each' hook start from errSuite parent,
558 // otherwise start from errSuite itself
559 self.suite = after ? errSuite.parent : errSuite;
560
561 if (self.suite) {
562 // call hookUp afterEach
563 self.hookUp(HOOK_TYPE_AFTER_EACH, function(err2, errSuite2) {
564 self.suite = orig;
565 // some hooks may fail even now
566 if (err2) {
567 return hookErr(err2, errSuite2, true);
568 }
569 // report error suite
570 fn(errSuite);
571 });
572 } else {
573 // there is no need calling other 'after each' hooks
574 self.suite = orig;
575 fn(errSuite);
576 }
577 }
578
579 function next(err, errSuite) {
580 // if we bail after first err
581 if (self.failures && suite._bail) {
582 tests = [];
583 }
584
585 if (self._abort) {
586 return fn();
587 }
588
589 if (err) {
590 return hookErr(err, errSuite, true);
591 }
592
593 // next test
594 test = tests.shift();
595
596 // all done
597 if (!test) {
598 return fn();
599 }
600
601 // grep
602 var match = self._grep.test(test.fullTitle());
603 if (self._invert) {
604 match = !match;
605 }
606 if (!match) {
607 // Run immediately only if we have defined a grep. When we
608 // define a grep — It can cause maximum callstack error if
609 // the grep is doing a large recursive loop by neglecting
610 // all tests. The run immediately function also comes with
611 // a performance cost. So we don't want to run immediately
612 // if we run the whole test suite, because running the whole
613 // test suite don't do any immediate recursive loops. Thus,
614 // allowing a JS runtime to breathe.
615 if (self._grep !== self._defaultGrep) {
616 Runner.immediately(next);
617 } else {
618 next();
619 }
620 return;
621 }
622
623 if (test.isPending()) {
624 if (self.forbidPending) {
625 test.isPending = alwaysFalse;
626 self.fail(test, new Error('Pending test forbidden'));
627 delete test.isPending;
628 } else {
629 self.emit(constants.EVENT_TEST_PENDING, test);
630 }
631 self.emit(constants.EVENT_TEST_END, test);
632 return next();
633 }
634
635 // execute test and hook(s)
636 self.emit(constants.EVENT_TEST_BEGIN, (self.test = test));
637 self.hookDown(HOOK_TYPE_BEFORE_EACH, function(err, errSuite) {
638 if (test.isPending()) {
639 if (self.forbidPending) {
640 test.isPending = alwaysFalse;
641 self.fail(test, new Error('Pending test forbidden'));
642 delete test.isPending;
643 } else {
644 self.emit(constants.EVENT_TEST_PENDING, test);
645 }
646 self.emit(constants.EVENT_TEST_END, test);
647 return next();
648 }
649 if (err) {
650 return hookErr(err, errSuite, false);
651 }
652 self.currentRunnable = self.test;
653 self.runTest(function(err) {
654 test = self.test;
655 if (err) {
656 var retry = test.currentRetry();
657 if (err instanceof Pending && self.forbidPending) {
658 self.fail(test, new Error('Pending test forbidden'));
659 } else if (err instanceof Pending) {
660 test.pending = true;
661 self.emit(constants.EVENT_TEST_PENDING, test);
662 } else if (retry < test.retries()) {
663 var clonedTest = test.clone();
664 clonedTest.currentRetry(retry + 1);
665 tests.unshift(clonedTest);
666
667 self.emit(constants.EVENT_TEST_RETRY, test, err);
668
669 // Early return + hook trigger so that it doesn't
670 // increment the count wrong
671 return self.hookUp(HOOK_TYPE_AFTER_EACH, next);
672 } else {
673 self.fail(test, err);
674 }
675 self.emit(constants.EVENT_TEST_END, test);
676
677 if (err instanceof Pending) {
678 return next();
679 }
680
681 return self.hookUp(HOOK_TYPE_AFTER_EACH, next);
682 }
683
684 test.state = STATE_PASSED;
685 self.emit(constants.EVENT_TEST_PASS, test);
686 self.emit(constants.EVENT_TEST_END, test);
687 self.hookUp(HOOK_TYPE_AFTER_EACH, next);
688 });
689 });
690 }
691
692 this.next = next;
693 this.hookErr = hookErr;
694 next();
695};
696
697function alwaysFalse() {
698 return false;
699}
700
701/**
702 * Run the given `suite` and invoke the callback `fn()` when complete.
703 *
704 * @private
705 * @param {Suite} suite
706 * @param {Function} fn
707 */
708Runner.prototype.runSuite = function(suite, fn) {
709 var i = 0;
710 var self = this;
711 var total = this.grepTotal(suite);
712 var afterAllHookCalled = false;
713
714 debug('run suite %s', suite.fullTitle());
715
716 if (!total || (self.failures && suite._bail)) {
717 return fn();
718 }
719
720 this.emit(constants.EVENT_SUITE_BEGIN, (this.suite = suite));
721
722 function next(errSuite) {
723 if (errSuite) {
724 // current suite failed on a hook from errSuite
725 if (errSuite === suite) {
726 // if errSuite is current suite
727 // continue to the next sibling suite
728 return done();
729 }
730 // errSuite is among the parents of current suite
731 // stop execution of errSuite and all sub-suites
732 return done(errSuite);
733 }
734
735 if (self._abort) {
736 return done();
737 }
738
739 var curr = suite.suites[i++];
740 if (!curr) {
741 return done();
742 }
743
744 // Avoid grep neglecting large number of tests causing a
745 // huge recursive loop and thus a maximum call stack error.
746 // See comment in `this.runTests()` for more information.
747 if (self._grep !== self._defaultGrep) {
748 Runner.immediately(function() {
749 self.runSuite(curr, next);
750 });
751 } else {
752 self.runSuite(curr, next);
753 }
754 }
755
756 function done(errSuite) {
757 self.suite = suite;
758 self.nextSuite = next;
759
760 if (afterAllHookCalled) {
761 fn(errSuite);
762 } else {
763 // mark that the afterAll block has been called once
764 // and so can be skipped if there is an error in it.
765 afterAllHookCalled = true;
766
767 // remove reference to test
768 delete self.test;
769
770 self.hook(HOOK_TYPE_AFTER_ALL, function() {
771 self.emit(constants.EVENT_SUITE_END, suite);
772 fn(errSuite);
773 });
774 }
775 }
776
777 this.nextSuite = next;
778
779 this.hook(HOOK_TYPE_BEFORE_ALL, function(err) {
780 if (err) {
781 return done();
782 }
783 self.runTests(suite, next);
784 });
785};
786
787/**
788 * Handle uncaught exceptions.
789 *
790 * @param {Error} err
791 * @private
792 */
793Runner.prototype.uncaught = function(err) {
794 if (err instanceof Pending) {
795 return;
796 }
797 if (err) {
798 debug('uncaught exception %O', err);
799 } else {
800 debug('uncaught undefined/falsy exception');
801 err = createInvalidExceptionError(
802 'Caught falsy/undefined exception which would otherwise be uncaught. No stack trace found; try a debugger',
803 err
804 );
805 }
806
807 if (!isError(err)) {
808 err = thrown2Error(err);
809 }
810 err.uncaught = true;
811
812 var runnable = this.currentRunnable;
813
814 if (!runnable) {
815 runnable = new Runnable('Uncaught error outside test suite');
816 runnable.parent = this.suite;
817
818 if (this.started) {
819 this.fail(runnable, err);
820 } else {
821 // Can't recover from this failure
822 this.emit(constants.EVENT_RUN_BEGIN);
823 this.fail(runnable, err);
824 this.emit(constants.EVENT_RUN_END);
825 }
826
827 return;
828 }
829
830 runnable.clearTimeout();
831
832 // Ignore errors if already failed or pending
833 // See #3226
834 if (runnable.isFailed() || runnable.isPending()) {
835 return;
836 }
837 // we cannot recover gracefully if a Runnable has already passed
838 // then fails asynchronously
839 var alreadyPassed = runnable.isPassed();
840 // this will change the state to "failed" regardless of the current value
841 this.fail(runnable, err);
842 if (!alreadyPassed) {
843 // recover from test
844 if (runnable.type === constants.EVENT_TEST_BEGIN) {
845 this.emit(constants.EVENT_TEST_END, runnable);
846 this.hookUp(HOOK_TYPE_AFTER_EACH, this.next);
847 return;
848 }
849 debug(runnable);
850
851 // recover from hooks
852 var errSuite = this.suite;
853
854 // XXX how about a less awful way to determine this?
855 // if hook failure is in afterEach block
856 if (runnable.fullTitle().indexOf('after each') > -1) {
857 return this.hookErr(err, errSuite, true);
858 }
859 // if hook failure is in beforeEach block
860 if (runnable.fullTitle().indexOf('before each') > -1) {
861 return this.hookErr(err, errSuite, false);
862 }
863 // if hook failure is in after or before blocks
864 return this.nextSuite(errSuite);
865 }
866
867 // bail
868 this.emit(constants.EVENT_RUN_END);
869};
870
871/**
872 * Run the root suite and invoke `fn(failures)`
873 * on completion.
874 *
875 * @public
876 * @memberof Runner
877 * @param {Function} fn
878 * @return {Runner} Runner instance.
879 */
880Runner.prototype.run = function(fn) {
881 var self = this;
882 var rootSuite = this.suite;
883
884 fn = fn || function() {};
885
886 function uncaught(err) {
887 self.uncaught(err);
888 }
889
890 function start() {
891 // If there is an `only` filter
892 if (rootSuite.hasOnly()) {
893 rootSuite.filterOnly();
894 }
895 self.started = true;
896 if (self._delay) {
897 self.emit(constants.EVENT_DELAY_END);
898 }
899 self.emit(constants.EVENT_RUN_BEGIN);
900
901 self.runSuite(rootSuite, function() {
902 debug('finished running');
903 self.emit(constants.EVENT_RUN_END);
904 });
905 }
906
907 debug(constants.EVENT_RUN_BEGIN);
908
909 // references cleanup to avoid memory leaks
910 this.on(constants.EVENT_SUITE_END, function(suite) {
911 suite.cleanReferences();
912 });
913
914 // callback
915 this.on(constants.EVENT_RUN_END, function() {
916 debug(constants.EVENT_RUN_END);
917 process.removeListener('uncaughtException', uncaught);
918 fn(self.failures);
919 });
920
921 // uncaught exception
922 process.on('uncaughtException', uncaught);
923
924 if (this._delay) {
925 // for reporters, I guess.
926 // might be nice to debounce some dots while we wait.
927 this.emit(constants.EVENT_DELAY_BEGIN, rootSuite);
928 rootSuite.once(EVENT_ROOT_SUITE_RUN, start);
929 } else {
930 start();
931 }
932
933 return this;
934};
935
936/**
937 * Cleanly abort execution.
938 *
939 * @memberof Runner
940 * @public
941 * @return {Runner} Runner instance.
942 */
943Runner.prototype.abort = function() {
944 debug('aborting');
945 this._abort = true;
946
947 return this;
948};
949
950/**
951 * Filter leaks with the given globals flagged as `ok`.
952 *
953 * @private
954 * @param {Array} ok
955 * @param {Array} globals
956 * @return {Array}
957 */
958function filterLeaks(ok, globals) {
959 return globals.filter(function(key) {
960 // Firefox and Chrome exposes iframes as index inside the window object
961 if (/^\d+/.test(key)) {
962 return false;
963 }
964
965 // in firefox
966 // if runner runs in an iframe, this iframe's window.getInterface method
967 // not init at first it is assigned in some seconds
968 if (global.navigator && /^getInterface/.test(key)) {
969 return false;
970 }
971
972 // an iframe could be approached by window[iframeIndex]
973 // in ie6,7,8 and opera, iframeIndex is enumerable, this could cause leak
974 if (global.navigator && /^\d+/.test(key)) {
975 return false;
976 }
977
978 // Opera and IE expose global variables for HTML element IDs (issue #243)
979 if (/^mocha-/.test(key)) {
980 return false;
981 }
982
983 var matched = ok.filter(function(ok) {
984 if (~ok.indexOf('*')) {
985 return key.indexOf(ok.split('*')[0]) === 0;
986 }
987 return key === ok;
988 });
989 return !matched.length && (!global.navigator || key !== 'onerror');
990 });
991}
992
993/**
994 * Check if argument is an instance of Error object or a duck-typed equivalent.
995 *
996 * @private
997 * @param {Object} err - object to check
998 * @param {string} err.message - error message
999 * @returns {boolean}
1000 */
1001function isError(err) {
1002 return err instanceof Error || (err && typeof err.message === 'string');
1003}
1004
1005/**
1006 *
1007 * Converts thrown non-extensible type into proper Error.
1008 *
1009 * @private
1010 * @param {*} thrown - Non-extensible type thrown by code
1011 * @return {Error}
1012 */
1013function thrown2Error(err) {
1014 return new Error(
1015 'the ' + type(err) + ' ' + stringify(err) + ' was thrown, throw an Error :)'
1016 );
1017}
1018
1019Runner.constants = constants;
1020
1021/**
1022 * Node.js' `EventEmitter`
1023 * @external EventEmitter
1024 * @see {@link https://nodejs.org/api/events.html#events_class_eventemitter}
1025 */