mocha.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
  1. 'use strict';
  2. /*!
  3. * mocha
  4. * Copyright(c) 2011 TJ Holowaychuk <tj@vision-media.ca>
  5. * MIT Licensed
  6. */
  7. var escapeRe = require('escape-string-regexp');
  8. var path = require('path');
  9. var reporters = require('./reporters');
  10. var utils = require('./utils');
  11. exports = module.exports = Mocha;
  12. /**
  13. * To require local UIs and reporters when running in node.
  14. */
  15. if (!process.browser) {
  16. var cwd = process.cwd();
  17. module.paths.push(cwd, path.join(cwd, 'node_modules'));
  18. }
  19. /**
  20. * Expose internals.
  21. */
  22. /**
  23. * @public
  24. * @class utils
  25. * @memberof Mocha
  26. */
  27. exports.utils = utils;
  28. exports.interfaces = require('./interfaces');
  29. /**
  30. *
  31. * @memberof Mocha
  32. * @public
  33. */
  34. exports.reporters = reporters;
  35. exports.Runnable = require('./runnable');
  36. exports.Context = require('./context');
  37. /**
  38. *
  39. * @memberof Mocha
  40. */
  41. exports.Runner = require('./runner');
  42. exports.Suite = require('./suite');
  43. exports.Hook = require('./hook');
  44. exports.Test = require('./test');
  45. /**
  46. * Return image `name` path.
  47. *
  48. * @private
  49. * @param {string} name
  50. * @return {string}
  51. */
  52. function image(name) {
  53. return path.join(__dirname, '..', 'assets', 'growl', name + '.png');
  54. }
  55. /**
  56. * Set up mocha with `options`.
  57. *
  58. * Options:
  59. *
  60. * - `ui` name "bdd", "tdd", "exports" etc
  61. * - `reporter` reporter instance, defaults to `mocha.reporters.spec`
  62. * - `globals` array of accepted globals
  63. * - `timeout` timeout in milliseconds
  64. * - `retries` number of times to retry failed tests
  65. * - `bail` bail on the first test failure
  66. * - `slow` milliseconds to wait before considering a test slow
  67. * - `ignoreLeaks` ignore global leaks
  68. * - `fullTrace` display the full stack-trace on failing
  69. * - `grep` string or regexp to filter tests with
  70. *
  71. * @class Mocha
  72. * @param {Object} options
  73. */
  74. function Mocha(options) {
  75. options = options || {};
  76. this.files = [];
  77. this.options = options;
  78. if (options.grep) {
  79. this.grep(new RegExp(options.grep));
  80. }
  81. if (options.fgrep) {
  82. this.fgrep(options.fgrep);
  83. }
  84. this.suite = new exports.Suite('', new exports.Context());
  85. this.ui(options.ui);
  86. this.bail(options.bail);
  87. this.reporter(options.reporter, options.reporterOptions);
  88. if (typeof options.timeout !== 'undefined' && options.timeout !== null) {
  89. this.timeout(options.timeout);
  90. }
  91. if (typeof options.retries !== 'undefined' && options.retries !== null) {
  92. this.retries(options.retries);
  93. }
  94. this.useColors(options.useColors);
  95. if (options.enableTimeouts !== null) {
  96. this.enableTimeouts(options.enableTimeouts);
  97. }
  98. if (options.slow) {
  99. this.slow(options.slow);
  100. }
  101. }
  102. /**
  103. * Enable or disable bailing on the first failure.
  104. *
  105. * @public
  106. * @api public
  107. * @param {boolean} [bail]
  108. */
  109. Mocha.prototype.bail = function(bail) {
  110. if (!arguments.length) {
  111. bail = true;
  112. }
  113. this.suite.bail(bail);
  114. return this;
  115. };
  116. /**
  117. * Add test `file`.
  118. *
  119. * @public
  120. * @api public
  121. * @param {string} file
  122. */
  123. Mocha.prototype.addFile = function(file) {
  124. this.files.push(file);
  125. return this;
  126. };
  127. /**
  128. * Set reporter to `reporter`, defaults to "spec".
  129. *
  130. * @public
  131. * @param {String|Function} reporter name or constructor
  132. * @param {Object} reporterOptions optional options
  133. * @api public
  134. * @param {string|Function} reporter name or constructor
  135. * @param {Object} reporterOptions optional options
  136. */
  137. Mocha.prototype.reporter = function(reporter, reporterOptions) {
  138. if (typeof reporter === 'function') {
  139. this._reporter = reporter;
  140. } else {
  141. reporter = reporter || 'spec';
  142. var _reporter;
  143. // Try to load a built-in reporter.
  144. if (reporters[reporter]) {
  145. _reporter = reporters[reporter];
  146. }
  147. // Try to load reporters from process.cwd() and node_modules
  148. if (!_reporter) {
  149. try {
  150. _reporter = require(reporter);
  151. } catch (err) {
  152. if (err.message.indexOf('Cannot find module') !== -1) {
  153. // Try to load reporters from a path (absolute or relative)
  154. try {
  155. _reporter = require(path.resolve(process.cwd(), reporter));
  156. } catch (_err) {
  157. err.message.indexOf('Cannot find module') !== -1
  158. ? console.warn('"' + reporter + '" reporter not found')
  159. : console.warn(
  160. '"' +
  161. reporter +
  162. '" reporter blew up with error:\n' +
  163. err.stack
  164. );
  165. }
  166. } else {
  167. console.warn(
  168. '"' + reporter + '" reporter blew up with error:\n' + err.stack
  169. );
  170. }
  171. }
  172. }
  173. if (!_reporter && reporter === 'teamcity') {
  174. console.warn(
  175. 'The Teamcity reporter was moved to a package named ' +
  176. 'mocha-teamcity-reporter ' +
  177. '(https://npmjs.org/package/mocha-teamcity-reporter).'
  178. );
  179. }
  180. if (!_reporter) {
  181. throw new Error('invalid reporter "' + reporter + '"');
  182. }
  183. this._reporter = _reporter;
  184. }
  185. this.options.reporterOptions = reporterOptions;
  186. return this;
  187. };
  188. /**
  189. * Set test UI `name`, defaults to "bdd".
  190. * @public
  191. * @api public
  192. * @param {string} bdd
  193. */
  194. Mocha.prototype.ui = function(name) {
  195. name = name || 'bdd';
  196. this._ui = exports.interfaces[name];
  197. if (!this._ui) {
  198. try {
  199. this._ui = require(name);
  200. } catch (err) {
  201. throw new Error('invalid interface "' + name + '"');
  202. }
  203. }
  204. this._ui = this._ui(this.suite);
  205. this.suite.on('pre-require', function(context) {
  206. exports.afterEach = context.afterEach || context.teardown;
  207. exports.after = context.after || context.suiteTeardown;
  208. exports.beforeEach = context.beforeEach || context.setup;
  209. exports.before = context.before || context.suiteSetup;
  210. exports.describe = context.describe || context.suite;
  211. exports.it = context.it || context.test;
  212. exports.xit = context.xit || context.test.skip;
  213. exports.setup = context.setup || context.beforeEach;
  214. exports.suiteSetup = context.suiteSetup || context.before;
  215. exports.suiteTeardown = context.suiteTeardown || context.after;
  216. exports.suite = context.suite || context.describe;
  217. exports.teardown = context.teardown || context.afterEach;
  218. exports.test = context.test || context.it;
  219. exports.run = context.run;
  220. });
  221. return this;
  222. };
  223. /**
  224. * Load registered files.
  225. *
  226. * @api private
  227. */
  228. Mocha.prototype.loadFiles = function(fn) {
  229. var self = this;
  230. var suite = this.suite;
  231. this.files.forEach(function(file) {
  232. file = path.resolve(file);
  233. suite.emit('pre-require', global, file, self);
  234. suite.emit('require', require(file), file, self);
  235. suite.emit('post-require', global, file, self);
  236. });
  237. fn && fn();
  238. };
  239. /**
  240. * Enable growl support.
  241. *
  242. * @api private
  243. */
  244. Mocha.prototype._growl = function(runner, reporter) {
  245. var notify = require('growl');
  246. runner.on('end', function() {
  247. var stats = reporter.stats;
  248. if (stats.failures) {
  249. var msg = stats.failures + ' of ' + runner.total + ' tests failed';
  250. notify(msg, {name: 'mocha', title: 'Failed', image: image('error')});
  251. } else {
  252. notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', {
  253. name: 'mocha',
  254. title: 'Passed',
  255. image: image('ok')
  256. });
  257. }
  258. });
  259. };
  260. /**
  261. * Escape string and add it to grep as a regexp.
  262. *
  263. * @public
  264. * @api public
  265. * @param str
  266. * @returns {Mocha}
  267. */
  268. Mocha.prototype.fgrep = function(str) {
  269. return this.grep(new RegExp(escapeRe(str)));
  270. };
  271. /**
  272. * Add regexp to grep, if `re` is a string it is escaped.
  273. *
  274. * @public
  275. * @param {RegExp|String} re
  276. * @return {Mocha}
  277. * @api public
  278. * @param {RegExp|string} re
  279. * @return {Mocha}
  280. */
  281. Mocha.prototype.grep = function(re) {
  282. if (utils.isString(re)) {
  283. // extract args if it's regex-like, i.e: [string, pattern, flag]
  284. var arg = re.match(/^\/(.*)\/(g|i|)$|.*/);
  285. this.options.grep = new RegExp(arg[1] || arg[0], arg[2]);
  286. } else {
  287. this.options.grep = re;
  288. }
  289. return this;
  290. };
  291. /**
  292. * Invert `.grep()` matches.
  293. *
  294. * @public
  295. * @return {Mocha}
  296. * @api public
  297. */
  298. Mocha.prototype.invert = function() {
  299. this.options.invert = true;
  300. return this;
  301. };
  302. /**
  303. * Ignore global leaks.
  304. *
  305. * @public
  306. * @param {Boolean} ignore
  307. * @return {Mocha}
  308. * @api public
  309. * @param {boolean} ignore
  310. * @return {Mocha}
  311. */
  312. Mocha.prototype.ignoreLeaks = function(ignore) {
  313. this.options.ignoreLeaks = Boolean(ignore);
  314. return this;
  315. };
  316. /**
  317. * Enable global leak checking.
  318. *
  319. * @return {Mocha}
  320. * @api public
  321. * @public
  322. */
  323. Mocha.prototype.checkLeaks = function() {
  324. this.options.ignoreLeaks = false;
  325. return this;
  326. };
  327. /**
  328. * Display long stack-trace on failing
  329. *
  330. * @return {Mocha}
  331. * @api public
  332. * @public
  333. */
  334. Mocha.prototype.fullTrace = function() {
  335. this.options.fullStackTrace = true;
  336. return this;
  337. };
  338. /**
  339. * Enable growl support.
  340. *
  341. * @return {Mocha}
  342. * @api public
  343. * @public
  344. */
  345. Mocha.prototype.growl = function() {
  346. this.options.growl = true;
  347. return this;
  348. };
  349. /**
  350. * Ignore `globals` array or string.
  351. *
  352. * @param {Array|String} globals
  353. * @return {Mocha}
  354. * @api public
  355. * @public
  356. * @param {Array|string} globals
  357. * @return {Mocha}
  358. */
  359. Mocha.prototype.globals = function(globals) {
  360. this.options.globals = (this.options.globals || []).concat(globals);
  361. return this;
  362. };
  363. /**
  364. * Emit color output.
  365. *
  366. * @param {Boolean} colors
  367. * @return {Mocha}
  368. * @api public
  369. * @public
  370. * @param {boolean} colors
  371. * @return {Mocha}
  372. */
  373. Mocha.prototype.useColors = function(colors) {
  374. if (colors !== undefined) {
  375. this.options.useColors = colors;
  376. }
  377. return this;
  378. };
  379. /**
  380. * Use inline diffs rather than +/-.
  381. *
  382. * @param {Boolean} inlineDiffs
  383. * @return {Mocha}
  384. * @api public
  385. * @public
  386. * @param {boolean} inlineDiffs
  387. * @return {Mocha}
  388. */
  389. Mocha.prototype.useInlineDiffs = function(inlineDiffs) {
  390. this.options.useInlineDiffs = inlineDiffs !== undefined && inlineDiffs;
  391. return this;
  392. };
  393. /**
  394. * Do not show diffs at all.
  395. *
  396. * @param {Boolean} hideDiff
  397. * @return {Mocha}
  398. * @api public
  399. * @public
  400. * @param {boolean} hideDiff
  401. * @return {Mocha}
  402. */
  403. Mocha.prototype.hideDiff = function(hideDiff) {
  404. this.options.hideDiff = hideDiff !== undefined && hideDiff;
  405. return this;
  406. };
  407. /**
  408. * Set the timeout in milliseconds.
  409. *
  410. * @param {Number} timeout
  411. * @return {Mocha}
  412. * @api public
  413. * @public
  414. * @param {number} timeout
  415. * @return {Mocha}
  416. */
  417. Mocha.prototype.timeout = function(timeout) {
  418. this.suite.timeout(timeout);
  419. return this;
  420. };
  421. /**
  422. * Set the number of times to retry failed tests.
  423. *
  424. * @param {Number} retry times
  425. * @return {Mocha}
  426. * @api public
  427. * @public
  428. */
  429. Mocha.prototype.retries = function(n) {
  430. this.suite.retries(n);
  431. return this;
  432. };
  433. /**
  434. * Set slowness threshold in milliseconds.
  435. *
  436. * @param {Number} slow
  437. * @return {Mocha}
  438. * @api public
  439. * @public
  440. * @param {number} slow
  441. * @return {Mocha}
  442. */
  443. Mocha.prototype.slow = function(slow) {
  444. this.suite.slow(slow);
  445. return this;
  446. };
  447. /**
  448. * Enable timeouts.
  449. *
  450. * @param {Boolean} enabled
  451. * @return {Mocha}
  452. * @api public
  453. * @public
  454. * @param {boolean} enabled
  455. * @return {Mocha}
  456. */
  457. Mocha.prototype.enableTimeouts = function(enabled) {
  458. this.suite.enableTimeouts(
  459. arguments.length && enabled !== undefined ? enabled : true
  460. );
  461. return this;
  462. };
  463. /**
  464. * Makes all tests async (accepting a callback)
  465. *
  466. * @return {Mocha}
  467. * @api public
  468. * @public
  469. */
  470. Mocha.prototype.asyncOnly = function() {
  471. this.options.asyncOnly = true;
  472. return this;
  473. };
  474. /**
  475. * Disable syntax highlighting (in browser).
  476. *
  477. * @api public
  478. * @public
  479. */
  480. Mocha.prototype.noHighlighting = function() {
  481. this.options.noHighlighting = true;
  482. return this;
  483. };
  484. /**
  485. * Enable uncaught errors to propagate (in browser).
  486. *
  487. * @return {Mocha}
  488. * @api public
  489. * @public
  490. */
  491. Mocha.prototype.allowUncaught = function() {
  492. this.options.allowUncaught = true;
  493. return this;
  494. };
  495. /**
  496. * Delay root suite execution.
  497. * @returns {Mocha}
  498. */
  499. Mocha.prototype.delay = function delay() {
  500. this.options.delay = true;
  501. return this;
  502. };
  503. /**
  504. * Tests marked only fail the suite
  505. * @returns {Mocha}
  506. */
  507. Mocha.prototype.forbidOnly = function() {
  508. this.options.forbidOnly = true;
  509. return this;
  510. };
  511. /**
  512. * Pending tests and tests marked skip fail the suite
  513. * @returns {Mocha}
  514. */
  515. Mocha.prototype.forbidPending = function() {
  516. this.options.forbidPending = true;
  517. return this;
  518. };
  519. /**
  520. * Run tests and invoke `fn()` when complete.
  521. *
  522. * Note that `loadFiles` relies on Node's `require` to execute
  523. * the test interface functions and will be subject to the
  524. * cache - if the files are already in the `require` cache,
  525. * they will effectively be skipped. Therefore, to run tests
  526. * multiple times or to run tests in files that are already
  527. * in the `require` cache, make sure to clear them from the
  528. * cache first in whichever manner best suits your needs.
  529. *
  530. * @api public
  531. * @public
  532. * @param {Function} fn
  533. * @return {Runner}
  534. */
  535. Mocha.prototype.run = function(fn) {
  536. if (this.files.length) {
  537. this.loadFiles();
  538. }
  539. var suite = this.suite;
  540. var options = this.options;
  541. options.files = this.files;
  542. var runner = new exports.Runner(suite, options.delay);
  543. var reporter = new this._reporter(runner, options);
  544. runner.ignoreLeaks = options.ignoreLeaks !== false;
  545. runner.fullStackTrace = options.fullStackTrace;
  546. runner.asyncOnly = options.asyncOnly;
  547. runner.allowUncaught = options.allowUncaught;
  548. runner.forbidOnly = options.forbidOnly;
  549. runner.forbidPending = options.forbidPending;
  550. if (options.grep) {
  551. runner.grep(options.grep, options.invert);
  552. }
  553. if (options.globals) {
  554. runner.globals(options.globals);
  555. }
  556. if (options.growl) {
  557. this._growl(runner, reporter);
  558. }
  559. if (options.useColors !== undefined) {
  560. exports.reporters.Base.useColors = options.useColors;
  561. }
  562. exports.reporters.Base.inlineDiffs = options.useInlineDiffs;
  563. exports.reporters.Base.hideDiff = options.hideDiff;
  564. function done(failures) {
  565. if (reporter.done) {
  566. reporter.done(failures, fn);
  567. } else {
  568. fn && fn(failures);
  569. }
  570. }
  571. return runner.run(done);
  572. };