UNPKG

14.3 kBPlain TextView Raw
1#!/usr/bin/env node
2'use strict';
3
4/* eslint no-unused-vars: off */
5
6/**
7 * Module dependencies.
8 */
9
10const program = require('commander');
11const path = require('path');
12const fs = require('fs');
13const resolve = path.resolve;
14const exists = fs.existsSync;
15const Mocha = require('../');
16const utils = Mocha.utils;
17const interfaceNames = Object.keys(Mocha.interfaces);
18const join = path.join;
19const cwd = process.cwd();
20const getOptions = require('./options');
21const mocha = new Mocha();
22
23/**
24 * Save timer references to avoid Sinon interfering (see GH-237).
25 */
26
27const Date = global.Date;
28const setTimeout = global.setTimeout;
29const setInterval = global.setInterval;
30const clearTimeout = global.clearTimeout;
31const 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 */
37const 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 */
48const 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 */
79const list = str => str.split(/ *, */);
80
81/**
82 * Parse multiple flag.
83 */
84const collect = (val, memo) => memo.concat(val);
85
86/**
87 * Hide the cursor.
88 */
89const hideCursor = () => {
90 process.stdout.write('\u001b[?25l');
91};
92
93/**
94 * Show the cursor.
95 */
96const showCursor = () => {
97 process.stdout.write('\u001b[?25h');
98};
99
100/**
101 * Stop play()ing.
102 */
103const stop = () => {
104 process.stdout.write('\u001b[2K');
105 clearInterval(play.timer);
106};
107
108/**
109 * Play the given array of strings.
110 */
111const 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
126let files = [];
127
128/**
129 * Globals.
130 */
131
132let globals = [];
133
134/**
135 * Requires.
136 */
137
138const requires = [];
139
140/**
141 * Images.
142 */
143
144const images = {
145 fail: path.join(__dirname, '..', 'images', 'error.png'),
146 pass: path.join(__dirname, '..', 'images', 'ok.png')
147};
148
149// options
150
151program
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
210program._name = 'mocha';
211
212// init command
213
214program
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
232program.on('option:globals', val => {
233 globals = globals.concat(list(val));
234});
235
236// --reporters
237
238program.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
259program.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
270module.paths.push(cwd, join(cwd, 'node_modules'));
271
272program.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
281if (!process.env.LOADED_MOCHA_OPTS) {
282 getOptions();
283}
284
285// parse args
286
287program.parse(process.argv);
288
289// infinite stack traces
290
291Error.stackTraceLimit = Infinity; // TODO: config
292
293// reporter options
294
295const reporterOptions = {};
296if (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
311mocha.reporter(program.reporter, reporterOptions);
312
313// load reporter
314
315let Reporter = null;
316try {
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
328if (!program.colors) {
329 mocha.useColors(false);
330}
331
332// --colors
333
334if (~process.argv.indexOf('--colors') || ~process.argv.indexOf('-c')) {
335 mocha.useColors(true);
336}
337
338// --inline-diffs
339
340if (program.inlineDiffs) {
341 mocha.useInlineDiffs(true);
342}
343
344// --no-diff
345
346if (process.argv.indexOf('--no-diff') !== -1) {
347 mocha.hideDiff(true);
348}
349
350// --slow <ms>
351
352if (program.slow) {
353 mocha.suite.slow(program.slow);
354}
355
356// --no-timeouts
357
358if (!program.timeouts) {
359 mocha.enableTimeouts(false);
360}
361
362// --timeout
363
364if (program.timeout) {
365 mocha.suite.timeout(program.timeout);
366}
367
368// --bail
369
370mocha.suite.bail(program.bail);
371
372// --grep
373
374if (program.grep) {
375 mocha.grep(program.grep);
376}
377
378// --fgrep
379
380if (program.fgrep) {
381 mocha.fgrep(program.fgrep);
382}
383
384// --invert
385
386if (program.invert) {
387 mocha.invert();
388}
389
390// --check-leaks
391
392if (program.checkLeaks) {
393 mocha.checkLeaks();
394}
395
396// --stack-trace
397
398if (program.fullTrace) {
399 mocha.fullTrace();
400}
401
402// --growl
403
404if (program.growl) {
405 mocha.growl();
406}
407
408// --async-only
409
410if (program.asyncOnly) {
411 mocha.asyncOnly();
412}
413
414// --delay
415
416if (program.delay) {
417 mocha.delay();
418}
419
420// --allow-uncaught
421
422if (program.allowUncaught) {
423 mocha.allowUncaught();
424}
425
426// --globals
427
428mocha.globals(globals);
429
430// --retries
431
432if (program.retries) {
433 mocha.suite.retries(program.retries);
434}
435
436// --forbid-only
437
438if (program.forbidOnly) mocha.forbidOnly();
439
440// --forbid-pending
441
442if (program.forbidPending) mocha.forbidPending();
443
444// custom compiler support
445
446if (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}
450const extensions = ['js'];
451program.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
466requires.forEach(mod => {
467 require(mod);
468});
469
470// interface
471
472mocha.ui(program.ui);
473
474// args
475
476const args = program.args;
477
478// default files to test/*.{js,coffee}
479
480if (!args.length) {
481 args.push('test');
482}
483
484args.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
500if (!files.length) {
501 console.error('No test files found');
502 process.exit(1);
503}
504
505// resolve
506let fileArgs = program.file.map(path => resolve(path));
507files = files.map(path => resolve(path));
508
509if (program.sort) {
510 files.sort();
511}
512
513// add files given through --file to be ran first
514files = fileArgs.concat(files);
515
516// --watch
517
518let runner;
519let loadAndRun;
520let purge;
521let rerun;
522
523if (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
585process.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});