_mocha 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. #!/usr/bin/env node
  2. 'use strict';
  3. /* eslint no-unused-vars: off */
  4. /**
  5. * Module dependencies.
  6. */
  7. const program = require('commander');
  8. const path = require('path');
  9. const fs = require('fs');
  10. const minimatch = require('minimatch');
  11. const resolve = path.resolve;
  12. const exists = fs.existsSync;
  13. const Mocha = require('../');
  14. const utils = Mocha.utils;
  15. const interfaceNames = Object.keys(Mocha.interfaces);
  16. const join = path.join;
  17. const cwd = process.cwd();
  18. const getOptions = require('./options');
  19. const mocha = new Mocha();
  20. /**
  21. * Save timer references to avoid Sinon interfering (see GH-237).
  22. */
  23. const Date = global.Date;
  24. const setTimeout = global.setTimeout;
  25. const setInterval = global.setInterval;
  26. const clearTimeout = global.clearTimeout;
  27. const clearInterval = global.clearInterval;
  28. /**
  29. * Exits Mocha when tests + code under test has finished execution (default)
  30. * @param {number} code - Exit code; typically # of failures
  31. */
  32. const exitLater = code => {
  33. process.on('exit', () => {
  34. process.exit(Math.min(code, 255));
  35. });
  36. };
  37. /**
  38. * Exits Mocha when Mocha itself has finished execution, regardless of
  39. * what the tests or code under test is doing.
  40. * @param {number} code - Exit code; typically # of failures
  41. */
  42. const exit = code => {
  43. const clampedCode = Math.min(code, 255);
  44. let draining = 0;
  45. // Eagerly set the process's exit code in case stream.write doesn't
  46. // execute its callback before the process terminates.
  47. process.exitCode = clampedCode;
  48. // flush output for Node.js Windows pipe bug
  49. // https://github.com/joyent/node/issues/6247 is just one bug example
  50. // https://github.com/visionmedia/mocha/issues/333 has a good discussion
  51. const done = () => {
  52. if (!draining--) {
  53. process.exit(clampedCode);
  54. }
  55. };
  56. const streams = [process.stdout, process.stderr];
  57. streams.forEach(stream => {
  58. // submit empty write request and wait for completion
  59. draining += 1;
  60. stream.write('', done);
  61. });
  62. done();
  63. };
  64. /**
  65. * Parse list.
  66. */
  67. const list = str => str.split(/ *, */);
  68. /**
  69. * Parse multiple flag.
  70. */
  71. const collect = (val, memo) => memo.concat(val);
  72. /**
  73. * Hide the cursor.
  74. */
  75. const hideCursor = () => {
  76. process.stdout.write('\u001b[?25l');
  77. };
  78. /**
  79. * Show the cursor.
  80. */
  81. const showCursor = () => {
  82. process.stdout.write('\u001b[?25h');
  83. };
  84. /**
  85. * Stop play()ing.
  86. */
  87. const stop = () => {
  88. process.stdout.write('\u001b[2K');
  89. clearInterval(play.timer);
  90. };
  91. /**
  92. * Play the given array of strings.
  93. */
  94. const play = (arr, interval) => {
  95. const len = arr.length;
  96. interval = interval || 100;
  97. let i = 0;
  98. play.timer = setInterval(() => {
  99. const str = arr[i++ % len];
  100. process.stdout.write(`\u001b[0G${str}`);
  101. }, interval);
  102. };
  103. /**
  104. * Files.
  105. */
  106. let files = [];
  107. /**
  108. * Globals.
  109. */
  110. let globals = [];
  111. /**
  112. * Requires.
  113. */
  114. const requires = [];
  115. /**
  116. * Images.
  117. */
  118. const images = {
  119. fail: path.join(__dirname, '..', 'assets', 'growl', 'error.png'),
  120. pass: path.join(__dirname, '..', 'assets', 'growl', 'ok.png')
  121. };
  122. // options
  123. program
  124. .version(
  125. JSON.parse(
  126. fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')
  127. ).version
  128. )
  129. .usage('[debug] [options] [files]')
  130. .option(
  131. '-A, --async-only',
  132. 'force all tests to take a callback (async) or return a promise'
  133. )
  134. .option('-c, --colors', 'force enabling of colors')
  135. .option('-C, --no-colors', 'force disabling of colors')
  136. .option('-G, --growl', 'enable growl notification support')
  137. .option('-O, --reporter-options <k=v,k2=v2,...>', 'reporter-specific options')
  138. .option('-R, --reporter <name>', 'specify the reporter to use', 'spec')
  139. .option('-S, --sort', 'sort test files')
  140. .option('-b, --bail', 'bail after first test failure')
  141. .option('-d, --debug', "enable node's debugger, synonym for node --debug")
  142. .option('-g, --grep <pattern>', 'only run tests matching <pattern>')
  143. .option('-f, --fgrep <string>', 'only run tests containing <string>')
  144. .option('-gc, --expose-gc', 'expose gc extension')
  145. .option('-i, --invert', 'inverts --grep and --fgrep matches')
  146. .option('-r, --require <name>', 'require the given module')
  147. .option('-s, --slow <ms>', '"slow" test threshold in milliseconds [75]')
  148. .option('-t, --timeout <ms>', 'set test-case timeout in milliseconds [2000]')
  149. .option(
  150. '-u, --ui <name>',
  151. `specify user-interface (${interfaceNames.join('|')})`,
  152. 'bdd'
  153. )
  154. .option('-w, --watch', 'watch files for changes')
  155. .option('--check-leaks', 'check for global variable leaks')
  156. .option('--full-trace', 'display the full stack trace')
  157. .option(
  158. '--compilers <ext>:<module>,...',
  159. 'use the given module(s) to compile files',
  160. list,
  161. []
  162. )
  163. .option('--debug-brk', "enable node's debugger breaking on the first line")
  164. .option(
  165. '--globals <names>',
  166. 'allow the given comma-delimited global [names]',
  167. list,
  168. []
  169. )
  170. .option('--es_staging', 'enable all staged features')
  171. .option(
  172. '--harmony<_classes,_generators,...>',
  173. 'all node --harmony* flags are available'
  174. )
  175. .option(
  176. '--preserve-symlinks',
  177. 'Instructs the module loader to preserve symbolic links when resolving and caching modules'
  178. )
  179. .option('--icu-data-dir', 'include ICU data')
  180. .option(
  181. '--inline-diffs',
  182. 'display actual/expected differences inline within each string'
  183. )
  184. .option('--no-diff', 'do not show a diff on failure')
  185. .option('--inspect', 'activate devtools in chrome')
  186. .option(
  187. '--inspect-brk',
  188. 'activate devtools in chrome and break on the first line'
  189. )
  190. .option('--interfaces', 'display available interfaces')
  191. .option('--no-deprecation', 'silence deprecation warnings')
  192. .option(
  193. '--exit',
  194. 'force shutdown of the event loop after test run: mocha will call process.exit'
  195. )
  196. .option('--no-timeouts', 'disables timeouts, given implicitly with --debug')
  197. .option('--no-warnings', 'silence all node process warnings')
  198. .option('--opts <path>', 'specify opts path', 'test/mocha.opts')
  199. .option('--perf-basic-prof', 'enable perf linux profiler (basic support)')
  200. .option('--napi-modules', 'enable experimental NAPI modules')
  201. .option('--prof', 'log statistical profiling information')
  202. .option('--log-timer-events', 'Time events including external callbacks')
  203. .option('--recursive', 'include sub directories')
  204. .option('--reporters', 'display available reporters')
  205. .option(
  206. '--retries <times>',
  207. 'set numbers of time to retry a failed test case'
  208. )
  209. .option(
  210. '--throw-deprecation',
  211. 'throw an exception anytime a deprecated function is used'
  212. )
  213. .option('--trace', 'trace function calls')
  214. .option('--trace-deprecation', 'show stack traces on deprecations')
  215. .option('--trace-warnings', 'show stack traces on node process warnings')
  216. .option('--use_strict', 'enforce strict mode')
  217. .option(
  218. '--watch-extensions <ext>,...',
  219. 'additional extensions to monitor with --watch',
  220. list,
  221. ['js']
  222. )
  223. .option('--delay', 'wait for async suite definition')
  224. .option('--allow-uncaught', 'enable uncaught errors to propagate')
  225. .option('--forbid-only', 'causes test marked with only to fail the suite')
  226. .option(
  227. '--forbid-pending',
  228. 'causes pending tests and test marked with skip to fail the suite'
  229. )
  230. .option(
  231. '--file <file>',
  232. 'include a file to be ran during the suite',
  233. collect,
  234. []
  235. )
  236. .option('--exclude <file>', 'a file or glob pattern to ignore', collect, []);
  237. program._name = 'mocha';
  238. // init command
  239. program
  240. .command('init <path>')
  241. .description('initialize a client-side mocha setup at <path>')
  242. .action(path => {
  243. const mkdir = require('mkdirp');
  244. mkdir.sync(path);
  245. const css = fs.readFileSync(join(__dirname, '..', 'mocha.css'));
  246. const js = fs.readFileSync(join(__dirname, '..', 'mocha.js'));
  247. const tmpl = fs.readFileSync(join(__dirname, '..', 'lib/template.html'));
  248. fs.writeFileSync(join(path, 'mocha.css'), css);
  249. fs.writeFileSync(join(path, 'mocha.js'), js);
  250. fs.writeFileSync(join(path, 'tests.js'), '');
  251. fs.writeFileSync(join(path, 'index.html'), tmpl);
  252. process.exit(0);
  253. });
  254. // --globals
  255. program.on('option:globals', val => {
  256. globals = globals.concat(list(val));
  257. });
  258. // --reporters
  259. program.on('option:reporters', () => {
  260. console.log();
  261. console.log(' dot - dot matrix');
  262. console.log(' doc - html documentation');
  263. console.log(' spec - hierarchical spec list');
  264. console.log(' json - single json object');
  265. console.log(' progress - progress bar');
  266. console.log(' list - spec-style listing');
  267. console.log(' tap - test-anything-protocol');
  268. console.log(' landing - unicode landing strip');
  269. console.log(' xunit - xunit reporter');
  270. console.log(' min - minimal reporter (great with --watch)');
  271. console.log(' json-stream - newline delimited json events');
  272. console.log(' markdown - markdown documentation (github flavour)');
  273. console.log(' nyan - nyan cat!');
  274. console.log();
  275. process.exit();
  276. });
  277. // --interfaces
  278. program.on('option:interfaces', () => {
  279. console.log('');
  280. interfaceNames.forEach(interfaceName => {
  281. console.log(` ${interfaceName}`);
  282. });
  283. console.log('');
  284. process.exit();
  285. });
  286. // -r, --require
  287. module.paths.push(cwd, join(cwd, 'node_modules'));
  288. program.on('option:require', mod => {
  289. const abs = exists(mod) || exists(`${mod}.js`);
  290. if (abs) {
  291. mod = resolve(mod);
  292. }
  293. requires.push(mod);
  294. });
  295. // If not already done, load mocha.opts
  296. if (!process.env.LOADED_MOCHA_OPTS) {
  297. getOptions();
  298. }
  299. // parse args
  300. program.parse(process.argv);
  301. // infinite stack traces
  302. Error.stackTraceLimit = Infinity; // TODO: config
  303. // reporter options
  304. const reporterOptions = {};
  305. if (program.reporterOptions !== undefined) {
  306. program.reporterOptions.split(',').forEach(opt => {
  307. const L = opt.split('=');
  308. if (L.length > 2 || L.length === 0) {
  309. throw new Error(`invalid reporter option '${opt}'`);
  310. } else if (L.length === 2) {
  311. reporterOptions[L[0]] = L[1];
  312. } else {
  313. reporterOptions[L[0]] = true;
  314. }
  315. });
  316. }
  317. // reporter
  318. mocha.reporter(program.reporter, reporterOptions);
  319. // --no-colors
  320. if (!program.colors) {
  321. mocha.useColors(false);
  322. }
  323. // --colors
  324. if (~process.argv.indexOf('--colors') || ~process.argv.indexOf('-c')) {
  325. mocha.useColors(true);
  326. }
  327. // --inline-diffs
  328. if (program.inlineDiffs) {
  329. mocha.useInlineDiffs(true);
  330. }
  331. // --no-diff
  332. if (process.argv.indexOf('--no-diff') !== -1) {
  333. mocha.hideDiff(true);
  334. }
  335. // --slow <ms>
  336. if (program.slow) {
  337. mocha.suite.slow(program.slow);
  338. }
  339. // --no-timeouts
  340. if (!program.timeouts) {
  341. mocha.enableTimeouts(false);
  342. }
  343. // --timeout
  344. if (program.timeout) {
  345. mocha.suite.timeout(program.timeout);
  346. }
  347. // --bail
  348. mocha.suite.bail(program.bail);
  349. // --grep
  350. if (program.grep) {
  351. mocha.grep(program.grep);
  352. }
  353. // --fgrep
  354. if (program.fgrep) {
  355. mocha.fgrep(program.fgrep);
  356. }
  357. // --invert
  358. if (program.invert) {
  359. mocha.invert();
  360. }
  361. // --check-leaks
  362. if (program.checkLeaks) {
  363. mocha.checkLeaks();
  364. }
  365. // --stack-trace
  366. if (program.fullTrace) {
  367. mocha.fullTrace();
  368. }
  369. // --growl
  370. if (program.growl) {
  371. mocha.growl();
  372. }
  373. // --async-only
  374. if (program.asyncOnly) {
  375. mocha.asyncOnly();
  376. }
  377. // --delay
  378. if (program.delay) {
  379. mocha.delay();
  380. }
  381. // --allow-uncaught
  382. if (program.allowUncaught) {
  383. mocha.allowUncaught();
  384. }
  385. // --globals
  386. mocha.globals(globals);
  387. // --retries
  388. if (program.retries) {
  389. mocha.suite.retries(program.retries);
  390. }
  391. // --forbid-only
  392. if (program.forbidOnly) mocha.forbidOnly();
  393. // --forbid-pending
  394. if (program.forbidPending) mocha.forbidPending();
  395. // custom compiler support
  396. if (program.compilers.length > 0) {
  397. require('util').deprecate(() => {},
  398. '"--compilers" will be removed in a future version of Mocha; see https://git.io/vdcSr for more info')();
  399. }
  400. const extensions = ['js'];
  401. program.compilers.forEach(c => {
  402. const idx = c.indexOf(':');
  403. const ext = c.slice(0, idx);
  404. let mod = c.slice(idx + 1);
  405. if (mod[0] === '.') {
  406. mod = join(process.cwd(), mod);
  407. }
  408. require(mod);
  409. extensions.push(ext);
  410. program.watchExtensions.push(ext);
  411. });
  412. // requires
  413. requires.forEach(mod => {
  414. require(mod);
  415. });
  416. // interface
  417. mocha.ui(program.ui);
  418. // args
  419. const args = program.args;
  420. // default files to test/*.{js,coffee}
  421. if (!args.length) {
  422. args.push('test');
  423. }
  424. args.forEach(arg => {
  425. let newFiles;
  426. try {
  427. newFiles = utils.lookupFiles(arg, extensions, program.recursive);
  428. } catch (err) {
  429. if (err.message.indexOf('cannot resolve path') === 0) {
  430. console.error(
  431. `Warning: Could not find any test files matching pattern: ${arg}`
  432. );
  433. return;
  434. }
  435. throw err;
  436. }
  437. if (typeof newFiles !== 'undefined') {
  438. if (typeof newFiles === 'string') {
  439. newFiles = [newFiles];
  440. }
  441. newFiles = newFiles.filter(fileName =>
  442. program.exclude.every(pattern => !minimatch(fileName, pattern))
  443. );
  444. }
  445. files = files.concat(newFiles);
  446. });
  447. if (!files.length) {
  448. console.error('No test files found');
  449. process.exit(1);
  450. }
  451. // resolve
  452. let fileArgs = program.file.map(path => resolve(path));
  453. files = files.map(path => resolve(path));
  454. if (program.sort) {
  455. files.sort();
  456. }
  457. // add files given through --file to be ran first
  458. files = fileArgs.concat(files);
  459. // --watch
  460. let runner;
  461. let loadAndRun;
  462. let purge;
  463. let rerun;
  464. if (program.watch) {
  465. console.log();
  466. hideCursor();
  467. process.on('SIGINT', () => {
  468. showCursor();
  469. console.log('\n');
  470. process.exit(130);
  471. });
  472. const watchFiles = utils.files(cwd, ['js'].concat(program.watchExtensions));
  473. let runAgain = false;
  474. loadAndRun = () => {
  475. try {
  476. mocha.files = files;
  477. runAgain = false;
  478. runner = mocha.run(() => {
  479. runner = null;
  480. if (runAgain) {
  481. rerun();
  482. }
  483. });
  484. } catch (e) {
  485. console.log(e.stack);
  486. }
  487. };
  488. purge = () => {
  489. watchFiles.forEach(file => {
  490. delete require.cache[file];
  491. });
  492. };
  493. loadAndRun();
  494. rerun = () => {
  495. purge();
  496. stop();
  497. if (!program.grep) {
  498. mocha.grep(null);
  499. }
  500. mocha.suite = mocha.suite.clone();
  501. mocha.suite.ctx = new Mocha.Context();
  502. mocha.ui(program.ui);
  503. loadAndRun();
  504. };
  505. utils.watch(watchFiles, () => {
  506. runAgain = true;
  507. if (runner) {
  508. runner.abort();
  509. } else {
  510. rerun();
  511. }
  512. });
  513. } else {
  514. // load
  515. mocha.files = files;
  516. runner = mocha.run(program.exit ? exit : exitLater);
  517. }
  518. process.on('SIGINT', () => {
  519. runner.abort();
  520. // This is a hack:
  521. // Instead of `process.exit(130)`, set runner.failures to 130 (exit code for SIGINT)
  522. // The amount of failures will be emitted as error code later
  523. runner.failures = 130;
  524. });