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