| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009 | 'use strict';/** * @module Runner *//** * Module dependencies. */var EventEmitter = require('events').EventEmitter;var Pending = require('./pending');var utils = require('./utils');var inherits = utils.inherits;var debug = require('debug')('mocha:runner');var Runnable = require('./runnable');var stackFilter = utils.stackTraceFilter();var stringify = utils.stringify;var type = utils.type;var undefinedError = utils.undefinedError;/** * Non-enumerable globals. */var globals = [  'setTimeout',  'clearTimeout',  'setInterval',  'clearInterval',  'XMLHttpRequest',  'Date',  'setImmediate',  'clearImmediate'];/** * Expose `Runner`. */module.exports = Runner;/** * Initialize a `Runner` for the given `suite`. Derived from [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter) * * Events: * *   - `start`  execution started *   - `end`  execution complete *   - `suite`  (suite) test suite execution started *   - `suite end`  (suite) all tests (and sub-suites) have finished *   - `test`  (test) test execution started *   - `test end`  (test) test completed *   - `hook`  (hook) hook execution started *   - `hook end`  (hook) hook complete *   - `pass`  (test) test passed *   - `fail`  (test, err) test failed *   - `pending`  (test) test pending * * @memberof Mocha * @public * @class * @api public * @param {Suite} [suite] Root suite * @param {boolean} [delay] Whether or not to delay execution of root suite * until ready. */function Runner(suite, delay) {  var self = this;  this._globals = [];  this._abort = false;  this._delay = delay;  this.suite = suite;  this.started = false;  this.total = suite.total();  this.failures = 0;  this.on('test end', function(test) {    self.checkGlobals(test);  });  this.on('hook end', function(hook) {    self.checkGlobals(hook);  });  this._defaultGrep = /.*/;  this.grep(this._defaultGrep);  this.globals(this.globalProps().concat(extraGlobals()));}/** * Wrapper for setImmediate, process.nextTick, or browser polyfill. * * @param {Function} fn * @api private */Runner.immediately = global.setImmediate || process.nextTick;/** * Inherit from `EventEmitter.prototype`. */inherits(Runner, EventEmitter);/** * Run tests with full titles matching `re`. Updates runner.total * with number of tests matched. * * @api public * @public * @memberof Mocha.Runner * @param {RegExp} re * @param {boolean} invert * @return {Runner} Runner instance. */Runner.prototype.grep = function(re, invert) {  debug('grep %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 Mocha.Runner * @api public * @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} * @api 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. * * @api public * @public * @memberof Mocha.Runner * @param {Array} arr * @return {Runner} Runner instance. */Runner.prototype.globals = function(arr) {  if (!arguments.length) {    return this._globals;  }  debug('globals %j', arr);  this._globals = this._globals.concat(arr);  return this;};/** * Check for global variable leaks. * * @api private */Runner.prototype.checkGlobals = function(test) {  if (this.ignoreLeaks) {    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 > 1) {    this.fail(      test,      new Error('global leaks detected: ' + leaks.join(', ') + '')    );  } else if (leaks.length) {    this.fail(test, new Error('global leak detected: ' + leaks[0]));  }};/** * Fail the given `test`. * * @api private * @param {Test} test * @param {Error} err */Runner.prototype.fail = function(test, err) {  if (test.isPending()) {    return;  }  ++this.failures;  test.state = 'failed';  if (!(err instanceof Error || (err && typeof err.message === 'string'))) {    err = new Error(      'the ' +        type(err) +        ' ' +        stringify(err) +        ' was thrown, throw an Error :)'    );  }  try {    err.stack =      this.fullStackTrace || !err.stack ? err.stack : stackFilter(err.stack);  } catch (ignore) {    // some environments do not take kindly to monkeying with the stack  }  this.emit('fail', test, err);  if (this.suite.bail()) {    this.emit('end');  }};/** * Fail the given `hook` with `err`. * * Hook failures work in the following pattern: * - If bail, 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 * * @api private * @param {Hook} hook * @param {Error} err */Runner.prototype.failHook = function(hook, err) {  if (hook.ctx && hook.ctx.currentTest) {    hook.originalTitle = hook.originalTitle || hook.title;    hook.title =      hook.originalTitle + ' for "' + hook.ctx.currentTest.title + '"';  }  this.fail(hook, err);};/** * Run hook `name` callbacks and then invoke `fn()`. * * @api private * @param {string} name * @param {Function} fn */Runner.prototype.hook = function(name, fn) {  var suite = this.suite;  var hooks = suite['_' + name];  var self = this;  function next(i) {    var hook = hooks[i];    if (!hook) {      return fn();    }    self.currentRunnable = hook;    hook.ctx.currentTest = self.test;    self.emit('hook', hook);    if (!hook.listeners('error').length) {      hook.on('error', function(err) {        self.failHook(hook, err);      });    }    hook.run(function(err) {      var testError = hook.error();      if (testError) {        self.fail(self.test, testError);      }      if (err) {        if (err instanceof Pending) {          if (name === 'beforeEach' || name === 'afterEach') {            self.test.pending = true;          } else {            suite.tests.forEach(function(test) {              test.pending = true;            });            // a pending hook won't be executed twice.            hook.pending = true;          }        } else {          self.failHook(hook, err);          // stop executing hooks, notify callee of hook err          return fn(err);        }      }      self.emit('hook end', hook);      delete hook.ctx.currentTest;      next(++i);    });  }  Runner.immediately(function() {    next(0);  });};/** * Run hook `name` for the given array of `suites` * in order, and callback `fn(err, errSuite)`. * * @api 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 hooks from the top level down. * * @param {String} name * @param {Function} fn * @api private */Runner.prototype.hookUp = function(name, fn) {  var suites = [this.suite].concat(this.parents()).reverse();  this.hooks(name, suites, fn);};/** * Run hooks from the bottom up. * * @param {String} name * @param {Function} fn * @api 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} * @api 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 * @api private */Runner.prototype.runTest = function(fn) {  var self = this;  var test = this.test;  if (!test) {    return;  }  if (this.forbidOnly && hasOnly(this.parents().reverse()[0] || this.suite)) {    fn(new Error('`.only` forbidden'));    return;  }  if (this.asyncOnly) {    test.asyncOnly = true;  }  test.on('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. * * @api 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) {      // call hookUp afterEach      self.hookUp('afterEach', 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) {      return fn();    }    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;    }    if (test.isPending()) {      if (self.forbidPending) {        test.isPending = alwaysFalse;        self.fail(test, new Error('Pending test forbidden'));        delete test.isPending;      } else {        self.emit('pending', test);      }      self.emit('test end', test);      return next();    }    // execute test and hook(s)    self.emit('test', (self.test = test));    self.hookDown('beforeEach', function(err, errSuite) {      if (test.isPending()) {        if (self.forbidPending) {          test.isPending = alwaysFalse;          self.fail(test, new Error('Pending test forbidden'));          delete test.isPending;        } else {          self.emit('pending', test);        }        self.emit('test end', test);        return next();      }      if (err) {        return hookErr(err, errSuite, false);      }      self.currentRunnable = self.test;      self.runTest(function(err) {        test = self.test;        if (err) {          var retry = test.currentRetry();          if (err instanceof Pending && self.forbidPending) {            self.fail(test, new Error('Pending test forbidden'));          } else if (err instanceof Pending) {            test.pending = true;            self.emit('pending', test);          } else if (retry < test.retries()) {            var clonedTest = test.clone();            clonedTest.currentRetry(retry + 1);            tests.unshift(clonedTest);            // Early return + hook trigger so that it doesn't            // increment the count wrong            return self.hookUp('afterEach', next);          } else {            self.fail(test, err);          }          self.emit('test end', test);          if (err instanceof Pending) {            return next();          }          return self.hookUp('afterEach', next);        }        test.state = 'passed';        self.emit('pass', test);        self.emit('test end', test);        self.hookUp('afterEach', next);      });    });  }  this.next = next;  this.hookErr = hookErr;  next();};function alwaysFalse() {  return false;}/** * Run the given `suite` and invoke the callback `fn()` when complete. * * @api private * @param {Suite} suite * @param {Function} fn */Runner.prototype.runSuite = function(suite, fn) {  var i = 0;  var self = this;  var total = this.grepTotal(suite);  var afterAllHookCalled = false;  debug('run suite %s', suite.fullTitle());  if (!total || (self.failures && suite._bail)) {    return fn();  }  this.emit('suite', (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;    if (afterAllHookCalled) {      fn(errSuite);    } else {      // mark that the afterAll block has been called once      // and so can be skipped if there is an error in it.      afterAllHookCalled = true;      // remove reference to test      delete self.test;      self.hook('afterAll', function() {        self.emit('suite end', suite);        fn(errSuite);      });    }  }  this.nextSuite = next;  this.hook('beforeAll', function(err) {    if (err) {      return done();    }    self.runTests(suite, next);  });};/** * Handle uncaught exceptions. * * @param {Error} err * @api private */Runner.prototype.uncaught = function(err) {  if (err) {    debug(      'uncaught exception %s',      err ===      function() {        return this;      }.call(err)        ? err.message || err        : err    );  } else {    debug('uncaught undefined exception');    err = undefinedError();  }  err.uncaught = true;  var runnable = this.currentRunnable;  if (!runnable) {    runnable = new Runnable('Uncaught error outside test suite');    runnable.parent = this.suite;    if (this.started) {      this.fail(runnable, err);    } else {      // Can't recover from this failure      this.emit('start');      this.fail(runnable, err);      this.emit('end');    }    return;  }  runnable.clearTimeout();  // Ignore errors if already failed or pending  // See #3226  if (runnable.isFailed() || runnable.isPending()) {    return;  }  // we cannot recover gracefully if a Runnable has already passed  // then fails asynchronously  var alreadyPassed = runnable.isPassed();  // this will change the state to "failed" regardless of the current value  this.fail(runnable, err);  if (!alreadyPassed) {    // recover from test    if (runnable.type === 'test') {      this.emit('test end', runnable);      this.hookUp('afterEach', this.next);      return;    }    // recover from hooks    var errSuite = this.suite;    // if hook failure is in afterEach block    if (runnable.fullTitle().indexOf('after each') > -1) {      return this.hookErr(err, errSuite, true);    }    // if hook failure is in beforeEach block    if (runnable.fullTitle().indexOf('before each') > -1) {      return this.hookErr(err, errSuite, false);    }    // if hook failure is in after or before blocks    return this.nextSuite(errSuite);  }  // bail  this.emit('end');};/** * Cleans up the references to all the deferred functions * (before/after/beforeEach/afterEach) and tests of a Suite. * These must be deleted otherwise a memory leak can happen, * as those functions may reference variables from closures, * thus those variables can never be garbage collected as long * as the deferred functions exist. * * @param {Suite} suite */function cleanSuiteReferences(suite) {  function cleanArrReferences(arr) {    for (var i = 0; i < arr.length; i++) {      delete arr[i].fn;    }  }  if (Array.isArray(suite._beforeAll)) {    cleanArrReferences(suite._beforeAll);  }  if (Array.isArray(suite._beforeEach)) {    cleanArrReferences(suite._beforeEach);  }  if (Array.isArray(suite._afterAll)) {    cleanArrReferences(suite._afterAll);  }  if (Array.isArray(suite._afterEach)) {    cleanArrReferences(suite._afterEach);  }  for (var i = 0; i < suite.tests.length; i++) {    delete suite.tests[i].fn;  }}/** * Run the root suite and invoke `fn(failures)` * on completion. * * @api public * @public * @memberof Mocha.Runner * @param {Function} fn * @return {Runner} Runner instance. */Runner.prototype.run = function(fn) {  var self = this;  var rootSuite = this.suite;  fn = fn || function() {};  function uncaught(err) {    self.uncaught(err);  }  function start() {    // If there is an `only` filter    if (hasOnly(rootSuite)) {      filterOnly(rootSuite);    }    self.started = true;    self.emit('start');    self.runSuite(rootSuite, function() {      debug('finished running');      self.emit('end');    });  }  debug('start');  // references cleanup to avoid memory leaks  this.on('suite end', cleanSuiteReferences);  // callback  this.on('end', function() {    debug('end');    process.removeListener('uncaughtException', uncaught);    fn(self.failures);  });  // uncaught exception  process.on('uncaughtException', uncaught);  if (this._delay) {    // for reporters, I guess.    // might be nice to debounce some dots while we wait.    this.emit('waiting', rootSuite);    rootSuite.once('run', start);  } else {    start();  }  return this;};/** * Cleanly abort execution. * * @memberof Mocha.Runner * @public * @api public * @return {Runner} Runner instance. */Runner.prototype.abort = function() {  debug('aborting');  this._abort = true;  return this;};/** * Filter suites based on `isOnly` logic. * * @param {Array} suite * @returns {Boolean} * @api private */function filterOnly(suite) {  if (suite._onlyTests.length) {    // If the suite contains `only` tests, run those and ignore any nested suites.    suite.tests = suite._onlyTests;    suite.suites = [];  } else {    // Otherwise, do not run any of the tests in this suite.    suite.tests = [];    suite._onlySuites.forEach(function(onlySuite) {      // If there are other `only` tests/suites nested in the current `only` suite, then filter that `only` suite.      // Otherwise, all of the tests on this `only` suite should be run, so don't filter it.      if (hasOnly(onlySuite)) {        filterOnly(onlySuite);      }    });    // Run the `only` suites, as well as any other suites that have `only` tests/suites as descendants.    suite.suites = suite.suites.filter(function(childSuite) {      return (        suite._onlySuites.indexOf(childSuite) !== -1 || filterOnly(childSuite)      );    });  }  // Keep the suite only if there is something to run  return suite.tests.length || suite.suites.length;}/** * Determines whether a suite has an `only` test or suite as a descendant. * * @param {Array} suite * @returns {Boolean} * @api private */function hasOnly(suite) {  return (    suite._onlyTests.length ||    suite._onlySuites.length ||    suite.suites.some(hasOnly)  );}/** * Filter leaks with the given globals flagged as `ok`. * * @api 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');  });}/** * Array of globals dependent on the environment. * * @return {Array} * @api private */function extraGlobals() {  if (typeof process === 'object' && typeof process.version === 'string') {    var parts = process.version.split('.');    var nodeVersion = parts.reduce(function(a, v) {      return (a << 8) | v;    });    // 'errno' was renamed to process._errno in v0.9.11.    if (nodeVersion < 0x00090b) {      return ['errno'];    }  }  return [];}
 |