runner.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. /**
  2. * Module dependencies.
  3. */
  4. var EventEmitter = require('events').EventEmitter
  5. , debug = require('debug')('mocha:runner')
  6. , Test = require('./test')
  7. , utils = require('./utils')
  8. , filter = utils.filter
  9. , keys = utils.keys;
  10. /**
  11. * Non-enumerable globals.
  12. */
  13. var globals = [
  14. 'setTimeout',
  15. 'clearTimeout',
  16. 'setInterval',
  17. 'clearInterval',
  18. 'XMLHttpRequest',
  19. 'Date'
  20. ];
  21. /**
  22. * Expose `Runner`.
  23. */
  24. module.exports = Runner;
  25. /**
  26. * Initialize a `Runner` for the given `suite`.
  27. *
  28. * Events:
  29. *
  30. * - `start` execution started
  31. * - `end` execution complete
  32. * - `suite` (suite) test suite execution started
  33. * - `suite end` (suite) all tests (and sub-suites) have finished
  34. * - `test` (test) test execution started
  35. * - `test end` (test) test completed
  36. * - `hook` (hook) hook execution started
  37. * - `hook end` (hook) hook complete
  38. * - `pass` (test) test passed
  39. * - `fail` (test, err) test failed
  40. * - `pending` (test) test pending
  41. *
  42. * @api public
  43. */
  44. function Runner(suite) {
  45. var self = this;
  46. this._globals = [];
  47. this.suite = suite;
  48. this.total = suite.total();
  49. this.failures = 0;
  50. this.on('test end', function(test){ self.checkGlobals(test); });
  51. this.on('hook end', function(hook){ self.checkGlobals(hook); });
  52. this.grep(/.*/);
  53. this.globals(this.globalProps().concat(['errno']));
  54. }
  55. /**
  56. * Wrapper for setImmediate, process.nextTick, or browser polyfill.
  57. *
  58. * @param {Function} fn
  59. * @api private
  60. */
  61. Runner.immediately = global.setImmediate || process.nextTick;
  62. /**
  63. * Inherit from `EventEmitter.prototype`.
  64. */
  65. Runner.prototype.__proto__ = EventEmitter.prototype;
  66. /**
  67. * Run tests with full titles matching `re`. Updates runner.total
  68. * with number of tests matched.
  69. *
  70. * @param {RegExp} re
  71. * @param {Boolean} invert
  72. * @return {Runner} for chaining
  73. * @api public
  74. */
  75. Runner.prototype.grep = function(re, invert){
  76. debug('grep %s', re);
  77. this._grep = re;
  78. this._invert = invert;
  79. this.total = this.grepTotal(this.suite);
  80. return this;
  81. };
  82. /**
  83. * Returns the number of tests matching the grep search for the
  84. * given suite.
  85. *
  86. * @param {Suite} suite
  87. * @return {Number}
  88. * @api public
  89. */
  90. Runner.prototype.grepTotal = function(suite) {
  91. var self = this;
  92. var total = 0;
  93. suite.eachTest(function(test){
  94. var match = self._grep.test(test.fullTitle());
  95. if (self._invert) match = !match;
  96. if (match) total++;
  97. });
  98. return total;
  99. };
  100. /**
  101. * Return a list of global properties.
  102. *
  103. * @return {Array}
  104. * @api private
  105. */
  106. Runner.prototype.globalProps = function() {
  107. var props = utils.keys(global);
  108. // non-enumerables
  109. for (var i = 0; i < globals.length; ++i) {
  110. if (~utils.indexOf(props, globals[i])) continue;
  111. props.push(globals[i]);
  112. }
  113. return props;
  114. };
  115. /**
  116. * Allow the given `arr` of globals.
  117. *
  118. * @param {Array} arr
  119. * @return {Runner} for chaining
  120. * @api public
  121. */
  122. Runner.prototype.globals = function(arr){
  123. if (0 == arguments.length) return this._globals;
  124. debug('globals %j', arr);
  125. utils.forEach(arr, function(arr){
  126. this._globals.push(arr);
  127. }, this);
  128. return this;
  129. };
  130. /**
  131. * Check for global variable leaks.
  132. *
  133. * @api private
  134. */
  135. Runner.prototype.checkGlobals = function(test){
  136. if (this.ignoreLeaks) return;
  137. var ok = this._globals;
  138. var globals = this.globalProps();
  139. var isNode = process.kill;
  140. var leaks;
  141. // check length - 2 ('errno' and 'location' globals)
  142. if (isNode && 1 == ok.length - globals.length) return
  143. else if (2 == ok.length - globals.length) return;
  144. leaks = filterLeaks(ok, globals);
  145. this._globals = this._globals.concat(leaks);
  146. if (leaks.length > 1) {
  147. this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + ''));
  148. } else if (leaks.length) {
  149. this.fail(test, new Error('global leak detected: ' + leaks[0]));
  150. }
  151. };
  152. /**
  153. * Fail the given `test`.
  154. *
  155. * @param {Test} test
  156. * @param {Error} err
  157. * @api private
  158. */
  159. Runner.prototype.fail = function(test, err){
  160. ++this.failures;
  161. test.state = 'failed';
  162. if ('string' == typeof err) {
  163. err = new Error('the string "' + err + '" was thrown, throw an Error :)');
  164. }
  165. this.emit('fail', test, err);
  166. };
  167. /**
  168. * Fail the given `hook` with `err`.
  169. *
  170. * Hook failures (currently) hard-end due
  171. * to that fact that a failing hook will
  172. * surely cause subsequent tests to fail,
  173. * causing jumbled reporting.
  174. *
  175. * @param {Hook} hook
  176. * @param {Error} err
  177. * @api private
  178. */
  179. Runner.prototype.failHook = function(hook, err){
  180. this.fail(hook, err);
  181. this.emit('end');
  182. };
  183. /**
  184. * Run hook `name` callbacks and then invoke `fn()`.
  185. *
  186. * @param {String} name
  187. * @param {Function} function
  188. * @api private
  189. */
  190. Runner.prototype.hook = function(name, fn){
  191. var suite = this.suite
  192. , hooks = suite['_' + name]
  193. , self = this
  194. , timer;
  195. function next(i) {
  196. var hook = hooks[i];
  197. if (!hook) return fn();
  198. if (self.failures && suite.bail()) return fn();
  199. self.currentRunnable = hook;
  200. hook.ctx.currentTest = self.test;
  201. self.emit('hook', hook);
  202. hook.on('error', function(err){
  203. self.failHook(hook, err);
  204. });
  205. hook.run(function(err){
  206. hook.removeAllListeners('error');
  207. var testError = hook.error();
  208. if (testError) self.fail(self.test, testError);
  209. if (err) return self.failHook(hook, err);
  210. self.emit('hook end', hook);
  211. delete hook.ctx.currentTest;
  212. next(++i);
  213. });
  214. }
  215. Runner.immediately(function(){
  216. next(0);
  217. });
  218. };
  219. /**
  220. * Run hook `name` for the given array of `suites`
  221. * in order, and callback `fn(err)`.
  222. *
  223. * @param {String} name
  224. * @param {Array} suites
  225. * @param {Function} fn
  226. * @api private
  227. */
  228. Runner.prototype.hooks = function(name, suites, fn){
  229. var self = this
  230. , orig = this.suite;
  231. function next(suite) {
  232. self.suite = suite;
  233. if (!suite) {
  234. self.suite = orig;
  235. return fn();
  236. }
  237. self.hook(name, function(err){
  238. if (err) {
  239. self.suite = orig;
  240. return fn(err);
  241. }
  242. next(suites.pop());
  243. });
  244. }
  245. next(suites.pop());
  246. };
  247. /**
  248. * Run hooks from the top level down.
  249. *
  250. * @param {String} name
  251. * @param {Function} fn
  252. * @api private
  253. */
  254. Runner.prototype.hookUp = function(name, fn){
  255. var suites = [this.suite].concat(this.parents()).reverse();
  256. this.hooks(name, suites, fn);
  257. };
  258. /**
  259. * Run hooks from the bottom up.
  260. *
  261. * @param {String} name
  262. * @param {Function} fn
  263. * @api private
  264. */
  265. Runner.prototype.hookDown = function(name, fn){
  266. var suites = [this.suite].concat(this.parents());
  267. this.hooks(name, suites, fn);
  268. };
  269. /**
  270. * Return an array of parent Suites from
  271. * closest to furthest.
  272. *
  273. * @return {Array}
  274. * @api private
  275. */
  276. Runner.prototype.parents = function(){
  277. var suite = this.suite
  278. , suites = [];
  279. while (suite = suite.parent) suites.push(suite);
  280. return suites;
  281. };
  282. /**
  283. * Run the current test and callback `fn(err)`.
  284. *
  285. * @param {Function} fn
  286. * @api private
  287. */
  288. Runner.prototype.runTest = function(fn){
  289. var test = this.test
  290. , self = this;
  291. if (this.asyncOnly) test.asyncOnly = true;
  292. try {
  293. test.on('error', function(err){
  294. self.fail(test, err);
  295. });
  296. test.run(fn);
  297. } catch (err) {
  298. fn(err);
  299. }
  300. };
  301. /**
  302. * Run tests in the given `suite` and invoke
  303. * the callback `fn()` when complete.
  304. *
  305. * @param {Suite} suite
  306. * @param {Function} fn
  307. * @api private
  308. */
  309. Runner.prototype.runTests = function(suite, fn){
  310. var self = this
  311. , tests = suite.tests.slice()
  312. , test;
  313. function next(err) {
  314. // if we bail after first err
  315. if (self.failures && suite._bail) return fn();
  316. // next test
  317. test = tests.shift();
  318. // all done
  319. if (!test) return fn();
  320. // grep
  321. var match = self._grep.test(test.fullTitle());
  322. if (self._invert) match = !match;
  323. if (!match) return next();
  324. // pending
  325. if (test.pending) {
  326. self.emit('pending', test);
  327. self.emit('test end', test);
  328. return next();
  329. }
  330. // execute test and hook(s)
  331. self.emit('test', self.test = test);
  332. self.hookDown('beforeEach', function(){
  333. self.currentRunnable = self.test;
  334. self.runTest(function(err){
  335. test = self.test;
  336. if (err) {
  337. self.fail(test, err);
  338. self.emit('test end', test);
  339. return self.hookUp('afterEach', next);
  340. }
  341. test.state = 'passed';
  342. self.emit('pass', test);
  343. self.emit('test end', test);
  344. self.hookUp('afterEach', next);
  345. });
  346. });
  347. }
  348. this.next = next;
  349. next();
  350. };
  351. /**
  352. * Run the given `suite` and invoke the
  353. * callback `fn()` when complete.
  354. *
  355. * @param {Suite} suite
  356. * @param {Function} fn
  357. * @api private
  358. */
  359. Runner.prototype.runSuite = function(suite, fn){
  360. var total = this.grepTotal(suite)
  361. , self = this
  362. , i = 0;
  363. debug('run suite %s', suite.fullTitle());
  364. if (!total) return fn();
  365. this.emit('suite', this.suite = suite);
  366. function next() {
  367. var curr = suite.suites[i++];
  368. if (!curr) return done();
  369. self.runSuite(curr, next);
  370. }
  371. function done() {
  372. self.suite = suite;
  373. self.hook('afterAll', function(){
  374. self.emit('suite end', suite);
  375. fn();
  376. });
  377. }
  378. this.hook('beforeAll', function(){
  379. self.runTests(suite, next);
  380. });
  381. };
  382. /**
  383. * Handle uncaught exceptions.
  384. *
  385. * @param {Error} err
  386. * @api private
  387. */
  388. Runner.prototype.uncaught = function(err){
  389. debug('uncaught exception %s', err.message);
  390. var runnable = this.currentRunnable;
  391. if (!runnable || 'failed' == runnable.state) return;
  392. runnable.clearTimeout();
  393. err.uncaught = true;
  394. this.fail(runnable, err);
  395. // recover from test
  396. if ('test' == runnable.type) {
  397. this.emit('test end', runnable);
  398. this.hookUp('afterEach', this.next);
  399. return;
  400. }
  401. // bail on hooks
  402. this.emit('end');
  403. };
  404. /**
  405. * Run the root suite and invoke `fn(failures)`
  406. * on completion.
  407. *
  408. * @param {Function} fn
  409. * @return {Runner} for chaining
  410. * @api public
  411. */
  412. Runner.prototype.run = function(fn){
  413. var self = this
  414. , fn = fn || function(){};
  415. function uncaught(err){
  416. self.uncaught(err);
  417. }
  418. debug('start');
  419. // callback
  420. this.on('end', function(){
  421. debug('end');
  422. process.removeListener('uncaughtException', uncaught);
  423. fn(self.failures);
  424. });
  425. // run suites
  426. this.emit('start');
  427. this.runSuite(this.suite, function(){
  428. debug('finished running');
  429. self.emit('end');
  430. });
  431. // uncaught exception
  432. process.on('uncaughtException', uncaught);
  433. return this;
  434. };
  435. /**
  436. * Filter leaks with the given globals flagged as `ok`.
  437. *
  438. * @param {Array} ok
  439. * @param {Array} globals
  440. * @return {Array}
  441. * @api private
  442. */
  443. function filterLeaks(ok, globals) {
  444. return filter(globals, function(key){
  445. // Firefox and Chrome exposes iframes as index inside the window object
  446. if (/^d+/.test(key)) return false;
  447. var matched = filter(ok, function(ok){
  448. if (~ok.indexOf('*')) return 0 == key.indexOf(ok.split('*')[0]);
  449. // Opera and IE expose global variables for HTML element IDs (issue #243)
  450. if (/^mocha-/.test(key)) return true;
  451. return key == ok;
  452. });
  453. return matched.length == 0 && (!global.navigator || 'onerror' !== key);
  454. });
  455. }