node-qunit
Version:
QUnit testing framework for Node.js
209 lines (168 loc) • 5.13 kB
JavaScript
var path = require('path'),
coverage = require('./coverage'),
cp = require('child_process'),
_ = require('underscore'),
log = exports.log = require('./log');
var options,
noop = function() {};
options = exports.options = {
// logging options
log: {
// log assertions overview
assertions: true,
// log expected and actual values for failed tests
errors: true,
// log tests overview
tests: true,
// log summary
summary: true,
// log global summary (all files)
globalSummary: true,
// log coverage
coverage: true,
// log global coverage (all files)
globalCoverage: true,
// log currently testing code file
testing: true
},
// run test coverage tool
coverage: false,
// define dependencies, which are required then before code
deps: null,
// define namespace your code will be attached to on global['your namespace']
namespace: null,
// max amount of ms child can be blocked, after that we assume running an infinite loop
maxBlockDuration: 2000
};
/**
* Run one spawned instance with tests
* @param {Object} opts
* @param {Function} callback
*/
function runOne(opts, callback) {
var child;
var pingCheckTimeoutId;
var argv = process.argv.slice();
argv.push(JSON.stringify(opts));
child = cp.fork(__dirname + '/child.js', argv, {env: process.env});
function kill() {
process.removeListener('exit', kill);
child.kill();
}
function complete(err, data) {
kill();
clearTimeout(pingCheckTimeoutId);
callback(err, data)
}
child.on('message', function(msg) {
switch (msg.event) {
case 'ping':
clearTimeout(pingCheckTimeoutId);
pingCheckTimeoutId = setTimeout(function() {
complete(new Error('Process blocked for too long'));
}, opts.maxBlockDuration);
break;
case 'assertionDone':
log.add('assertions', msg.data);
break;
case 'testDone':
log.add('tests', msg.data);
break;
case 'done':
clearTimeout(pingCheckTimeoutId);
msg.data.code = opts.code.path;
log.add('summaries', msg.data);
if (opts.coverage) {
coverage.add(msg.data.coverage);
msg.data.coverage = coverage.get();
msg.data.coverage.code = msg.data.code;
log.add('coverages', msg.data.coverage);
}
if (opts.log.testing) {
console.log('done');
}
complete(null, msg.data);
break;
case 'uncaughtException':
complete(_.extend(new Error(), msg.data));
break;
}
});
process.on('exit', kill);
if (opts.log.testing) {
console.log('\nTesting ', opts.code.path + ' ... ');
}
}
/**
* Make an absolute path from relative
* @param {string|Object} file
* @return {Object}
*/
function absPath(file) {
if (typeof file === 'string') {
file = {path: file};
}
if (file.path.charAt(0) != '/') {
file.path = path.resolve(process.cwd(), file.path);
}
return file;
}
/**
* Convert path or array of paths to array of abs paths
* @param {Array|string} files
* @return {Array}
*/
function absPaths(files) {
var ret = [];
if (Array.isArray(files)) {
files.forEach(function(file) {
ret.push(absPath(file));
});
} else if (files) {
ret.push(absPath(files));
}
return ret;
}
/**
* Run tests in spawned node instance async for every test.
* @param {Object|Array} files
* @param {Function} callback optional
*/
exports.run = function(files, callback) {
var filesCount = 0;
callback || (callback = noop);
if (!Array.isArray(files)) {
files = [files];
}
if (options.coverage || files[0].coverage) coverage.setup(options.coverage);
files.forEach(function(file) {
var opts = _.extend({}, options, file);
!opts.log && (opts.log = {});
opts.deps = absPaths(opts.deps);
opts.code = absPath(opts.code);
opts.tests = absPaths(opts.tests);
runOne(opts, function(err) {
if (err) {
return callback(err, log.stats());
}
filesCount++;
if (filesCount >= files.length) {
_.each(opts.log, function(val, name) {
if (val && log.print[name]) {
log.print[name]();
}
});
// Write coverage report.
if (opts.coverage) coverage.report();
callback(null, log.stats());
}
});
});
};
/**
* Set options
* @param {Object}
*/
exports.setup = function(opts) {
_.extend(options, opts);
};