468 lines
10 KiB
JavaScript
Executable File
468 lines
10 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Module dependencies.
|
|
*/
|
|
|
|
var program = require('commander')
|
|
, sprintf = require('util').format
|
|
, path = require('path')
|
|
, fs = require('fs')
|
|
, glob = require('glob')
|
|
, resolve = path.resolve
|
|
, exists = fs.existsSync || path.existsSync
|
|
, Mocha = require('../')
|
|
, utils = Mocha.utils
|
|
, interfaces = Mocha.interfaces
|
|
, join = path.join
|
|
, basename = path.basename
|
|
, cwd = process.cwd()
|
|
, 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('-r, --require <name>', 'require the given module')
|
|
.option('-R, --reporter <name>', 'specify the reporter to use', 'dot')
|
|
.option('-u, --ui <name>', 'specify user-interface (bdd|tdd|exports)', 'bdd')
|
|
.option('-g, --grep <pattern>', 'only run tests matching <pattern>')
|
|
.option('-i, --invert', 'inverts --grep matches')
|
|
.option('-t, --timeout <ms>', 'set test-case timeout in milliseconds [2000]')
|
|
.option('-s, --slow <ms>', '"slow" test threshold in milliseconds [75]')
|
|
.option('-w, --watch', 'watch files for changes')
|
|
.option('-c, --colors', 'force enabling of colors')
|
|
.option('-C, --no-colors', 'force disabling of colors')
|
|
.option('-G, --growl', 'enable growl notification support')
|
|
.option('-d, --debug', "enable node's debugger, synonym for node --debug")
|
|
.option('-b, --bail', "bail after first test failure")
|
|
.option('-A, --async-only', "force all tests to take a callback (async)")
|
|
.option('-S, --sort', "sort test files")
|
|
.option('--recursive', 'include sub directories')
|
|
.option('--debug-brk', "enable node's debugger breaking on the first line")
|
|
.option('--globals <names>', 'allow the given comma-delimited global [names]', list, [])
|
|
.option('--check-leaks', 'check for global variable leaks')
|
|
.option('--interfaces', 'display available interfaces')
|
|
.option('--reporters', 'display available reporters')
|
|
.option('--compilers <ext>:<module>,...', 'use the given module(s) to compile files', list, [])
|
|
.option('--inline-diffs', 'display actual/expected differences inline within each string')
|
|
.option('--no-exit', 'require a clean shutdown of the event loop: mocha will not call process.exit')
|
|
|
|
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);
|
|
});
|
|
|
|
// mocha.opts support
|
|
|
|
try {
|
|
var opts = fs.readFileSync('test/mocha.opts', 'utf8')
|
|
.trim()
|
|
.split(/\s+/);
|
|
|
|
process.argv = process.argv
|
|
.slice(0, 2)
|
|
.concat(opts.concat(process.argv.slice(2)));
|
|
} catch (err) {
|
|
// ignore
|
|
}
|
|
|
|
// parse args
|
|
|
|
program.parse(process.argv);
|
|
|
|
// infinite stack traces
|
|
|
|
Error.stackTraceLimit = Infinity; // TODO: config
|
|
|
|
// reporter
|
|
|
|
mocha.reporter(program.reporter);
|
|
|
|
// interface
|
|
|
|
mocha.ui(program.ui);
|
|
|
|
// load reporter
|
|
|
|
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);
|
|
|
|
// --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));
|
|
|
|
// --invert
|
|
|
|
if (program.invert) mocha.invert();
|
|
|
|
// --check-leaks
|
|
|
|
if (program.checkLeaks) mocha.checkLeaks();
|
|
|
|
// --growl
|
|
|
|
if (program.growl) mocha.growl();
|
|
|
|
// --async-only
|
|
|
|
if (program.asyncOnly) mocha.asyncOnly();
|
|
|
|
// --globals
|
|
|
|
mocha.globals(globals);
|
|
|
|
// 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);
|
|
});
|
|
|
|
var re = new RegExp('\\.(' + extensions.join('|') + ')$');
|
|
|
|
// requires
|
|
|
|
requires.forEach(function(mod) {
|
|
require(mod);
|
|
});
|
|
|
|
// files
|
|
|
|
var files = []
|
|
, args = program.args;
|
|
|
|
// default files to test/*.{js,coffee}
|
|
|
|
if (!args.length) args.push('test');
|
|
|
|
args.forEach(function(arg){
|
|
files = files.concat(lookupFiles(arg, 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);
|
|
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()
|
|
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();
|
|
}
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
// load
|
|
|
|
mocha.files = files;
|
|
runner = mocha.run(program.exit ? process.exit : exitLater);
|
|
|
|
function exitLater(code) {
|
|
process.on('exit', function() { process.exit(code) })
|
|
}
|
|
|
|
process.on('SIGINT', function() { runner.abort(); })
|
|
|
|
// enable growl notifications
|
|
|
|
function growl(runner, reporter) {
|
|
var notify = require('growl');
|
|
|
|
runner.on('end', function(){
|
|
var stats = reporter.stats;
|
|
if (stats.failures) {
|
|
var msg = stats.failures + ' of ' + runner.total + ' tests failed';
|
|
notify(msg, { name: 'mocha', title: 'Failed', image: images.fail });
|
|
} else {
|
|
notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', {
|
|
name: 'mocha'
|
|
, title: 'Passed'
|
|
, image: images.pass
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* Lookup file names at the given `path`.
|
|
*/
|
|
|
|
function lookupFiles(path, recursive) {
|
|
var files = [];
|
|
|
|
if (!exists(path)) {
|
|
if (exists(path + '.js')) {
|
|
path += '.js'
|
|
} else {
|
|
files = glob.sync(path);
|
|
if (!files.length) throw new Error("cannot resolve path (or pattern) '" + path + "'");
|
|
return files;
|
|
}
|
|
}
|
|
|
|
var stat = fs.statSync(path);
|
|
if (stat.isFile()) return path;
|
|
|
|
fs.readdirSync(path).forEach(function(file){
|
|
file = join(path, file);
|
|
var stat = fs.statSync(file);
|
|
if (stat.isDirectory()) {
|
|
if (recursive) files = files.concat(lookupFiles(file, recursive));
|
|
return
|
|
}
|
|
if (!stat.isFile() || !re.test(file) || basename(file)[0] == '.') return;
|
|
files.push(file);
|
|
});
|
|
|
|
return files;
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|