| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307 |
- 'use strict';
- /**
- * @typedef {import('./types.d.ts').RunnerOptions} RunnerOptions
- */
- /**
- * Module dependencies.
- * @private
- */
- var EventEmitter = require('node:events').EventEmitter;
- var Pending = require('./pending');
- var utils = require('./utils');
- var debug = require('debug')('mocha:runner');
- var Runnable = require('./runnable');
- var Suite = require('./suite');
- var HOOK_TYPE_BEFORE_EACH = Suite.constants.HOOK_TYPE_BEFORE_EACH;
- var HOOK_TYPE_AFTER_EACH = Suite.constants.HOOK_TYPE_AFTER_EACH;
- var HOOK_TYPE_AFTER_ALL = Suite.constants.HOOK_TYPE_AFTER_ALL;
- var HOOK_TYPE_BEFORE_ALL = Suite.constants.HOOK_TYPE_BEFORE_ALL;
- var EVENT_ROOT_SUITE_RUN = Suite.constants.EVENT_ROOT_SUITE_RUN;
- var STATE_FAILED = Runnable.constants.STATE_FAILED;
- var STATE_PASSED = Runnable.constants.STATE_PASSED;
- var STATE_PENDING = Runnable.constants.STATE_PENDING;
- var stackFilter = utils.stackTraceFilter();
- var stringify = utils.stringify;
- const {
- createInvalidExceptionError,
- createUnsupportedError,
- createFatalError,
- isMochaError,
- } = require('./errors');
- const { constants: errorConstants } = require('./error-constants');
- /**
- * Non-enumerable globals.
- * @private
- * @readonly
- */
- var globals = [
- 'setTimeout',
- 'clearTimeout',
- 'setInterval',
- 'clearInterval',
- 'XMLHttpRequest',
- 'Date',
- 'setImmediate',
- 'clearImmediate'
- ];
- var constants = utils.defineConstants(
- /**
- * {@link Runner}-related constants. Used by reporters. Each event emits the corresponding object, unless otherwise indicated.
- * @example
- * const Mocha = require('mocha');
- * const Base = Mocha.reporters.Base;
- * const {
- * EVENT_HOOK_BEGIN,
- * EVENT_TEST_PASS,
- * EVENT_TEST_FAIL,
- * EVENT_TEST_END
- * } = Mocha.Runner.constants
- *
- * function MyReporter(runner, options) {
- * Base.call(this, runner, options);
- *
- * runner.on(EVENT_HOOK_BEGIN, function(hook) {
- * console.log('hook called: ', hook.title);
- * });
- *
- * runner.on(EVENT_TEST_PASS, function(test) {
- * console.log('pass: %s', test.fullTitle());
- * });
- *
- * runner.on(EVENT_TEST_FAIL, function(test, err) {
- * console.log('fail: %s -- error: %s', test.fullTitle(), err.message);
- * });
- *
- * runner.on(EVENT_TEST_END, function() {
- * console.log('end: %d/%d', runner.stats.passes, runner.stats.tests);
- * });
- * }
- *
- * module.exports = MyReporter;
- *
- * @public
- * @memberof Runner
- * @readonly
- * @alias constants
- * @static
- * @enum {string}
- */
- {
- /**
- * Emitted when {@link Hook} execution begins
- */
- EVENT_HOOK_BEGIN: 'hook',
- /**
- * Emitted when {@link Hook} execution ends
- */
- EVENT_HOOK_END: 'hook end',
- /**
- * Emitted when Root {@link Suite} execution begins (all files have been parsed and hooks/tests are ready for execution)
- */
- EVENT_RUN_BEGIN: 'start',
- /**
- * Emitted when Root {@link Suite} execution has been delayed via `delay` option
- */
- EVENT_DELAY_BEGIN: 'waiting',
- /**
- * Emitted when delayed Root {@link Suite} execution is triggered by user via `global.run()`
- */
- EVENT_DELAY_END: 'ready',
- /**
- * Emitted when Root {@link Suite} execution ends
- */
- EVENT_RUN_END: 'end',
- /**
- * Emitted when {@link Suite} execution begins
- */
- EVENT_SUITE_BEGIN: 'suite',
- /**
- * Emitted when {@link Suite} execution ends
- */
- EVENT_SUITE_END: 'suite end',
- /**
- * Emitted when {@link Test} execution begins
- */
- EVENT_TEST_BEGIN: 'test',
- /**
- * Emitted when {@link Test} execution ends
- */
- EVENT_TEST_END: 'test end',
- /**
- * Emitted when {@link Test} execution fails. Includes an `err` object of type `Error`.
- * @example
- * runner.on(EVENT_TEST_FAIL, function(test, err) {
- * console.log('fail: %s -- error: %s', test.fullTitle(), err.message);
- * });
- *
- *
- */
- EVENT_TEST_FAIL: 'fail',
- /**
- * Emitted when {@link Test} execution succeeds
- */
- EVENT_TEST_PASS: 'pass',
- /**
- * Emitted when {@link Test} becomes pending
- */
- EVENT_TEST_PENDING: 'pending',
- /**
- * Emitted when {@link Test} execution has failed, but will retry
- */
- EVENT_TEST_RETRY: 'retry',
- /**
- * Initial state of Runner
- */
- STATE_IDLE: 'idle',
- /**
- * State set to this value when the Runner has started running
- */
- STATE_RUNNING: 'running',
- /**
- * State set to this value when the Runner has stopped
- */
- STATE_STOPPED: 'stopped'
- }
- );
- class Runner extends EventEmitter {
- /**
- * Initialize a `Runner` at the Root {@link Suite}, which represents a hierarchy of {@link Suite|Suites} and {@link Test|Tests}.
- *
- * @extends external:EventEmitter
- * @public
- * @class
- * @param {Suite} suite - Root suite
- * @param {Object} [opts] - Settings object
- * @param {boolean} [opts.cleanReferencesAfterRun] - Whether to clean references to test fns and hooks when a suite is done.
- * @param {boolean} [opts.delay] - Whether to delay execution of root suite until ready.
- * @param {boolean} [opts.dryRun] - Whether to report tests without running them.
- * @param {boolean} [opts.failZero] - Whether to fail test run if zero tests encountered.
- */
- constructor(suite, opts = {}) {
- super();
- var self = this;
- this._globals = [];
- this._abort = false;
- this.suite = suite;
- this._opts = opts;
- this.state = constants.STATE_IDLE;
- this.total = suite.total();
- this.failures = 0;
- /**
- * @type {Map<EventEmitter,Map<string,Set<EventListener>>>}
- */
- this._eventListeners = new Map();
- this.on(constants.EVENT_TEST_END, function (test) {
- if (test.type === 'test' && test.retriedTest() && test.parent) {
- var idx =
- test.parent.tests && test.parent.tests.indexOf(test.retriedTest());
- if (idx > -1) test.parent.tests[idx] = test;
- }
- self.checkGlobals(test);
- });
- this.on(constants.EVENT_HOOK_END, function (hook) {
- self.checkGlobals(hook);
- });
- this._defaultGrep = /.*/;
- this.grep(this._defaultGrep);
- this.globals(this.globalProps());
- this.uncaught = this._uncaught.bind(this);
- this.unhandled = (reason, promise) => {
- if (isMochaError(reason)) {
- debug(
- 'trapped unhandled rejection coming out of Mocha; forwarding to uncaught handler:',
- reason
- );
- this.uncaught(reason);
- } else {
- debug(
- 'trapped unhandled rejection from (probably) user code; re-emitting on process'
- );
- this._removeEventListener(
- process,
- 'unhandledRejection',
- this.unhandled
- );
- try {
- process.emit('unhandledRejection', reason, promise);
- } finally {
- this._addEventListener(process, 'unhandledRejection', this.unhandled);
- }
- }
- };
- }
- }
- /**
- * Wrapper for setImmediate, process.nextTick, or browser polyfill.
- *
- * @param {Function} fn
- * @private
- */
- Runner.immediately = global.setImmediate || process.nextTick;
- /**
- * Replacement for `target.on(eventName, listener)` that does bookkeeping to remove them when this runner instance is disposed.
- * @param {EventEmitter} target - The `EventEmitter`
- * @param {string} eventName - The event name
- * @param {string} fn - Listener function
- * @private
- */
- Runner.prototype._addEventListener = function (target, eventName, listener) {
- debug(
- '_addEventListener(): adding for event %s; %d current listeners',
- eventName,
- target.listenerCount(eventName)
- );
- /* istanbul ignore next */
- if (
- this._eventListeners.has(target) &&
- this._eventListeners.get(target).has(eventName) &&
- this._eventListeners.get(target).get(eventName).has(listener)
- ) {
- debug(
- 'warning: tried to attach duplicate event listener for %s',
- eventName
- );
- return;
- }
- target.on(eventName, listener);
- const targetListeners = this._eventListeners.has(target)
- ? this._eventListeners.get(target)
- : new Map();
- const targetEventListeners = targetListeners.has(eventName)
- ? targetListeners.get(eventName)
- : new Set();
- targetEventListeners.add(listener);
- targetListeners.set(eventName, targetEventListeners);
- this._eventListeners.set(target, targetListeners);
- };
- /**
- * Replacement for `target.removeListener(eventName, listener)` that also updates the bookkeeping.
- * @param {EventEmitter} target - The `EventEmitter`
- * @param {string} eventName - The event name
- * @param {function} listener - Listener function
- * @private
- */
- Runner.prototype._removeEventListener = function (target, eventName, listener) {
- target.removeListener(eventName, listener);
- if (this._eventListeners.has(target)) {
- const targetListeners = this._eventListeners.get(target);
- if (targetListeners.has(eventName)) {
- const targetEventListeners = targetListeners.get(eventName);
- targetEventListeners.delete(listener);
- if (!targetEventListeners.size) {
- targetListeners.delete(eventName);
- }
- }
- if (!targetListeners.size) {
- this._eventListeners.delete(target);
- }
- } else {
- debug('trying to remove listener for untracked object %s', target);
- }
- };
- /**
- * Removes all event handlers set during a run on this instance.
- * Remark: this does _not_ clean/dispose the tests or suites themselves.
- */
- Runner.prototype.dispose = function () {
- this.removeAllListeners();
- this._eventListeners.forEach((targetListeners, target) => {
- targetListeners.forEach((targetEventListeners, eventName) => {
- targetEventListeners.forEach(listener => {
- target.removeListener(eventName, listener);
- });
- });
- });
- this._eventListeners.clear();
- };
- /**
- * Run tests with full titles matching `re`. Updates runner.total
- * with number of tests matched.
- *
- * @public
- * @memberof Runner
- * @param {RegExp} re
- * @param {boolean} invert
- * @return {Runner} Runner instance.
- */
- Runner.prototype.grep = function (re, invert) {
- debug('grep(): setting to %s', re);
- this._grep = re;
- this._invert = invert;
- this.total = this.grepTotal(this.suite);
- return this;
- };
- /**
- * Returns the number of tests matching the grep search for the
- * given suite.
- *
- * @memberof Runner
- * @public
- * @param {Suite} suite
- * @return {number}
- */
- Runner.prototype.grepTotal = function (suite) {
- var self = this;
- var total = 0;
- suite.eachTest(function (test) {
- var match = self._grep.test(test.fullTitle());
- if (self._invert) {
- match = !match;
- }
- if (match) {
- total++;
- }
- });
- return total;
- };
- /**
- * Return a list of global properties.
- *
- * @return {Array}
- * @private
- */
- Runner.prototype.globalProps = function () {
- var props = Object.keys(global);
- // non-enumerables
- for (var i = 0; i < globals.length; ++i) {
- if (~props.indexOf(globals[i])) {
- continue;
- }
- props.push(globals[i]);
- }
- return props;
- };
- /**
- * Allow the given `arr` of globals.
- *
- * @public
- * @memberof Runner
- * @param {Array} arr
- * @return {Runner} Runner instance.
- */
- Runner.prototype.globals = function (arr) {
- if (!arguments.length) {
- return this._globals;
- }
- debug('globals(): setting to %O', arr);
- this._globals = this._globals.concat(arr);
- return this;
- };
- /**
- * Check for global variable leaks.
- *
- * @private
- */
- Runner.prototype.checkGlobals = function (test) {
- if (!this.checkLeaks) {
- return;
- }
- var ok = this._globals;
- var globals = this.globalProps();
- var leaks;
- if (test) {
- ok = ok.concat(test._allowedGlobals || []);
- }
- if (this.prevGlobalsLength === globals.length) {
- return;
- }
- this.prevGlobalsLength = globals.length;
- leaks = filterLeaks(ok, globals);
- this._globals = this._globals.concat(leaks);
- if (leaks.length) {
- var msg = `global leak(s) detected: ${leaks.map(e => `'${e}'`).join(', ')}`;
- this.fail(test, new Error(msg));
- }
- };
- /**
- * Fail the given `test`.
- *
- * If `test` is a hook, failures work in the following pattern:
- * - If bail, run corresponding `after each` and `after` hooks,
- * then exit
- * - Failed `before` hook skips all tests in a suite and subsuites,
- * but jumps to corresponding `after` hook
- * - Failed `before each` hook skips remaining tests in a
- * suite and jumps to corresponding `after each` hook,
- * which is run only once
- * - Failed `after` hook does not alter execution order
- * - Failed `after each` hook skips remaining tests in a
- * suite and subsuites, but executes other `after each`
- * hooks
- *
- * @private
- * @param {Runnable} test
- * @param {Error} err
- * @param {boolean} [force=false] - Whether to fail a pending test.
- */
- Runner.prototype.fail = function (test, err, force) {
- force = force === true;
- if (test.isPending() && !force) {
- return;
- }
- if (this.state === constants.STATE_STOPPED) {
- if (err.code === errorConstants.MULTIPLE_DONE) {
- throw err;
- }
- throw createFatalError(
- 'Test failed after root suite execution completed!',
- err
- );
- }
- ++this.failures;
- debug('total number of failures: %d', this.failures);
- test.state = STATE_FAILED;
- if (!isError(err)) {
- err = thrown2Error(err);
- }
- // Filter the stack traces
- if (!this.fullStackTrace) {
- const alreadyFiltered = new Set();
- let currentErr = err;
- while (currentErr && currentErr.stack && !alreadyFiltered.has(currentErr)) {
- alreadyFiltered.add(currentErr);
- try {
- currentErr.stack = stackFilter(currentErr.stack);
- } catch (ignore) {
- // some environments do not take kindly to monkeying with the stack
- }
- currentErr = currentErr.cause;
- }
- }
- this.emit(constants.EVENT_TEST_FAIL, test, err);
- };
- /**
- * Run hook `name` callbacks and then invoke `fn()`.
- *
- * @private
- * @param {string} name
- * @param {Function} fn
- */
- Runner.prototype.hook = function (name, fn) {
- if (this._opts.dryRun) return fn();
- var suite = this.suite;
- var hooks = suite.getHooks(name);
- var self = this;
- function next(i) {
- var hook = hooks[i];
- if (!hook) {
- return fn();
- }
- self.currentRunnable = hook;
- if (name === HOOK_TYPE_BEFORE_ALL) {
- hook.ctx.currentTest = hook.parent.tests[0];
- } else if (name === HOOK_TYPE_AFTER_ALL) {
- hook.ctx.currentTest = hook.parent.tests[hook.parent.tests.length - 1];
- } else {
- hook.ctx.currentTest = self.test;
- }
- setHookTitle(hook);
- hook.allowUncaught = self.allowUncaught;
- self.emit(constants.EVENT_HOOK_BEGIN, hook);
- if (!hook.listeners('error').length) {
- self._addEventListener(hook, 'error', function (err) {
- self.fail(hook, err);
- });
- }
- hook.run(function cbHookRun(err) {
- var testError = hook.error();
- if (testError) {
- self.fail(self.test, testError);
- }
- // conditional skip
- if (hook.pending) {
- if (name === HOOK_TYPE_AFTER_EACH) {
- // TODO define and implement use case
- if (self.test) {
- self.test.pending = true;
- }
- } else if (name === HOOK_TYPE_BEFORE_EACH) {
- if (self.test) {
- self.test.pending = true;
- }
- self.emit(constants.EVENT_HOOK_END, hook);
- hook.pending = false; // activates hook for next test
- return fn(new Error('abort hookDown'));
- } else if (name === HOOK_TYPE_BEFORE_ALL) {
- suite.tests.forEach(function (test) {
- test.pending = true;
- });
- suite.suites.forEach(function (suite) {
- suite.pending = true;
- });
- hooks = [];
- } else {
- hook.pending = false;
- var errForbid = createUnsupportedError('`this.skip` forbidden');
- self.fail(hook, errForbid);
- return fn(errForbid);
- }
- } else if (err) {
- self.fail(hook, err);
- // stop executing hooks, notify callee of hook err
- return fn(err);
- }
- self.emit(constants.EVENT_HOOK_END, hook);
- delete hook.ctx.currentTest;
- setHookTitle(hook);
- next(++i);
- });
- function setHookTitle(hook) {
- hook.originalTitle = hook.originalTitle || hook.title;
- if (hook.ctx && hook.ctx.currentTest) {
- hook.title = `${hook.originalTitle} for "${hook.ctx.currentTest.title}"`;
- } else {
- var parentTitle;
- if (hook.parent.title) {
- parentTitle = hook.parent.title;
- } else {
- parentTitle = hook.parent.root ? '{root}' : '';
- }
- hook.title = `${hook.originalTitle} in "${parentTitle}"`;
- }
- }
- }
- Runner.immediately(function () {
- next(0);
- });
- };
- /**
- * Run hook `name` for the given array of `suites`
- * in order, and callback `fn(err, errSuite)`.
- *
- * @private
- * @param {string} name
- * @param {Array} suites
- * @param {Function} fn
- */
- Runner.prototype.hooks = function (name, suites, fn) {
- var self = this;
- var orig = this.suite;
- function next(suite) {
- self.suite = suite;
- if (!suite) {
- self.suite = orig;
- return fn();
- }
- self.hook(name, function (err) {
- if (err) {
- var errSuite = self.suite;
- self.suite = orig;
- return fn(err, errSuite);
- }
- next(suites.pop());
- });
- }
- next(suites.pop());
- };
- /**
- * Run 'afterEach' hooks from bottom up.
- *
- * @param {String} name
- * @param {Function} fn
- * @private
- */
- Runner.prototype.hookUp = function (name, fn) {
- var suites = [this.suite].concat(this.parents()).reverse();
- this.hooks(name, suites, fn);
- };
- /**
- * Run 'beforeEach' hooks from top level down.
- *
- * @param {String} name
- * @param {Function} fn
- * @private
- */
- Runner.prototype.hookDown = function (name, fn) {
- var suites = [this.suite].concat(this.parents());
- this.hooks(name, suites, fn);
- };
- /**
- * Return an array of parent Suites from
- * closest to furthest.
- *
- * @return {Array}
- * @private
- */
- Runner.prototype.parents = function () {
- var suite = this.suite;
- var suites = [];
- while (suite.parent) {
- suite = suite.parent;
- suites.push(suite);
- }
- return suites;
- };
- /**
- * Run the current test and callback `fn(err)`.
- *
- * @param {Function} fn
- * @private
- */
- Runner.prototype.runTest = function (fn) {
- if (this._opts.dryRun) return Runner.immediately(fn);
- var self = this;
- var test = this.test;
- if (!test) {
- return;
- }
- if (this.asyncOnly) {
- test.asyncOnly = true;
- }
- this._addEventListener(test, 'error', function (err) {
- self.fail(test, err);
- });
- if (this.allowUncaught) {
- test.allowUncaught = true;
- return test.run(fn);
- }
- try {
- test.run(fn);
- } catch (err) {
- fn(err);
- }
- };
- /**
- * Run tests in the given `suite` and invoke the callback `fn()` when complete.
- *
- * @private
- * @param {Suite} suite
- * @param {Function} fn
- */
- Runner.prototype.runTests = function (suite, fn) {
- var self = this;
- var tests = suite.tests.slice();
- var test;
- function hookErr(_, errSuite, after) {
- // before/after Each hook for errSuite failed:
- var orig = self.suite;
- // for failed 'after each' hook start from errSuite parent,
- // otherwise start from errSuite itself
- self.suite = after ? errSuite.parent : errSuite;
- if (self.suite) {
- self.hookUp(HOOK_TYPE_AFTER_EACH, function (err2, errSuite2) {
- self.suite = orig;
- // some hooks may fail even now
- if (err2) {
- return hookErr(err2, errSuite2, true);
- }
- // report error suite
- fn(errSuite);
- });
- } else {
- // there is no need calling other 'after each' hooks
- self.suite = orig;
- fn(errSuite);
- }
- }
- function next(err, errSuite) {
- // if we bail after first err
- if (self.failures && suite._bail) {
- tests = [];
- }
- if (self._abort) {
- return fn();
- }
- if (err) {
- return hookErr(err, errSuite, true);
- }
- // next test
- test = tests.shift();
- // all done
- if (!test) {
- return fn();
- }
- // grep
- var match = self._grep.test(test.fullTitle());
- if (self._invert) {
- match = !match;
- }
- if (!match) {
- // Run immediately only if we have defined a grep. When we
- // define a grep — It can cause maximum callstack error if
- // the grep is doing a large recursive loop by neglecting
- // all tests. The run immediately function also comes with
- // a performance cost. So we don't want to run immediately
- // if we run the whole test suite, because running the whole
- // test suite don't do any immediate recursive loops. Thus,
- // allowing a JS runtime to breathe.
- if (self._grep !== self._defaultGrep) {
- Runner.immediately(next);
- } else {
- next();
- }
- return;
- }
- // static skip, no hooks are executed
- if (test.isPending()) {
- if (self.forbidPending) {
- self.fail(test, new Error('Pending test forbidden'), true);
- } else {
- test.state = STATE_PENDING;
- self.emit(constants.EVENT_TEST_PENDING, test);
- }
- self.emit(constants.EVENT_TEST_END, test);
- return next();
- }
- // execute test and hook(s)
- self.emit(constants.EVENT_TEST_BEGIN, (self.test = test));
- self.hookDown(HOOK_TYPE_BEFORE_EACH, function (err, errSuite) {
- // conditional skip within beforeEach
- if (test.isPending()) {
- if (self.forbidPending) {
- self.fail(test, new Error('Pending test forbidden'), true);
- } else {
- test.state = STATE_PENDING;
- self.emit(constants.EVENT_TEST_PENDING, test);
- }
- self.emit(constants.EVENT_TEST_END, test);
- // skip inner afterEach hooks below errSuite level
- var origSuite = self.suite;
- self.suite = errSuite || self.suite;
- return self.hookUp(HOOK_TYPE_AFTER_EACH, function (e, eSuite) {
- self.suite = origSuite;
- next(e, eSuite);
- });
- }
- if (err) {
- return hookErr(err, errSuite, false);
- }
- self.currentRunnable = self.test;
- self.runTest(function (err) {
- test = self.test;
- // conditional skip within it
- if (test.pending) {
- if (self.forbidPending) {
- self.fail(test, new Error('Pending test forbidden'), true);
- } else {
- test.state = STATE_PENDING;
- self.emit(constants.EVENT_TEST_PENDING, test);
- }
- self.emit(constants.EVENT_TEST_END, test);
- return self.hookUp(HOOK_TYPE_AFTER_EACH, next);
- } else if (err) {
- var retry = test.currentRetry();
- if (retry < test.retries()) {
- var clonedTest = test.clone();
- clonedTest.currentRetry(retry + 1);
- tests.unshift(clonedTest);
- self.emit(constants.EVENT_TEST_RETRY, test, err);
- // Early return + hook trigger so that it doesn't
- // increment the count wrong
- return self.hookUp(HOOK_TYPE_AFTER_EACH, next);
- } else {
- self.fail(test, err);
- }
- self.emit(constants.EVENT_TEST_END, test);
- return self.hookUp(HOOK_TYPE_AFTER_EACH, next);
- }
- test.state = STATE_PASSED;
- self.emit(constants.EVENT_TEST_PASS, test);
- self.emit(constants.EVENT_TEST_END, test);
- self.hookUp(HOOK_TYPE_AFTER_EACH, next);
- });
- });
- }
- this.next = next;
- this.hookErr = hookErr;
- next();
- };
- /**
- * Run the given `suite` and invoke the callback `fn()` when complete.
- *
- * @private
- * @param {Suite} suite
- * @param {Function} fn
- */
- Runner.prototype.runSuite = function (suite, fn) {
- var i = 0;
- var self = this;
- var total = this.grepTotal(suite);
- debug('runSuite(): running %s', suite.fullTitle());
- if (!total || (self.failures && suite._bail)) {
- debug('runSuite(): bailing');
- return fn();
- }
- this.emit(constants.EVENT_SUITE_BEGIN, (this.suite = suite));
- function next(errSuite) {
- if (errSuite) {
- // current suite failed on a hook from errSuite
- if (errSuite === suite) {
- // if errSuite is current suite
- // continue to the next sibling suite
- return done();
- }
- // errSuite is among the parents of current suite
- // stop execution of errSuite and all sub-suites
- return done(errSuite);
- }
- if (self._abort) {
- return done();
- }
- var curr = suite.suites[i++];
- if (!curr) {
- return done();
- }
- // Avoid grep neglecting large number of tests causing a
- // huge recursive loop and thus a maximum call stack error.
- // See comment in `this.runTests()` for more information.
- if (self._grep !== self._defaultGrep) {
- Runner.immediately(function () {
- self.runSuite(curr, next);
- });
- } else {
- self.runSuite(curr, next);
- }
- }
- function done(errSuite) {
- self.suite = suite;
- self.nextSuite = next;
- // remove reference to test
- delete self.test;
- self.hook(HOOK_TYPE_AFTER_ALL, function () {
- self.emit(constants.EVENT_SUITE_END, suite);
- fn(errSuite);
- });
- }
- this.nextSuite = next;
- this.hook(HOOK_TYPE_BEFORE_ALL, function (err) {
- if (err) {
- return done();
- }
- self.runTests(suite, next);
- });
- };
- /**
- * Handle uncaught exceptions within runner.
- *
- * This function is bound to the instance as `Runner#uncaught` at instantiation
- * time. It's intended to be listening on the `Process.uncaughtException` event.
- * In order to not leak EE listeners, we need to ensure no more than a single
- * `uncaughtException` listener exists per `Runner`. The only way to do
- * this--because this function needs the context (and we don't have lambdas)--is
- * to use `Function.prototype.bind`. We need strict equality to unregister and
- * _only_ unregister the _one_ listener we set from the
- * `Process.uncaughtException` event; would be poor form to just remove
- * everything. See {@link Runner#run} for where the event listener is registered
- * and unregistered.
- * @param {Error} err - Some uncaught error
- * @private
- */
- Runner.prototype._uncaught = function (err) {
- // this is defensive to prevent future developers from mis-calling this function.
- // it's more likely that it'd be called with the incorrect context--say, the global
- // `process` object--than it would to be called with a context that is not a "subclass"
- // of `Runner`.
- if (!(this instanceof Runner)) {
- throw createFatalError(
- 'Runner#uncaught() called with invalid context',
- this
- );
- }
- if (err instanceof Pending) {
- debug('uncaught(): caught a Pending');
- return;
- }
- // browser does not exit script when throwing in global.onerror()
- if (this.allowUncaught && !utils.isBrowser()) {
- debug('uncaught(): bubbling exception due to --allow-uncaught');
- throw err;
- }
- if (this.state === constants.STATE_STOPPED) {
- debug('uncaught(): throwing after run has completed!');
- throw err;
- }
- if (err) {
- debug('uncaught(): got truthy exception %O', err);
- } else {
- debug('uncaught(): undefined/falsy exception');
- err = createInvalidExceptionError(
- 'Caught falsy/undefined exception which would otherwise be uncaught. No stack trace found; try a debugger',
- err
- );
- }
- if (!isError(err)) {
- err = thrown2Error(err);
- debug('uncaught(): converted "error" %o to Error', err);
- }
- err.uncaught = true;
- var runnable = this.currentRunnable;
- if (!runnable) {
- runnable = new Runnable('Uncaught error outside test suite');
- debug('uncaught(): no current Runnable; created a phony one');
- runnable.parent = this.suite;
- if (this.state === constants.STATE_RUNNING) {
- debug('uncaught(): failing gracefully');
- this.fail(runnable, err);
- } else {
- // Can't recover from this failure
- debug('uncaught(): test run has not yet started; unrecoverable');
- this.emit(constants.EVENT_RUN_BEGIN);
- this.fail(runnable, err);
- this.emit(constants.EVENT_RUN_END);
- }
- return;
- }
- runnable.clearTimeout();
- if (runnable.isFailed()) {
- debug('uncaught(): Runnable has already failed');
- // Ignore error if already failed
- return;
- } else if (runnable.isPending()) {
- debug('uncaught(): pending Runnable wound up failing!');
- // report 'pending test' retrospectively as failed
- this.fail(runnable, err, true);
- return;
- }
- // we cannot recover gracefully if a Runnable has already passed
- // then fails asynchronously
- if (runnable.isPassed()) {
- debug('uncaught(): Runnable has already passed; bailing gracefully');
- this.fail(runnable, err);
- this.abort();
- } else {
- debug('uncaught(): forcing Runnable to complete with Error');
- return runnable.callback(err);
- }
- };
- /**
- * Run the root suite and invoke `fn(failures)`
- * on completion.
- *
- * @public
- * @memberof Runner
- * @param {Function} fn - Callback when finished
- * @param {RunnerOptions} [opts] - For subclasses
- * @returns {Runner} Runner instance.
- */
- Runner.prototype.run = function (fn, opts = {}) {
- var rootSuite = this.suite;
- var options = opts.options || {};
- debug('run(): got options: %O', options);
- fn = fn || function () {};
- const end = () => {
- if (!this.total && this._opts.failZero) this.failures = 1;
- debug('run(): root suite completed; emitting %s', constants.EVENT_RUN_END);
- this.emit(constants.EVENT_RUN_END);
- };
- const begin = () => {
- debug('run(): emitting %s', constants.EVENT_RUN_BEGIN);
- this.emit(constants.EVENT_RUN_BEGIN);
- debug('run(): emitted %s', constants.EVENT_RUN_BEGIN);
- this.runSuite(rootSuite, end);
- };
- const prepare = () => {
- debug('run(): starting');
- // If there is an `only` filter
- if (rootSuite.hasOnly()) {
- rootSuite.filterOnly();
- debug('run(): filtered exclusive Runnables');
- }
- this.state = constants.STATE_RUNNING;
- if (this._opts.delay) {
- this.emit(constants.EVENT_DELAY_END);
- debug('run(): "delay" ended');
- }
- return begin();
- };
- // references cleanup to avoid memory leaks
- if (this._opts.cleanReferencesAfterRun) {
- this.on(constants.EVENT_SUITE_END, suite => {
- suite.cleanReferences();
- });
- }
- // callback
- this.on(constants.EVENT_RUN_END, function () {
- this.state = constants.STATE_STOPPED;
- debug('run(): emitted %s', constants.EVENT_RUN_END);
- fn(this.failures);
- });
- this._removeEventListener(process, 'uncaughtException', this.uncaught);
- this._removeEventListener(process, 'unhandledRejection', this.unhandled);
- this._addEventListener(process, 'uncaughtException', this.uncaught);
- this._addEventListener(process, 'unhandledRejection', this.unhandled);
- if (this._opts.delay) {
- // for reporters, I guess.
- // might be nice to debounce some dots while we wait.
- this.emit(constants.EVENT_DELAY_BEGIN, rootSuite);
- rootSuite.once(EVENT_ROOT_SUITE_RUN, prepare);
- debug('run(): waiting for green light due to --delay');
- } else {
- Runner.immediately(prepare);
- }
- return this;
- };
- /**
- * Toggle partial object linking behavior; used for building object references from
- * unique ID's. Does nothing in serial mode, because the object references already exist.
- * Subclasses can implement this (e.g., `ParallelBufferedRunner`)
- * @abstract
- * @param {boolean} [value] - If `true`, enable partial object linking, otherwise disable
- * @returns {Runner}
- * @chainable
- * @public
- * @example
- * // this reporter needs proper object references when run in parallel mode
- * class MyReporter {
- * constructor(runner) {
- * runner.linkPartialObjects(true)
- * .on(EVENT_SUITE_BEGIN, suite => {
- * // this Suite may be the same object...
- * })
- * .on(EVENT_TEST_BEGIN, test => {
- * // ...as the `test.parent` property
- * });
- * }
- * }
- */
- Runner.prototype.linkPartialObjects = function () {
- return this;
- };
- /*
- * Like {@link Runner#run}, but does not accept a callback and returns a `Promise` instead of a `Runner`.
- * This function cannot reject; an `unhandledRejection` event will bubble up to the `process` object instead.
- * @public
- * @memberof Runner
- * @param {Object} [opts] - Options for {@link Runner#run}
- * @returns {Promise<number>} Failure count
- */
- Runner.prototype.runAsync = async function runAsync(opts = {}) {
- return new Promise(resolve => {
- this.run(resolve, opts);
- });
- };
- /**
- * Cleanly abort execution.
- *
- * @memberof Runner
- * @public
- * @return {Runner} Runner instance.
- */
- Runner.prototype.abort = function () {
- debug('abort(): aborting');
- this._abort = true;
- return this;
- };
- /**
- * Returns `true` if Mocha is running in parallel mode. For reporters.
- *
- * Subclasses should return an appropriate value.
- * @public
- * @returns {false}
- */
- Runner.prototype.isParallelMode = function isParallelMode() {
- return false;
- };
- /**
- * Configures an alternate reporter for worker processes to use. Subclasses
- * using worker processes should implement this.
- * @public
- * @param {string} path - Absolute path to alternate reporter for worker processes to use
- * @returns {Runner}
- * @throws When in serial mode
- * @chainable
- * @abstract
- */
- Runner.prototype.workerReporter = function () {
- throw createUnsupportedError('workerReporter() not supported in serial mode');
- };
- /**
- * Filter leaks with the given globals flagged as `ok`.
- *
- * @private
- * @param {Array} ok
- * @param {Array} globals
- * @return {Array}
- */
- function filterLeaks(ok, globals) {
- return globals.filter(function (key) {
- // Firefox and Chrome exposes iframes as index inside the window object
- if (/^\d+/.test(key)) {
- return false;
- }
- // in firefox
- // if runner runs in an iframe, this iframe's window.getInterface method
- // not init at first it is assigned in some seconds
- if (global.navigator && /^getInterface/.test(key)) {
- return false;
- }
- // an iframe could be approached by window[iframeIndex]
- // in ie6,7,8 and opera, iframeIndex is enumerable, this could cause leak
- if (global.navigator && /^\d+/.test(key)) {
- return false;
- }
- // Opera and IE expose global variables for HTML element IDs (issue #243)
- if (/^mocha-/.test(key)) {
- return false;
- }
- var matched = ok.filter(function (ok) {
- if (~ok.indexOf('*')) {
- return key.indexOf(ok.split('*')[0]) === 0;
- }
- return key === ok;
- });
- return !matched.length && (!global.navigator || key !== 'onerror');
- });
- }
- /**
- * Check if argument is an instance of Error object or a duck-typed equivalent.
- *
- * @private
- * @param {Object} err - object to check
- * @param {string} err.message - error message
- * @returns {boolean}
- */
- function isError(err) {
- return err instanceof Error || (err && typeof err.message === 'string');
- }
- /**
- *
- * Converts thrown non-extensible type into proper Error.
- *
- * @private
- * @param {*} thrown - Non-extensible type thrown by code
- * @return {Error}
- */
- function thrown2Error(err) {
- return new Error(
- `the ${utils.canonicalType(err)} ${stringify(
- err
- )} was thrown, throw an Error :)`
- );
- }
- Runner.constants = constants;
- /**
- * Node.js' `EventEmitter`
- * @external EventEmitter
- * @see {@link https://nodejs.org/api/events.html#events_class_eventemitter}
- */
- module.exports = Runner;
|