Skip to content

Commit

Permalink
Add check-coverage command, update tests and set up self coverage check
Browse files Browse the repository at this point in the history
  • Loading branch information
gotwarlost committed Oct 31, 2012
1 parent 2c56237 commit a3bb5f5
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 1 deletion.
104 changes: 104 additions & 0 deletions lib/command/check-coverage.js
@@ -0,0 +1,104 @@
/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/

var nopt = require('nopt'),
path = require('path'),
fs = require('fs'),
Collector = require('../collector'),
formatOption = require('../util/help-formatter').formatOption,
util = require('util'),
utils = require('../object-utils'),
filesFor = require('../util/file-matcher').filesFor,
Command = require('./index');

function CheckCoverageCommand() {
Command.call(this);
}

CheckCoverageCommand.TYPE = 'check-coverage';
util.inherits(CheckCoverageCommand, Command);

Command.mix(CheckCoverageCommand, {
synopsis: function () {
return "checks overall coverage against thresholds from coverage JSON files. Exits 1 if thresholds are not met, 0 otherwise";
},

usage: function () {
console.error('\nUsage: ' + this.toolName() + ' ' + this.type() + ' <options> [<include-pattern>]\n\nOptions are:\n\n' +
[
formatOption('--statements <threshold>', 'The minimum statement coverage required as a percentage'),
formatOption('--functions <threshold>', 'The minimum statement threshold coverage required as a percentage'),
formatOption('--branches <threshold>', 'The minimum branch coverage required as a percentage'),
formatOption('--lines <threshold>', 'The minimum line coverage required as a percentage')
].join('\n\n') + '\n');

console.error('\n');

console.error('<include-pattern> is a fileset pattern that can be used to select one or more coverage files ' +
'for merge. This defaults to "**/coverage*.json"');

console.error('\n');
},

run: function (args, callback) {

var config = {
root: path,
dir: path,
statements: Number,
lines: Number,
branches: Number,
functions: Number,
verbose: Boolean
},
opts = nopt(config, { v : '--verbose' }, args, 0),
includePattern = '**/coverage*.json',
root,
collector = new Collector(),
errors = [];

if (opts.argv.remain.length > 0) {
includePattern = opts.argv.remain[0];
}

root = opts.root || process.cwd();
filesFor({
root: root,
includes: [ includePattern ]
}, function (err, files) {
if (err) { throw err; }
files.forEach(function (file) {
var coverageObject = JSON.parse(fs.readFileSync(file, 'utf8'));
collector.add(coverageObject);
});
var thresholds = {
statements: opts.statements || 0,
branches: opts.branches || 0,
lines: opts.lines || 0,
functions: opts.functions || 0
},
actuals = utils.summarizeCoverage(collector.getFinalCoverage());

if (opts.verbose) {
console.log('Compare actuals against thresholds');
console.log(JSON.stringify({ actuals: actuals, thresholds: thresholds }, undefined, 4));
}

Object.keys(thresholds).forEach(function (key) {
var actual = actuals[key].pct,
threshold = thresholds[key];

if (actual < threshold) {
errors.push('ERROR: Coverage for ' + key + ' (' + actual + '%) does not meet threshold (' + threshold + '%)');
}
});
return callback(errors.length === 0 ? null : errors.join("\n"));
});
}
});

module.exports = CheckCoverageCommand;


1 change: 1 addition & 0 deletions package.json
Expand Up @@ -14,6 +14,7 @@
"scripts": {
"pretest": "jshint --config ./node_modules/yui-lint/jshint.json lib/*js lib/command/*js lib/report/*js lib/store/*js lib/util/*js",
"test": "test/run.js",
"posttest": "node ./lib/cli.js check-coverage --statements 95 --branches 80",
"docs": "npm install yuidocjs && node node_modules/yuidocjs/lib/cli.js ."
},
"bin": {
Expand Down
7 changes: 6 additions & 1 deletion test/cli-helper.js
@@ -1,14 +1,15 @@
/*jslint nomen: true */
var path = require('path'),
fs = require('fs'),
util = require('util'),
cp = require('child_process'),
Module = require('module'),
originalLoader = Module._extensions['.js'],
common = require('./common'),
MAIN_FILE = path.resolve(__dirname, '..', 'lib', 'cli.js'),
DEFAULT_CWD = path.resolve(__dirname, 'cli', 'sample-project'),
COVER_ROOT = path.resolve(__dirname, '..'),
EXCLUDES = [ '**/node_modules/**', '**/test/** '],
EXCLUDES = [ '**/node_modules/**', '**/test/**', '**/yui-load-hook.js'],
COVER_VAR = '$$selfcover$$',
seq = 0,
OPTS = {
Expand Down Expand Up @@ -144,6 +145,10 @@ function customHook(lazyHook, callback) {
fs.writeFileSync(file, JSON.stringify(global[COVER_VAR], undefined, 4), 'utf8');
}
});
process.on('uncaughtException', function (ex) {
util.error(ex);
process.exit(1);
});
};

require('../lib/util/file-matcher').matcherFor({ root : COVER_ROOT, excludes: EXCLUDES, includes: [ '**/*.js'] }, function (err, matcher) {
Expand Down
79 changes: 79 additions & 0 deletions test/cli/test-check-coverage-command.js
@@ -0,0 +1,79 @@
/*jslint nomen: true */
var path = require('path'),
fs = require('fs'),
rimraf = require('rimraf'),
mkdirp = require('mkdirp'),
COMMAND = 'check-coverage',
COVER_COMMAND = 'cover',
DIR = path.resolve(__dirname, 'sample-project'),
OUTPUT_DIR = path.resolve(DIR, 'coverage'),
helper = require('../cli-helper'),
existsSync = fs.existsSync || path.existsSync,
run = helper.runCommand.bind(null, COMMAND),
runCover = helper.runCommand.bind(null, COVER_COMMAND);

module.exports = {
setUp: function (cb) {
rimraf.sync(OUTPUT_DIR);
mkdirp.sync(OUTPUT_DIR);
helper.resetOpts();
runCover([ 'test/run.js', '--report', 'none' ], function (results) {
cb();
});
},
tearDown: function (cb) {
rimraf.sync(OUTPUT_DIR);
cb();
},
"should fail on inadequate statement coverage": function (test) {
test.ok(existsSync(path.resolve(OUTPUT_DIR, 'coverage.json')));
run([ '--statements', '72' ], function (results) {
test.ok(!results.succeeded());
test.ok(results.grepError(/Coverage for statements/));
test.done();
});
},
"should fail on inadequate branch coverage": function (test) {
test.ok(existsSync(path.resolve(OUTPUT_DIR, 'coverage.json')));
run([ '--branches', '72' ], function (results) {
test.ok(!results.succeeded());
test.ok(results.grepError(/Coverage for branches/));
test.done();
});
},
"should fail on inadequate function coverage": function (test) {
test.ok(existsSync(path.resolve(OUTPUT_DIR, 'coverage.json')));
run([ '--functions', '72' ], function (results) {
test.ok(!results.succeeded());
test.ok(results.grepError(/Coverage for functions/));
test.done();
});
},
"should fail on inadequate line coverage": function (test) {
test.ok(existsSync(path.resolve(OUTPUT_DIR, 'coverage.json')));
run([ '--lines', '72' ], function (results) {
test.ok(!results.succeeded());
test.ok(results.grepError(/Coverage for lines/));
test.done();
});
},
"should fail with multiple reasons when multiple thresholds violated": function (test) {
test.ok(existsSync(path.resolve(OUTPUT_DIR, 'coverage.json')));
run([ '--statements=72','--functions=50','--branches=72','--lines=72', '-v' ], function (results) {
test.ok(!results.succeeded());
test.ok(results.grepError(/Coverage for lines/));
test.ok(results.grepError(/Coverage for statements/));
test.ok(results.grepError(/Coverage for branches/));
test.ok(!results.grepError(/Coverage for functions/));
test.ok(!results.grepOutput(/\\"actuals\\"/), "Verbose message not printed as expected");
test.done();
});
},
"should succeed with any threshold when no coverage found": function (test) {
test.ok(existsSync(path.resolve(OUTPUT_DIR, 'coverage.json')));
run([ '--statements', '72', '**/foobar.json' ], function (results) {
test.ok(results.succeeded());
test.done();
});
}
};
2 changes: 2 additions & 0 deletions test/run.js
Expand Up @@ -67,6 +67,8 @@ function runTests(pat, forceCover) {
'**/node_modules/**',
'--x',
'**/test/**',
'--x',
'**/yui-load-hook.js',
path.resolve(__dirname, 'run-junit.js'),
'--',
pat || ''
Expand Down

0 comments on commit a3bb5f5

Please sign in to comment.