runner.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894
  1. /**
  2. * Module dependencies.
  3. */
  4. var EventEmitter = require('events').EventEmitter;
  5. var Pending = require('./pending');
  6. var utils = require('./utils');
  7. var inherits = utils.inherits;
  8. var debug = require('debug')('mocha:runner');
  9. var Runnable = require('./runnable');
  10. var filter = utils.filter;
  11. var indexOf = utils.indexOf;
  12. var keys = utils.keys;
  13. var stackFilter = utils.stackTraceFilter();
  14. var stringify = utils.stringify;
  15. var type = utils.type;
  16. var undefinedError = utils.undefinedError;
  17. var isArray = utils.isArray;
  18. /**
  19. * Non-enumerable globals.
  20. */
  21. var globals = [
  22. 'setTimeout',
  23. 'clearTimeout',
  24. 'setInterval',
  25. 'clearInterval',
  26. 'XMLHttpRequest',
  27. 'Date',
  28. 'setImmediate',
  29. 'clearImmediate'
  30. ];
  31. /**
  32. * Expose `Runner`.
  33. */
  34. module.exports = Runner;
  35. /**
  36. * Initialize a `Runner` for the given `suite`.
  37. *
  38. * Events:
  39. *
  40. * - `start` execution started
  41. * - `end` execution complete
  42. * - `suite` (suite) test suite execution started
  43. * - `suite end` (suite) all tests (and sub-suites) have finished
  44. * - `test` (test) test execution started
  45. * - `test end` (test) test completed
  46. * - `hook` (hook) hook execution started
  47. * - `hook end` (hook) hook complete
  48. * - `pass` (test) test passed
  49. * - `fail` (test, err) test failed
  50. * - `pending` (test) test pending
  51. *
  52. * @api public
  53. * @param {Suite} suite Root suite
  54. * @param {boolean} [delay] Whether or not to delay execution of root suite
  55. * until ready.
  56. */
  57. function Runner(suite, delay) {
  58. var self = this;
  59. this._globals = [];
  60. this._abort = false;
  61. this._delay = delay;
  62. this.suite = suite;
  63. this.started = false;
  64. this.total = suite.total();
  65. this.failures = 0;
  66. this.on('test end', function(test) {
  67. self.checkGlobals(test);
  68. });
  69. this.on('hook end', function(hook) {
  70. self.checkGlobals(hook);
  71. });
  72. this._defaultGrep = /.*/;
  73. this.grep(this._defaultGrep);
  74. this.globals(this.globalProps().concat(extraGlobals()));
  75. }
  76. /**
  77. * Wrapper for setImmediate, process.nextTick, or browser polyfill.
  78. *
  79. * @param {Function} fn
  80. * @api private
  81. */
  82. Runner.immediately = global.setImmediate || process.nextTick;
  83. /**
  84. * Inherit from `EventEmitter.prototype`.
  85. */
  86. inherits(Runner, EventEmitter);
  87. /**
  88. * Run tests with full titles matching `re`. Updates runner.total
  89. * with number of tests matched.
  90. *
  91. * @param {RegExp} re
  92. * @param {Boolean} invert
  93. * @return {Runner} for chaining
  94. * @api public
  95. * @param {RegExp} re
  96. * @param {boolean} invert
  97. * @return {Runner} Runner instance.
  98. */
  99. Runner.prototype.grep = function(re, invert) {
  100. debug('grep %s', re);
  101. this._grep = re;
  102. this._invert = invert;
  103. this.total = this.grepTotal(this.suite);
  104. return this;
  105. };
  106. /**
  107. * Returns the number of tests matching the grep search for the
  108. * given suite.
  109. *
  110. * @param {Suite} suite
  111. * @return {Number}
  112. * @api public
  113. * @param {Suite} suite
  114. * @return {number}
  115. */
  116. Runner.prototype.grepTotal = function(suite) {
  117. var self = this;
  118. var total = 0;
  119. suite.eachTest(function(test) {
  120. var match = self._grep.test(test.fullTitle());
  121. if (self._invert) {
  122. match = !match;
  123. }
  124. if (match) {
  125. total++;
  126. }
  127. });
  128. return total;
  129. };
  130. /**
  131. * Return a list of global properties.
  132. *
  133. * @return {Array}
  134. * @api private
  135. */
  136. Runner.prototype.globalProps = function() {
  137. var props = keys(global);
  138. // non-enumerables
  139. for (var i = 0; i < globals.length; ++i) {
  140. if (~indexOf(props, globals[i])) {
  141. continue;
  142. }
  143. props.push(globals[i]);
  144. }
  145. return props;
  146. };
  147. /**
  148. * Allow the given `arr` of globals.
  149. *
  150. * @param {Array} arr
  151. * @return {Runner} for chaining
  152. * @api public
  153. * @param {Array} arr
  154. * @return {Runner} Runner instance.
  155. */
  156. Runner.prototype.globals = function(arr) {
  157. if (!arguments.length) {
  158. return this._globals;
  159. }
  160. debug('globals %j', arr);
  161. this._globals = this._globals.concat(arr);
  162. return this;
  163. };
  164. /**
  165. * Check for global variable leaks.
  166. *
  167. * @api private
  168. */
  169. Runner.prototype.checkGlobals = function(test) {
  170. if (this.ignoreLeaks) {
  171. return;
  172. }
  173. var ok = this._globals;
  174. var globals = this.globalProps();
  175. var leaks;
  176. if (test) {
  177. ok = ok.concat(test._allowedGlobals || []);
  178. }
  179. if (this.prevGlobalsLength === globals.length) {
  180. return;
  181. }
  182. this.prevGlobalsLength = globals.length;
  183. leaks = filterLeaks(ok, globals);
  184. this._globals = this._globals.concat(leaks);
  185. if (leaks.length > 1) {
  186. this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + ''));
  187. } else if (leaks.length) {
  188. this.fail(test, new Error('global leak detected: ' + leaks[0]));
  189. }
  190. };
  191. /**
  192. * Fail the given `test`.
  193. *
  194. * @api private
  195. * @param {Test} test
  196. * @param {Error} err
  197. */
  198. Runner.prototype.fail = function(test, err) {
  199. ++this.failures;
  200. test.state = 'failed';
  201. if (!(err instanceof Error || err && typeof err.message === 'string')) {
  202. err = new Error('the ' + type(err) + ' ' + stringify(err) + ' was thrown, throw an Error :)');
  203. }
  204. err.stack = (this.fullStackTrace || !err.stack)
  205. ? err.stack
  206. : stackFilter(err.stack);
  207. this.emit('fail', test, err);
  208. };
  209. /**
  210. * Fail the given `hook` with `err`.
  211. *
  212. * Hook failures work in the following pattern:
  213. * - If bail, then exit
  214. * - Failed `before` hook skips all tests in a suite and subsuites,
  215. * but jumps to corresponding `after` hook
  216. * - Failed `before each` hook skips remaining tests in a
  217. * suite and jumps to corresponding `after each` hook,
  218. * which is run only once
  219. * - Failed `after` hook does not alter
  220. * execution order
  221. * - Failed `after each` hook skips remaining tests in a
  222. * suite and subsuites, but executes other `after each`
  223. * hooks
  224. *
  225. * @api private
  226. * @param {Hook} hook
  227. * @param {Error} err
  228. */
  229. Runner.prototype.failHook = function(hook, err) {
  230. if (hook.ctx && hook.ctx.currentTest) {
  231. hook.originalTitle = hook.originalTitle || hook.title;
  232. hook.title = hook.originalTitle + ' for "' + hook.ctx.currentTest.title + '"';
  233. }
  234. this.fail(hook, err);
  235. if (this.suite.bail()) {
  236. this.emit('end');
  237. }
  238. };
  239. /**
  240. * Run hook `name` callbacks and then invoke `fn()`.
  241. *
  242. * @api private
  243. * @param {string} name
  244. * @param {Function} fn
  245. */
  246. Runner.prototype.hook = function(name, fn) {
  247. var suite = this.suite;
  248. var hooks = suite['_' + name];
  249. var self = this;
  250. function next(i) {
  251. var hook = hooks[i];
  252. if (!hook) {
  253. return fn();
  254. }
  255. self.currentRunnable = hook;
  256. hook.ctx.currentTest = self.test;
  257. self.emit('hook', hook);
  258. if (!hook.listeners('error').length) {
  259. hook.on('error', function(err) {
  260. self.failHook(hook, err);
  261. });
  262. }
  263. hook.run(function(err) {
  264. var testError = hook.error();
  265. if (testError) {
  266. self.fail(self.test, testError);
  267. }
  268. if (err) {
  269. if (err instanceof Pending) {
  270. suite.pending = true;
  271. } else {
  272. self.failHook(hook, err);
  273. // stop executing hooks, notify callee of hook err
  274. return fn(err);
  275. }
  276. }
  277. self.emit('hook end', hook);
  278. delete hook.ctx.currentTest;
  279. next(++i);
  280. });
  281. }
  282. Runner.immediately(function() {
  283. next(0);
  284. });
  285. };
  286. /**
  287. * Run hook `name` for the given array of `suites`
  288. * in order, and callback `fn(err, errSuite)`.
  289. *
  290. * @api private
  291. * @param {string} name
  292. * @param {Array} suites
  293. * @param {Function} fn
  294. */
  295. Runner.prototype.hooks = function(name, suites, fn) {
  296. var self = this;
  297. var orig = this.suite;
  298. function next(suite) {
  299. self.suite = suite;
  300. if (!suite) {
  301. self.suite = orig;
  302. return fn();
  303. }
  304. self.hook(name, function(err) {
  305. if (err) {
  306. var errSuite = self.suite;
  307. self.suite = orig;
  308. return fn(err, errSuite);
  309. }
  310. next(suites.pop());
  311. });
  312. }
  313. next(suites.pop());
  314. };
  315. /**
  316. * Run hooks from the top level down.
  317. *
  318. * @param {String} name
  319. * @param {Function} fn
  320. * @api private
  321. */
  322. Runner.prototype.hookUp = function(name, fn) {
  323. var suites = [this.suite].concat(this.parents()).reverse();
  324. this.hooks(name, suites, fn);
  325. };
  326. /**
  327. * Run hooks from the bottom up.
  328. *
  329. * @param {String} name
  330. * @param {Function} fn
  331. * @api private
  332. */
  333. Runner.prototype.hookDown = function(name, fn) {
  334. var suites = [this.suite].concat(this.parents());
  335. this.hooks(name, suites, fn);
  336. };
  337. /**
  338. * Return an array of parent Suites from
  339. * closest to furthest.
  340. *
  341. * @return {Array}
  342. * @api private
  343. */
  344. Runner.prototype.parents = function() {
  345. var suite = this.suite;
  346. var suites = [];
  347. while (suite.parent) {
  348. suite = suite.parent;
  349. suites.push(suite);
  350. }
  351. return suites;
  352. };
  353. /**
  354. * Run the current test and callback `fn(err)`.
  355. *
  356. * @param {Function} fn
  357. * @api private
  358. */
  359. Runner.prototype.runTest = function(fn) {
  360. var self = this;
  361. var test = this.test;
  362. if (this.asyncOnly) {
  363. test.asyncOnly = true;
  364. }
  365. if (this.allowUncaught) {
  366. test.allowUncaught = true;
  367. return test.run(fn);
  368. }
  369. try {
  370. test.on('error', function(err) {
  371. self.fail(test, err);
  372. });
  373. test.run(fn);
  374. } catch (err) {
  375. fn(err);
  376. }
  377. };
  378. /**
  379. * Run tests in the given `suite` and invoke the callback `fn()` when complete.
  380. *
  381. * @api private
  382. * @param {Suite} suite
  383. * @param {Function} fn
  384. */
  385. Runner.prototype.runTests = function(suite, fn) {
  386. var self = this;
  387. var tests = suite.tests.slice();
  388. var test;
  389. function hookErr(_, errSuite, after) {
  390. // before/after Each hook for errSuite failed:
  391. var orig = self.suite;
  392. // for failed 'after each' hook start from errSuite parent,
  393. // otherwise start from errSuite itself
  394. self.suite = after ? errSuite.parent : errSuite;
  395. if (self.suite) {
  396. // call hookUp afterEach
  397. self.hookUp('afterEach', function(err2, errSuite2) {
  398. self.suite = orig;
  399. // some hooks may fail even now
  400. if (err2) {
  401. return hookErr(err2, errSuite2, true);
  402. }
  403. // report error suite
  404. fn(errSuite);
  405. });
  406. } else {
  407. // there is no need calling other 'after each' hooks
  408. self.suite = orig;
  409. fn(errSuite);
  410. }
  411. }
  412. function next(err, errSuite) {
  413. // if we bail after first err
  414. if (self.failures && suite._bail) {
  415. return fn();
  416. }
  417. if (self._abort) {
  418. return fn();
  419. }
  420. if (err) {
  421. return hookErr(err, errSuite, true);
  422. }
  423. // next test
  424. test = tests.shift();
  425. // all done
  426. if (!test) {
  427. return fn();
  428. }
  429. // grep
  430. var match = self._grep.test(test.fullTitle());
  431. if (self._invert) {
  432. match = !match;
  433. }
  434. if (!match) {
  435. // Run immediately only if we have defined a grep. When we
  436. // define a grep — It can cause maximum callstack error if
  437. // the grep is doing a large recursive loop by neglecting
  438. // all tests. The run immediately function also comes with
  439. // a performance cost. So we don't want to run immediately
  440. // if we run the whole test suite, because running the whole
  441. // test suite don't do any immediate recursive loops. Thus,
  442. // allowing a JS runtime to breathe.
  443. if (self._grep !== self._defaultGrep) {
  444. Runner.immediately(next);
  445. } else {
  446. next();
  447. }
  448. return;
  449. }
  450. if (test.isPending()) {
  451. self.emit('pending', test);
  452. self.emit('test end', test);
  453. return next();
  454. }
  455. // execute test and hook(s)
  456. self.emit('test', self.test = test);
  457. self.hookDown('beforeEach', function(err, errSuite) {
  458. if (suite.isPending()) {
  459. self.emit('pending', test);
  460. self.emit('test end', test);
  461. return next();
  462. }
  463. if (err) {
  464. return hookErr(err, errSuite, false);
  465. }
  466. self.currentRunnable = self.test;
  467. self.runTest(function(err) {
  468. test = self.test;
  469. if (err) {
  470. var retry = test.currentRetry();
  471. if (err instanceof Pending) {
  472. test.pending = true;
  473. self.emit('pending', test);
  474. } else if (retry < test.retries()) {
  475. var clonedTest = test.clone();
  476. clonedTest.currentRetry(retry + 1);
  477. tests.unshift(clonedTest);
  478. // Early return + hook trigger so that it doesn't
  479. // increment the count wrong
  480. return self.hookUp('afterEach', next);
  481. } else {
  482. self.fail(test, err);
  483. }
  484. self.emit('test end', test);
  485. if (err instanceof Pending) {
  486. return next();
  487. }
  488. return self.hookUp('afterEach', next);
  489. }
  490. test.state = 'passed';
  491. self.emit('pass', test);
  492. self.emit('test end', test);
  493. self.hookUp('afterEach', next);
  494. });
  495. });
  496. }
  497. this.next = next;
  498. this.hookErr = hookErr;
  499. next();
  500. };
  501. /**
  502. * Run the given `suite` and invoke the callback `fn()` when complete.
  503. *
  504. * @api private
  505. * @param {Suite} suite
  506. * @param {Function} fn
  507. */
  508. Runner.prototype.runSuite = function(suite, fn) {
  509. var i = 0;
  510. var self = this;
  511. var total = this.grepTotal(suite);
  512. var afterAllHookCalled = false;
  513. debug('run suite %s', suite.fullTitle());
  514. if (!total || (self.failures && suite._bail)) {
  515. return fn();
  516. }
  517. this.emit('suite', this.suite = suite);
  518. function next(errSuite) {
  519. if (errSuite) {
  520. // current suite failed on a hook from errSuite
  521. if (errSuite === suite) {
  522. // if errSuite is current suite
  523. // continue to the next sibling suite
  524. return done();
  525. }
  526. // errSuite is among the parents of current suite
  527. // stop execution of errSuite and all sub-suites
  528. return done(errSuite);
  529. }
  530. if (self._abort) {
  531. return done();
  532. }
  533. var curr = suite.suites[i++];
  534. if (!curr) {
  535. return done();
  536. }
  537. // Avoid grep neglecting large number of tests causing a
  538. // huge recursive loop and thus a maximum call stack error.
  539. // See comment in `this.runTests()` for more information.
  540. if (self._grep !== self._defaultGrep) {
  541. Runner.immediately(function() {
  542. self.runSuite(curr, next);
  543. });
  544. } else {
  545. self.runSuite(curr, next);
  546. }
  547. }
  548. function done(errSuite) {
  549. self.suite = suite;
  550. self.nextSuite = next;
  551. if (afterAllHookCalled) {
  552. fn(errSuite);
  553. } else {
  554. // mark that the afterAll block has been called once
  555. // and so can be skipped if there is an error in it.
  556. afterAllHookCalled = true;
  557. // remove reference to test
  558. delete self.test;
  559. self.hook('afterAll', function() {
  560. self.emit('suite end', suite);
  561. fn(errSuite);
  562. });
  563. }
  564. }
  565. this.nextSuite = next;
  566. this.hook('beforeAll', function(err) {
  567. if (err) {
  568. return done();
  569. }
  570. self.runTests(suite, next);
  571. });
  572. };
  573. /**
  574. * Handle uncaught exceptions.
  575. *
  576. * @param {Error} err
  577. * @api private
  578. */
  579. Runner.prototype.uncaught = function(err) {
  580. if (err) {
  581. debug('uncaught exception %s', err !== function() {
  582. return this;
  583. }.call(err) ? err : (err.message || err));
  584. } else {
  585. debug('uncaught undefined exception');
  586. err = undefinedError();
  587. }
  588. err.uncaught = true;
  589. var runnable = this.currentRunnable;
  590. if (!runnable) {
  591. runnable = new Runnable('Uncaught error outside test suite');
  592. runnable.parent = this.suite;
  593. if (this.started) {
  594. this.fail(runnable, err);
  595. } else {
  596. // Can't recover from this failure
  597. this.emit('start');
  598. this.fail(runnable, err);
  599. this.emit('end');
  600. }
  601. return;
  602. }
  603. runnable.clearTimeout();
  604. // Ignore errors if complete
  605. if (runnable.state) {
  606. return;
  607. }
  608. this.fail(runnable, err);
  609. // recover from test
  610. if (runnable.type === 'test') {
  611. this.emit('test end', runnable);
  612. this.hookUp('afterEach', this.next);
  613. return;
  614. }
  615. // recover from hooks
  616. if (runnable.type === 'hook') {
  617. var errSuite = this.suite;
  618. // if hook failure is in afterEach block
  619. if (runnable.fullTitle().indexOf('after each') > -1) {
  620. return this.hookErr(err, errSuite, true);
  621. }
  622. // if hook failure is in beforeEach block
  623. if (runnable.fullTitle().indexOf('before each') > -1) {
  624. return this.hookErr(err, errSuite, false);
  625. }
  626. // if hook failure is in after or before blocks
  627. return this.nextSuite(errSuite);
  628. }
  629. // bail
  630. this.emit('end');
  631. };
  632. /**
  633. * Cleans up the references to all the deferred functions
  634. * (before/after/beforeEach/afterEach) and tests of a Suite.
  635. * These must be deleted otherwise a memory leak can happen,
  636. * as those functions may reference variables from closures,
  637. * thus those variables can never be garbage collected as long
  638. * as the deferred functions exist.
  639. *
  640. * @param {Suite} suite
  641. */
  642. function cleanSuiteReferences(suite) {
  643. function cleanArrReferences(arr) {
  644. for (var i = 0; i < arr.length; i++) {
  645. delete arr[i].fn;
  646. }
  647. }
  648. if (isArray(suite._beforeAll)) {
  649. cleanArrReferences(suite._beforeAll);
  650. }
  651. if (isArray(suite._beforeEach)) {
  652. cleanArrReferences(suite._beforeEach);
  653. }
  654. if (isArray(suite._afterAll)) {
  655. cleanArrReferences(suite._afterAll);
  656. }
  657. if (isArray(suite._afterEach)) {
  658. cleanArrReferences(suite._afterEach);
  659. }
  660. for (var i = 0; i < suite.tests.length; i++) {
  661. delete suite.tests[i].fn;
  662. }
  663. }
  664. /**
  665. * Run the root suite and invoke `fn(failures)`
  666. * on completion.
  667. *
  668. * @param {Function} fn
  669. * @return {Runner} for chaining
  670. * @api public
  671. * @param {Function} fn
  672. * @return {Runner} Runner instance.
  673. */
  674. Runner.prototype.run = function(fn) {
  675. var self = this;
  676. var rootSuite = this.suite;
  677. fn = fn || function() {};
  678. function uncaught(err) {
  679. self.uncaught(err);
  680. }
  681. function start() {
  682. self.started = true;
  683. self.emit('start');
  684. self.runSuite(rootSuite, function() {
  685. debug('finished running');
  686. self.emit('end');
  687. });
  688. }
  689. debug('start');
  690. // references cleanup to avoid memory leaks
  691. this.on('suite end', cleanSuiteReferences);
  692. // callback
  693. this.on('end', function() {
  694. debug('end');
  695. process.removeListener('uncaughtException', uncaught);
  696. fn(self.failures);
  697. });
  698. // uncaught exception
  699. process.on('uncaughtException', uncaught);
  700. if (this._delay) {
  701. // for reporters, I guess.
  702. // might be nice to debounce some dots while we wait.
  703. this.emit('waiting', rootSuite);
  704. rootSuite.once('run', start);
  705. } else {
  706. start();
  707. }
  708. return this;
  709. };
  710. /**
  711. * Cleanly abort execution.
  712. *
  713. * @api public
  714. * @return {Runner} Runner instance.
  715. */
  716. Runner.prototype.abort = function() {
  717. debug('aborting');
  718. this._abort = true;
  719. return this;
  720. };
  721. /**
  722. * Filter leaks with the given globals flagged as `ok`.
  723. *
  724. * @api private
  725. * @param {Array} ok
  726. * @param {Array} globals
  727. * @return {Array}
  728. */
  729. function filterLeaks(ok, globals) {
  730. return filter(globals, function(key) {
  731. // Firefox and Chrome exposes iframes as index inside the window object
  732. if (/^d+/.test(key)) {
  733. return false;
  734. }
  735. // in firefox
  736. // if runner runs in an iframe, this iframe's window.getInterface method not init at first
  737. // it is assigned in some seconds
  738. if (global.navigator && (/^getInterface/).test(key)) {
  739. return false;
  740. }
  741. // an iframe could be approached by window[iframeIndex]
  742. // in ie6,7,8 and opera, iframeIndex is enumerable, this could cause leak
  743. if (global.navigator && (/^\d+/).test(key)) {
  744. return false;
  745. }
  746. // Opera and IE expose global variables for HTML element IDs (issue #243)
  747. if (/^mocha-/.test(key)) {
  748. return false;
  749. }
  750. var matched = filter(ok, function(ok) {
  751. if (~ok.indexOf('*')) {
  752. return key.indexOf(ok.split('*')[0]) === 0;
  753. }
  754. return key === ok;
  755. });
  756. return !matched.length && (!global.navigator || key !== 'onerror');
  757. });
  758. }
  759. /**
  760. * Array of globals dependent on the environment.
  761. *
  762. * @return {Array}
  763. * @api private
  764. */
  765. function extraGlobals() {
  766. if (typeof process === 'object' && typeof process.version === 'string') {
  767. var parts = process.version.split('.');
  768. var nodeVersion = utils.reduce(parts, function(a, v) {
  769. return a << 8 | v;
  770. });
  771. // 'errno' was renamed to process._errno in v0.9.11.
  772. if (nodeVersion < 0x00090B) {
  773. return ['errno'];
  774. }
  775. }
  776. return [];
  777. }