Permalink
Browse files

Make reporting low/ high watermarks configurable

  • Loading branch information...
1 parent 82660fe commit aebc7291dff3cfa9755117eceab6d1229352805d @gotwarlost committed Dec 28, 2013
@@ -72,6 +72,8 @@ function run(args, commandName, enableHooks, callback) {
}
},
config = configuration.loadFile(opts.config, overrides),
+ watermarks = config.reporting.watermarks(),
+ reportOpts,
verbose = config.verbose,
cmdAndArgs = opts.argv.remain,
cmd,
@@ -111,21 +113,22 @@ function run(args, commandName, enableHooks, callback) {
if (enableHooks) {
reportingDir = path.resolve(config.reporting.dir());
+ reportOpts = { dir: reportingDir, watermarks: watermarks };
mkdirp.sync(reportingDir); //ensure we fail early if we cannot do this
reports.push.apply(reports, config.reporting.reports().map(function (r) {
- return Report.create(r, { dir: reportingDir });
+ return Report.create(r, reportOpts);
}));
if (config.reporting.print() !== 'none') {
switch (config.reporting.print()) {
case 'detail':
- reports.push(Report.create('text'));
+ reports.push(Report.create('text', reportOpts));
break;
case 'both':
- reports.push(Report.create('text'));
- reports.push(Report.create('text-summary'));
+ reports.push(Report.create('text', reportOpts));
+ reports.push(Report.create('text-summary', reportOpts));
break;
default:
- reports.push(Report.create('text-summary'));
+ reports.push(Report.create('text-summary', reportOpts));
break;
}
}
View
@@ -12,7 +12,8 @@ var nopt = require('nopt'),
formatOption = require('../util/help-formatter').formatOption,
filesFor = require('../util/file-matcher').filesFor,
util = require('util'),
- Command = require('./index');
+ Command = require('./index'),
+ configuration = require('../configuration');
function ReportCommand() {
Command.call(this);
@@ -29,6 +30,7 @@ Command.mix(ReportCommand, {
usage: function () {
console.error('\nUsage: ' + this.toolName() + ' ' + this.type() + ' <options> [ <format> [<include-pattern>] ]\n\nOptions are:\n\n' +
[
+ formatOption('--config <path-to-config>', 'the configuration file to use, defaults to .istanbul.yml'),
formatOption('--root <input-directory>', 'The input root directory for finding coverage files'),
formatOption('--dir <report-directory>', 'The output directory where files will be written. This defaults to ./coverage/'),
formatOption('--verbose, -v', 'verbose mode')
@@ -45,18 +47,30 @@ Command.mix(ReportCommand, {
run: function (args, callback) {
- var config = {
+ var template = {
+ config: path,
root: path,
dir: path,
verbose: Boolean
},
- opts = nopt(config, { v : '--verbose' }, args, 0),
+ opts = nopt(template, { v : '--verbose' }, args, 0),
fmtAndArgs = opts.argv.remain,
fmt = 'lcov',
includePattern = '**/coverage*.json',
reporter,
root,
- collector = new Collector();
+ collector = new Collector(),
+ config = configuration.loadFile(opts.config, {
+ verbose: opts.verbose,
+ reporting: {
+ dir: opts.dir
+ }
+ }),
+ reportOpts = {
+ verbose: config.verbose,
+ dir: config.reporting.dir(),
+ watermarks: config.reporting.watermarks()
+ };
if (fmtAndArgs.length > 0) {
fmt = fmtAndArgs[0];
@@ -66,10 +80,8 @@ Command.mix(ReportCommand, {
includePattern = fmtAndArgs[1];
}
- opts.dir = opts.dir || path.resolve(process.cwd(), 'coverage');
-
try {
- reporter = Report.create(fmt, opts);
+ reporter = Report.create(fmt, reportOpts);
} catch (ex) {
return callback(inputError.create('Invalid report format [' + fmt + ']'));
}
View
@@ -2,10 +2,11 @@ var path = require('path'),
fs = require('fs'),
CAMEL_PATTERN = /([a-z])([A-Z])/g,
YML_PATTERN = /\.ya?ml$/,
- yaml = require('js-yaml');
+ yaml = require('js-yaml'),
+ defaults = require('./report/common/defaults');
function defaultConfig() {
- return {
+ var ret = {
verbose: false,
instrumentation: {
root: '.',
@@ -22,12 +23,7 @@ function defaultConfig() {
reporting: {
print: 'summary',
reports: [ 'lcov' ],
- dir: './coverage',
- watermarks: {
- statements: [ 50, 80 ],
- functions: [ 50, 80 ],
- branches: [ 50, 80 ]
- }
+ dir: './coverage'
},
hooks: {
'hook-run-in-context': false,
@@ -39,6 +35,8 @@ function defaultConfig() {
functions: 0
}
};
+ ret.reporting.watermarks = defaults.watermarks();
+ return ret;
}
function dasherize(word) {
@@ -103,13 +101,13 @@ addMethods(InstrumentOptions,
InstrumentOptions.prototype.root = function () { return path.resolve(this.config.root); };
InstrumentOptions.prototype.excludes = function (excludeTests) {
- var defaults;
+ var defs;
if (this.defaultExcludes()) {
- defaults = [ '**/node_modules/**' ];
+ defs = [ '**/node_modules/**' ];
if (excludeTests) {
- defaults = defaults.concat(['**/test/**', '**/tests/**']);
+ defs = defs.concat(['**/test/**', '**/tests/**']);
}
- return defaults.concat(this.config.excludes);
+ return defs.concat(this.config.excludes);
}
return this.config.excludes;
};
@@ -120,6 +118,48 @@ function ReportingOptions(config) {
addMethods(ReportingOptions, 'print', 'reports', 'dir');
+function isInvalidMark(v, key) {
+ var prefix = 'Watermark for [' + key + '] :';
+
+ if (v.length !== 2) {
+ return prefix + 'must be an array of length 2';
+ }
+ v[0] = Number(v[0]);
+ v[1] = Number(v[1]);
+
+ if (isNaN(v[0]) || isNaN(v[1])) {
+ return prefix + 'must have valid numbers';
+ }
+ if (v[0] < 0 || v[1] < 0) {
+ return prefix + 'must be positive numbers';
+ }
+ if (v[1] > 100) {
+ return prefix + 'cannot exceed 100';
+ }
+ if (v[1] <= v[0]) {
+ return prefix + 'low must be less than high';
+ }
+ return null;
+}
+
+ReportingOptions.prototype.watermarks = function () {
+ var v = this.config.watermarks,
+ defs = defaults.watermarks(),
+ ret = {};
+
+ Object.keys(defs).forEach(function (k) {
+ var mark = v[k], //it will already be a non-zero length array because of the way the merge works
+ message = isInvalidMark(mark, k);
+ if (message) {
+ console.error(message);
+ ret[k] = defs[k];
+ } else {
+ ret[k] = mark;
+ }
+ });
+ return ret;
+};
+
function HookOptions(config) {
this.config = config;
}
@@ -160,6 +200,7 @@ function loadFile(file, overrides) {
}
if (file) {
+ console.error('Loading config: ' + file);
configObject = file.match(YML_PATTERN) ?
yaml.safeLoad(fs.readFileSync(file, 'utf8'), { filename: file }) :
require(path.resolve(file));
@@ -0,0 +1,29 @@
+module.exports = {
+ watermarks: function () {
+ return {
+ statements: [ 50, 80 ],
+ lines: [ 50, 80 ],
+ functions: [ 50, 80],
+ branches: [ 50, 80 ]
+ };
+ },
+
+ classFor: function (type, metrics, watermarks) {
+ var mark = watermarks[type],
+ value = metrics[type].pct;
+ return value >= mark[1] ? 'high' : value >= mark[0] ? 'medium' : 'low';
+ },
+
+ colorize: function (str, clazz) {
+ /* istanbul ignore if: untestable in batch mode */
+ if (process.stdout.isTTY) {
+ switch (clazz) {
+ case 'low' : str = '\033[91m' + str + '\033[0m'; break;
+ case 'medium': str = '\033[93m' + str + '\033[0m'; break;
+ case 'high': str = '\033[92m' + str + '\033[0m'; break;
+ }
+ }
+ return str;
+ }
+};
+
View
@@ -5,6 +5,7 @@
/*jshint maxlen: 300 */
var handlebars = require('handlebars'),
+ defaults = require('./common/defaults'),
path = require('path'),
SEP = path.sep || '/',
fs = require('fs'),
@@ -295,11 +296,11 @@ function annotateBranches(fileCoverage, structuredText) {
});
}
-function getReportClass(stats) {
+function getReportClass(stats, watermark) {
var coveragePct = stats.pct,
identity = 1;
if (coveragePct * identity === coveragePct) {
- return coveragePct >= 80 ? 'high' : coveragePct >= 50 ? 'medium' : 'low';
+ return coveragePct >= watermark[1] ? 'high' : coveragePct >= watermark[0] ? 'medium' : 'low';
} else {
return '';
}
@@ -328,6 +329,7 @@ function HtmlReport(opts) {
this.opts.linkMapper = this.opts.linkMapper || this.standardLinkMapper();
this.opts.writer = this.opts.writer || null;
this.opts.templateData = { datetime: Date() };
+ this.opts.watermarks = this.opts.watermarks || defaults.watermarks();
}
HtmlReport.TYPE = 'html';
@@ -361,7 +363,7 @@ Report.mix(HtmlReport, {
templateData.entity = node.name || 'All files';
templateData.metrics = node.metrics;
- templateData.reportClass = getReportClass(node.metrics.statements);
+ templateData.reportClass = getReportClass(node.metrics.statements, opts.watermarks.statements);
templateData.pathHtml = pathTemplate({ html: this.getPathHtml(node, linkMapper) });
templateData.prettify = {
js: linkMapper.asset(node, 'prettify.js'),
@@ -406,7 +408,8 @@ Report.mix(HtmlReport, {
writeIndexPage: function (writer, node) {
var linkMapper = this.opts.linkMapper,
templateData = this.opts.templateData,
- children = Array.prototype.slice.apply(node.children);
+ children = Array.prototype.slice.apply(node.children),
+ watermarks = this.opts.watermarks;
children.sort(function (a, b) {
return a.name < b.name ? -1 : 1;
@@ -418,10 +421,10 @@ Report.mix(HtmlReport, {
children.forEach(function (child) {
var metrics = child.metrics,
reportClasses = {
- statements: getReportClass(metrics.statements),
- lines: getReportClass(metrics.lines),
- functions: getReportClass(metrics.functions),
- branches: getReportClass(metrics.branches)
+ statements: getReportClass(metrics.statements, watermarks.statements),
+ lines: getReportClass(metrics.lines, watermarks.lines),
+ functions: getReportClass(metrics.functions, watermarks.functions),
+ branches: getReportClass(metrics.branches, watermarks.branches)
},
data = {
metrics: metrics,
View
@@ -33,8 +33,8 @@ function LcovReport(opts) {
htmlDir = path.resolve(baseDir, 'lcov-report');
mkdirp.sync(baseDir);
- this.lcov = new LcovOnlyReport({ dir: baseDir });
- this.html = new HtmlReport({ dir: htmlDir, sourceStore: opts.sourceStore});
+ this.lcov = new LcovOnlyReport({ dir: baseDir, watermarks: opts.watermarks });
+ this.html = new HtmlReport({ dir: htmlDir, watermarks: opts.watermarks, sourceStore: opts.sourceStore});
}
LcovReport.TYPE = 'lcov';
View
@@ -5,6 +5,7 @@
var path = require('path'),
mkdirp = require('mkdirp'),
+ defaults = require('./common/defaults'),
fs = require('fs'),
utils = require('../object-utils'),
Report = require('./index');
@@ -29,29 +30,32 @@ function TextSummaryReport(opts) {
opts = opts || {};
this.dir = opts.dir || process.cwd();
this.file = opts.file;
+ this.watermarks = opts.watermarks || defaults.watermarks();
}
TextSummaryReport.TYPE = 'text-summary';
-function lineForKey(summary, key) {
+function lineForKey(summary, key, watermarks) {
var metrics = summary[key],
skipped,
- result;
+ result,
+ clazz = defaults.classFor(key, summary, watermarks);
key = key.substring(0, 1).toUpperCase() + key.substring(1);
if (key.length < 12) { key += ' '.substring(0, 12 - key.length); }
result = [ key , ':', metrics.pct + '%', '(', metrics.covered + '/' + metrics.total, ')'].join(' ');
skipped = metrics.skipped;
if (skipped > 0) {
result += ', ' + skipped + ' ignored';
}
- return result;
+ return defaults.colorize(result, clazz);
}
Report.mix(TextSummaryReport, {
writeReport: function (collector /*, sync */) {
var summaries = [],
finalSummary,
lines = [],
+ watermarks = this.watermarks,
text;
collector.files().forEach(function (file) {
summaries.push(utils.summarizeFileCoverage(collector.fileCoverageFor(file)));
@@ -60,10 +64,10 @@ Report.mix(TextSummaryReport, {
lines.push('');
lines.push('=============================== Coverage summary ===============================');
lines.push.apply(lines, [
- lineForKey(finalSummary, 'statements'),
- lineForKey(finalSummary, 'branches'),
- lineForKey(finalSummary, 'functions'),
- lineForKey(finalSummary, 'lines')
+ lineForKey(finalSummary, 'statements', watermarks),
+ lineForKey(finalSummary, 'branches', watermarks),
+ lineForKey(finalSummary, 'functions', watermarks),
+ lineForKey(finalSummary, 'lines', watermarks)
]);
lines.push('================================================================================');
text = lines.join('\n');
Oops, something went wrong.

0 comments on commit aebc729

Please sign in to comment.