481 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			481 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env node
 | |
| 
 | |
| /**
 | |
|  * Module dependencies.
 | |
|  */
 | |
| 
 | |
| var program = require('commander'),
 | |
|   path = require('path'),
 | |
|   fs = require('fs'),
 | |
|   resolve = path.resolve,
 | |
|   exists = fs.existsSync || path.existsSync,
 | |
|   Mocha = require('../'),
 | |
|   utils = Mocha.utils,
 | |
|   join = path.join,
 | |
|   cwd = process.cwd(),
 | |
|   getOptions = require('./options'),
 | |
|   mocha = new Mocha;
 | |
| 
 | |
| /**
 | |
|  * Save timer references to avoid Sinon interfering (see GH-237).
 | |
|  */
 | |
| 
 | |
| var Date = global.Date
 | |
|   , setTimeout = global.setTimeout
 | |
|   , setInterval = global.setInterval
 | |
|   , clearTimeout = global.clearTimeout
 | |
|   , clearInterval = global.clearInterval;
 | |
| 
 | |
| /**
 | |
|  * Files.
 | |
|  */
 | |
| 
 | |
| var files = [];
 | |
| 
 | |
| /**
 | |
|  * Globals.
 | |
|  */
 | |
| 
 | |
| var globals = [];
 | |
| 
 | |
| /**
 | |
|  * Requires.
 | |
|  */
 | |
| 
 | |
| var requires = [];
 | |
| 
 | |
| /**
 | |
|  * Images.
 | |
|  */
 | |
| 
 | |
| var images = {
 | |
|     fail: __dirname + '/../images/error.png'
 | |
|   , pass: __dirname + '/../images/ok.png'
 | |
| };
 | |
| 
 | |
| // options
 | |
| 
 | |
| program
 | |
|   .version(JSON.parse(fs.readFileSync(__dirname + '/../package.json', 'utf8')).version)
 | |
|   .usage('[debug] [options] [files]')
 | |
|   .option('-A, --async-only', "force all tests to take a callback (async) or return a promise")
 | |
|   .option('-c, --colors', 'force enabling of colors')
 | |
|   .option('-C, --no-colors', 'force disabling of colors')
 | |
|   .option('-G, --growl', 'enable growl notification support')
 | |
|   .option('-O, --reporter-options <k=v,k2=v2,...>', 'reporter-specific options')
 | |
|   .option('-R, --reporter <name>', 'specify the reporter to use', 'spec')
 | |
|   .option('-S, --sort', "sort test files")
 | |
|   .option('-b, --bail', "bail after first test failure")
 | |
|   .option('-d, --debug', "enable node's debugger, synonym for node --debug")
 | |
|   .option('-g, --grep <pattern>', 'only run tests matching <pattern>')
 | |
|   .option('-f, --fgrep <string>', 'only run tests containing <string>')
 | |
|   .option('-gc, --expose-gc', 'expose gc extension')
 | |
|   .option('-i, --invert', 'inverts --grep and --fgrep matches')
 | |
|   .option('-r, --require <name>', 'require the given module')
 | |
|   .option('-s, --slow <ms>', '"slow" test threshold in milliseconds [75]')
 | |
|   .option('-t, --timeout <ms>', 'set test-case timeout in milliseconds [2000]')
 | |
|   .option('-u, --ui <name>', 'specify user-interface (bdd|tdd|exports)', 'bdd')
 | |
|   .option('-w, --watch', 'watch files for changes')
 | |
|   .option('--check-leaks', 'check for global variable leaks')
 | |
|   .option('--full-trace', 'display the full stack trace')
 | |
|   .option('--compilers <ext>:<module>,...', 'use the given module(s) to compile files', list, [])
 | |
|   .option('--debug-brk', "enable node's debugger breaking on the first line")
 | |
|   .option('--globals <names>', 'allow the given comma-delimited global [names]', list, [])
 | |
|   .option('--es_staging', 'enable all staged features')
 | |
|   .option('--harmony<_classes,_generators,...>', 'all node --harmony* flags are available')
 | |
|   .option('--icu-data-dir', 'include ICU data')
 | |
|   .option('--inline-diffs', 'display actual/expected differences inline within each string')
 | |
|   .option('--interfaces', 'display available interfaces')
 | |
|   .option('--no-deprecation', 'silence deprecation warnings')
 | |
|   .option('--no-exit', 'require a clean shutdown of the event loop: mocha will not call process.exit')
 | |
|   .option('--no-timeouts', 'disables timeouts, given implicitly with --debug')
 | |
|   .option('--opts <path>', 'specify opts path', 'test/mocha.opts')
 | |
|   .option('--perf-basic-prof', 'enable perf linux profiler (basic support)')
 | |
|   .option('--prof', 'log statistical profiling information')
 | |
|   .option('--log-timer-events', 'Time events including external callbacks')
 | |
|   .option('--recursive', 'include sub directories')
 | |
|   .option('--reporters', 'display available reporters')
 | |
|   .option('--retries <times>', 'set numbers of time to retry a failed test case')
 | |
|   .option('--throw-deprecation', 'throw an exception anytime a deprecated function is used')
 | |
|   .option('--trace', 'trace function calls')
 | |
|   .option('--trace-deprecation', 'show stack traces on deprecations')
 | |
|   .option('--use_strict', 'enforce strict mode')
 | |
|   .option('--watch-extensions <ext>,...', 'additional extensions to monitor with --watch', list, [])
 | |
|   .option('--delay', 'wait for async suite definition')
 | |
| 
 | |
| program.name = 'mocha';
 | |
| 
 | |
| // init command
 | |
| 
 | |
| program
 | |
|   .command('init <path>')
 | |
|   .description('initialize a client-side mocha setup at <path>')
 | |
|   .action(function(path){
 | |
|     var mkdir = require('mkdirp');
 | |
|     mkdir.sync(path);
 | |
|     var css = fs.readFileSync(join(__dirname, '..', 'mocha.css'));
 | |
|     var js = fs.readFileSync(join(__dirname, '..', 'mocha.js'));
 | |
|     var tmpl = fs.readFileSync(join(__dirname, '..', 'lib/template.html'));
 | |
|     fs.writeFileSync(join(path, 'mocha.css'), css);
 | |
|     fs.writeFileSync(join(path, 'mocha.js'), js);
 | |
|     fs.writeFileSync(join(path, 'tests.js'), '');
 | |
|     fs.writeFileSync(join(path, 'index.html'), tmpl);
 | |
|     process.exit(0);
 | |
|   });
 | |
| 
 | |
| // --globals
 | |
| 
 | |
| program.on('globals', function(val){
 | |
|   globals = globals.concat(list(val));
 | |
| });
 | |
| 
 | |
| // --reporters
 | |
| 
 | |
| program.on('reporters', function(){
 | |
|   console.log();
 | |
|   console.log('    dot - dot matrix');
 | |
|   console.log('    doc - html documentation');
 | |
|   console.log('    spec - hierarchical spec list');
 | |
|   console.log('    json - single json object');
 | |
|   console.log('    progress - progress bar');
 | |
|   console.log('    list - spec-style listing');
 | |
|   console.log('    tap - test-anything-protocol');
 | |
|   console.log('    landing - unicode landing strip');
 | |
|   console.log('    xunit - xunit reporter');
 | |
|   console.log('    html-cov - HTML test coverage');
 | |
|   console.log('    json-cov - JSON test coverage');
 | |
|   console.log('    min - minimal reporter (great with --watch)');
 | |
|   console.log('    json-stream - newline delimited json events');
 | |
|   console.log('    markdown - markdown documentation (github flavour)');
 | |
|   console.log('    nyan - nyan cat!');
 | |
|   console.log();
 | |
|   process.exit();
 | |
| });
 | |
| 
 | |
| // --interfaces
 | |
| 
 | |
| program.on('interfaces', function(){
 | |
|   console.log('');
 | |
|   console.log('    bdd');
 | |
|   console.log('    tdd');
 | |
|   console.log('    qunit');
 | |
|   console.log('    exports');
 | |
|   console.log('');
 | |
|   process.exit();
 | |
| });
 | |
| 
 | |
| // -r, --require
 | |
| 
 | |
| module.paths.push(cwd, join(cwd, 'node_modules'));
 | |
| 
 | |
| program.on('require', function(mod){
 | |
|   var abs = exists(mod) || exists(mod + '.js');
 | |
|   if (abs) mod = resolve(mod);
 | |
|   requires.push(mod);
 | |
| });
 | |
| 
 | |
| // If not already done, load mocha.opts
 | |
| if (!process.env.LOADED_MOCHA_OPTS) {
 | |
|   getOptions();
 | |
| }
 | |
| 
 | |
| // parse args
 | |
| 
 | |
| program.parse(process.argv);
 | |
| 
 | |
| // infinite stack traces
 | |
| 
 | |
| Error.stackTraceLimit = Infinity; // TODO: config
 | |
| 
 | |
| // reporter options
 | |
| 
 | |
| var reporterOptions = {};
 | |
| if (program.reporterOptions !== undefined) {
 | |
|     program.reporterOptions.split(",").forEach(function(opt) {
 | |
|         var L = opt.split("=");
 | |
|         if (L.length > 2 || L.length === 0) {
 | |
|             throw new Error("invalid reporter option '" + opt + "'");
 | |
|         } else if (L.length === 2) {
 | |
|             reporterOptions[L[0]] = L[1];
 | |
|         } else {
 | |
|             reporterOptions[L[0]] = true;
 | |
|         }
 | |
|     });
 | |
| }
 | |
| 
 | |
| // reporter
 | |
| 
 | |
| mocha.reporter(program.reporter, reporterOptions);
 | |
| 
 | |
| // load reporter
 | |
| 
 | |
| var Reporter = null;
 | |
| try {
 | |
|   Reporter = require('../lib/reporters/' + program.reporter);
 | |
| } catch (err) {
 | |
|   try {
 | |
|     Reporter = require(program.reporter);
 | |
|   } catch (err) {
 | |
|     throw new Error('reporter "' + program.reporter + '" does not exist');
 | |
|   }
 | |
| }
 | |
| 
 | |
| // --no-colors
 | |
| 
 | |
| if (!program.colors) mocha.useColors(false);
 | |
| 
 | |
| // --colors
 | |
| 
 | |
| if (~process.argv.indexOf('--colors') ||
 | |
|     ~process.argv.indexOf('-c')) {
 | |
|   mocha.useColors(true);
 | |
| }
 | |
| 
 | |
| // --inline-diffs
 | |
| 
 | |
| if (program.inlineDiffs) mocha.useInlineDiffs(true);
 | |
| 
 | |
| // --slow <ms>
 | |
| 
 | |
| if (program.slow) mocha.suite.slow(program.slow);
 | |
| 
 | |
| // --no-timeouts
 | |
| 
 | |
| if (!program.timeouts) mocha.enableTimeouts(false);
 | |
| 
 | |
| // --timeout
 | |
| 
 | |
| if (program.timeout) mocha.suite.timeout(program.timeout);
 | |
| 
 | |
| // --bail
 | |
| 
 | |
| mocha.suite.bail(program.bail);
 | |
| 
 | |
| // --grep
 | |
| 
 | |
| if (program.grep) mocha.grep(new RegExp(program.grep));
 | |
| 
 | |
| // --fgrep
 | |
| 
 | |
| if (program.fgrep) mocha.grep(program.fgrep);
 | |
| 
 | |
| // --invert
 | |
| 
 | |
| if (program.invert) mocha.invert();
 | |
| 
 | |
| // --check-leaks
 | |
| 
 | |
| if (program.checkLeaks) mocha.checkLeaks();
 | |
| 
 | |
| // --stack-trace
 | |
| 
 | |
| if(program.fullTrace) mocha.fullTrace();
 | |
| 
 | |
| // --growl
 | |
| 
 | |
| if (program.growl) mocha.growl();
 | |
| 
 | |
| // --async-only
 | |
| 
 | |
| if (program.asyncOnly) mocha.asyncOnly();
 | |
| 
 | |
| // --delay
 | |
| 
 | |
| if (program.delay) mocha.delay();
 | |
| 
 | |
| // --globals
 | |
| 
 | |
| mocha.globals(globals);
 | |
| 
 | |
| // --retries
 | |
| 
 | |
| if (program.retries) mocha.suite.retries(program.retries);
 | |
| 
 | |
| // custom compiler support
 | |
| 
 | |
| var extensions = ['js'];
 | |
| program.compilers.forEach(function(c) {
 | |
|   var compiler = c.split(':')
 | |
|     , ext = compiler[0]
 | |
|     , mod = compiler[1];
 | |
| 
 | |
|   if (mod[0] == '.') mod = join(process.cwd(), mod);
 | |
|   require(mod);
 | |
|   extensions.push(ext);
 | |
|   program.watchExtensions.push(ext);
 | |
| });
 | |
| 
 | |
| // requires
 | |
| 
 | |
| requires.forEach(function(mod) {
 | |
|   require(mod);
 | |
| });
 | |
| 
 | |
| // interface
 | |
| 
 | |
| mocha.ui(program.ui);
 | |
| 
 | |
| //args
 | |
| 
 | |
| var args = program.args;
 | |
| 
 | |
| // default files to test/*.{js,coffee}
 | |
| 
 | |
| if (!args.length) args.push('test');
 | |
| 
 | |
| args.forEach(function(arg){
 | |
|   files = files.concat(utils.lookupFiles(arg, extensions, program.recursive));
 | |
| });
 | |
| 
 | |
| // resolve
 | |
| 
 | |
| files = files.map(function(path){
 | |
|   return resolve(path);
 | |
| });
 | |
| 
 | |
| if (program.sort) {
 | |
|   files.sort();
 | |
| }
 | |
| 
 | |
| // --watch
 | |
| 
 | |
| var runner;
 | |
| if (program.watch) {
 | |
|   console.log();
 | |
|   hideCursor();
 | |
|   process.on('SIGINT', function(){
 | |
|     showCursor();
 | |
|     console.log('\n');
 | |
|     process.exit();
 | |
|   });
 | |
| 
 | |
| 
 | |
|   var watchFiles = utils.files(cwd, [ 'js' ].concat(program.watchExtensions));
 | |
|   var runAgain = false;
 | |
| 
 | |
|   function loadAndRun() {
 | |
|     try {
 | |
|       mocha.files = files;
 | |
|       runAgain = false;
 | |
|       runner = mocha.run(function(){
 | |
|         runner = null;
 | |
|         if (runAgain) {
 | |
|           rerun();
 | |
|         }
 | |
|       });
 | |
|     } catch(e) {
 | |
|       console.log(e.stack);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   function purge() {
 | |
|     watchFiles.forEach(function(file){
 | |
|       delete require.cache[file];
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   loadAndRun();
 | |
| 
 | |
|   function rerun() {
 | |
|     purge();
 | |
|     stop()
 | |
|     if (!program.grep)
 | |
|       mocha.grep(null);
 | |
|     mocha.suite = mocha.suite.clone();
 | |
|     mocha.suite.ctx = new Mocha.Context;
 | |
|     mocha.ui(program.ui);
 | |
|     loadAndRun();
 | |
|   }
 | |
| 
 | |
|   utils.watch(watchFiles, function(){
 | |
|     runAgain = true;
 | |
|     if (runner) {
 | |
|       runner.abort();
 | |
|     } else {
 | |
|       rerun();
 | |
|     }
 | |
|   });
 | |
| 
 | |
| } else {
 | |
| 
 | |
| // load
 | |
| 
 | |
|   mocha.files = files;
 | |
|   runner = mocha.run(program.exit ? exit : exitLater);
 | |
| 
 | |
| }
 | |
| 
 | |
| function exitLater(code) {
 | |
|   process.on('exit', function() { process.exit(code) })
 | |
| }
 | |
| 
 | |
| function exit(code) {
 | |
|   // flush output for Node.js Windows pipe bug
 | |
|   // https://github.com/joyent/node/issues/6247 is just one bug example
 | |
|   // https://github.com/visionmedia/mocha/issues/333 has a good discussion
 | |
|   function done() {
 | |
|     if (!(draining--)) process.exit(code);
 | |
|   }
 | |
| 
 | |
|   var draining = 0;
 | |
|   var streams = [process.stdout, process.stderr];
 | |
| 
 | |
|   streams.forEach(function(stream){
 | |
|     // submit empty write request and wait for completion
 | |
|     draining += 1;
 | |
|     stream.write('', done);
 | |
|   });
 | |
| 
 | |
|   done();
 | |
| }
 | |
| 
 | |
| process.on('SIGINT', function() { runner.abort(); })
 | |
| 
 | |
| /**
 | |
|  * Parse list.
 | |
|  */
 | |
| 
 | |
| function list(str) {
 | |
|   return str.split(/ *, */);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Hide the cursor.
 | |
|  */
 | |
| 
 | |
| function hideCursor(){
 | |
|   process.stdout.write('\u001b[?25l');
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Show the cursor.
 | |
|  */
 | |
| 
 | |
| function showCursor(){
 | |
|   process.stdout.write('\u001b[?25h');
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Stop play()ing.
 | |
|  */
 | |
| 
 | |
| function stop() {
 | |
|   process.stdout.write('\u001b[2K');
 | |
|   clearInterval(play.timer);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Play the given array of strings.
 | |
|  */
 | |
| 
 | |
| function play(arr, interval) {
 | |
|   var len = arr.length
 | |
|     , interval = interval || 100
 | |
|     , i = 0;
 | |
| 
 | |
|   play.timer = setInterval(function(){
 | |
|     var str = arr[i++ % len];
 | |
|     process.stdout.write('\u001b[0G' + str);
 | |
|   }, interval);
 | |
| }
 | 
