Permalink
Browse files

Add check-coverage command, update tests and set up self coverage check

  • Loading branch information...
1 parent 2c56237 commit a3bb5f5925f50cf3b52b4605b070dc6e753f3514 @gotwarlost gotwarlost committed Oct 31, 2012
Showing with 192 additions and 1 deletion.
  1. +104 −0 lib/command/check-coverage.js
  2. +1 −0 package.json
  3. +6 −1 test/cli-helper.js
  4. +79 −0 test/cli/test-check-coverage-command.js
  5. +2 −0 test/run.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;
+
+
View
@@ -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": {
View
@@ -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 = {
@@ -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) {
@@ -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();
+ });
+ }
+};
View
@@ -67,6 +67,8 @@ function runTests(pat, forceCover) {
'**/node_modules/**',
'--x',
'**/test/**',
+ '--x',
+ '**/yui-load-hook.js',
path.resolve(__dirname, 'run-junit.js'),
'--',
pat || ''

0 comments on commit a3bb5f5

Please sign in to comment.