Skip to content

Commit

Permalink
#22 refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
kof committed Sep 12, 2011
1 parent e92568d commit 1e47411
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 234 deletions.
3 changes: 0 additions & 3 deletions Makefile
@@ -1,6 +1,3 @@
install:
cd deps/jscoverage && ./configure && make

test:
node ./test/testrunner.js

Expand Down
11 changes: 11 additions & 0 deletions lib/child.js
Expand Up @@ -28,6 +28,13 @@ if (options.paths){
require.paths.push.apply(require.paths, options.paths);
}

/**
* Calculate coverage stats using bunker
*/
function calcCoverage() {

}

/**
* Callback for each started test.
* @param {Object} test
Expand Down Expand Up @@ -76,6 +83,10 @@ QUnit.done = function done(res) {
// only the last one has correct data #wtf
clearTimeout(done.timeout);
done.timeout = setTimeout(function() {
if (options.coverage) {
res.coverage = calcCoverage();
}

process.send({
event: 'done',
data: res
Expand Down
244 changes: 14 additions & 230 deletions lib/coverage.js
@@ -1,236 +1,20 @@
/**
* Some functions are borrowed by Expresso http://github.com/visionmedia/expresso
*/
var bunker = require('bunker'),
fs = require('fs');

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 = "";
exports.instrument = function(path) {
var src = fs.readFileSync(path, 'utf-8'),
newSrc = bunker(src).compile();

child = spawn( "mktemp", [ "-dt", "jscoverage.XXXXXXXXXX"] );
fs.renameSync(src, '__' + src);
fs.writeFileSync(path, newSrc, 'utf-8');
};

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');
}
exports.restore = function(path) {
// do it only if the original file exist
if (fs.statSync('__' + path).isFile()) {
fs.unlinkSync(path);
fs.renameSync('__' + path, path);
}
};

/**
* 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 );
});

});
});
};
};
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -14,7 +14,7 @@
"bin": {"qunit": "./bin/cli.js"},
"engines": {"node": ">= 0.5.0"},
"scripts": {
"install": "make install"
"test": "make test"
},
"dependencies": {
"underscore": ">= 1.1.7",
Expand Down

0 comments on commit 1e47411

Please sign in to comment.