postman
Version:
The Postman will help deliver messages around your application
236 lines (212 loc) • 6.49 kB
JavaScript
/**
* Some functions are borrowed by Expresso http://github.com/visionmedia/expresso
*/
var util = require( "util" ),
path = require( "path" ),
fs = require( "fs" ),
spawn = require( "child_process" ).spawn;
var boring = false;
var jscoveragePath = path.normalize( __dirname + "/../deps/jscoverage/jscoverage" );
/**
* Pad the given string to the maximum width provided.
*
* @param {String} str
* @param {Number} width
* @return {String}
*/
function lpad(str, width) {
str = String(str);
var n = width - str.length;
if (n < 1) return str;
while (n--) str = ' ' + str;
return str;
}
/**
* Pad the given string to the maximum width provided.
*
* @param {String} str
* @param {Number} width
* @return {String}
*/
function rpad(str, width) {
str = String(str);
var n = width - str.length;
if (n < 1) return str;
while (n--) str = str + ' ';
return str;
}
/**
* Colorized util.error().
*
* @param {String} str
*/
function print(str){
util.error(colorize(str));
}
/**
* Colorize the given string using ansi-escape sequences.
* Disabled when --boring is set.
*
* @param {String} str
* @return {String}
*/
function colorize(str){
var colors = { bold: 1, red: 31, green: 32, yellow: 33 };
return str.replace(/\[(\w+)\]\{([^]*?)\}/g, function(_, color, str){
return boring
? str
: '\x1B[' + colors[color] + 'm' + str + '\x1B[0m';
});
}
/**
* Populate code coverage data.
*
* @param {Object} cov
*/
function populateCoverage(cov) {
cov.LOC =
cov.SLOC =
cov.totalFiles =
cov.totalHits =
cov.totalMisses =
cov.coverage = 0;
for (var name in cov) {
var file = cov[name];
if (Array.isArray(file)) {
// Stats
++cov.totalFiles;
cov.totalHits += file.totalHits = coverage(file, true);
cov.totalMisses += file.totalMisses = coverage(file, false);
file.totalLines = file.totalHits + file.totalMisses;
cov.SLOC += file.SLOC = file.totalLines;
if (!file.source) file.source = [];
cov.LOC += file.LOC = file.source.length;
file.coverage = (file.totalHits / file.totalLines) * 100;
// Source
var width = file.source.length.toString().length;
file.source = file.source.map(function(line, i){
++i;
var hits = file[i] === 0 ? 0 : (file[i] || ' ');
if (!boring) {
if (hits === 0) {
hits = '\x1b[31m' + hits + '\x1b[0m';
line = '\x1b[41m' + line + '\x1b[0m';
} else {
hits = '\x1b[32m' + hits + '\x1b[0m';
}
}
return '\n ' + lpad(i, width) + ' | ' + hits + ' | ' + line;
}).join('');
}
}
cov.coverage = (cov.totalHits / cov.SLOC) * 100;
}
/**
* Total coverage for the given file data.
*
* @param {Array} data
* @return {Type}
*/
function coverage(data, val) {
var n = 0;
for (var i = 0, len = data.length; i < len; ++i) {
if (data[i] !== undefined && data[i] == val) ++n;
}
return n;
}
/**
* Create a temp dir
*
* @param {Function} callback
*/
function mktemp( callback ) {
var child,
dir = "";
child = spawn( "mktemp", [ "-dt", "jscoverage.XXXXXXXXXX"] );
child.stderr.on( "data", function( err ) {
if ( err.toString().trim() ) {
throw new Error( err );
}
});
child.stdout.on( "data", function( path ) {
dir += path.toString().trim();
});
child.on( "exit", function( code ) {
callback( dir );
});
}
/**
* Report test coverage.
*
* @param {Object} cov
*/
exports.reportCoverage = function(cov) {
populateCoverage(cov);
// Stats
print('\n [bold]{Test Coverage}\n');
var sep = ' +------------------------------------------+----------+------+------+--------+';
util.puts(sep);
util.puts(' | filename ' + lpad('', 31) + ' | coverage | LOC | SLOC | missed |');
util.puts(sep);
for (var name in cov) {
var file = cov[name];
if (Array.isArray(file)) {
util.print(' | ' + rpad(name, 40));
util.print(' | ' + lpad(file.coverage.toFixed(2), 8));
util.print(' | ' + lpad(file.LOC, 4));
util.print(' | ' + lpad(file.SLOC, 4));
util.print(' | ' + lpad(file.totalMisses, 6));
util.print(' |\n');
}
}
util.puts(sep);
util.print(' |' + rpad('', 41));
util.print(' | ' + lpad(cov.coverage.toFixed(2), 8));
util.print(' | ' + lpad(cov.LOC, 4));
util.print(' | ' + lpad(cov.SLOC, 4));
util.print(' | ' + lpad(cov.totalMisses, 6));
util.print(' |\n');
util.puts(sep);
// Source
for (var name in cov) {
if (name.match(/\.js$/)) {
var file = cov[name];
print('\n [bold]{' + name + '}:');
print(file.source);
util.print('\n');
}
}
};
/**
* Add instruments to code using jscoverage.
* jscoverage is inflexible if I want to work with files, not dirs,
* so I symlink a needed file together with dir structure to get the right path in report
*
* @param {String} origin path to the file
* @param {Function} callback
*/
exports.instrument = function( origin, callback ) {
mktemp( function( tmp ) {
var // relative path to the file
pathRel = origin.substr( process.cwd().length ),
// path to the symlinked file
src = path.join( tmp, "code", pathRel ),
// path to the instrumented file
cov = path.join( tmp, "cov", pathRel );
// using spawned mkdir to create the dir recursively
spawn( "mkdir", ["-p", path.dirname(src) ]).on( "exit", function() {
// now symlink the file which have to be instrumented
fs.symlinkSync( origin, src );
var child = spawn( jscoveragePath, ["-v", path.join( tmp, "code"), path.join( tmp, "cov")] );
child.on( "exit", function() {
callback( cov, function() {
// remove tmp dir
spawn( "rm", ["-fr", tmp] );
});
});
child.stderr.on( "data", function( buf ) {
throw new Error( buf );
});
});
});
};