Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80680 views
1
/*
2
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
3
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
4
*/
5
6
var nopt = require('nopt'),
7
path = require('path'),
8
fs = require('fs'),
9
Collector = require('../collector'),
10
formatOption = require('../util/help-formatter').formatOption,
11
util = require('util'),
12
utils = require('../object-utils'),
13
filesFor = require('../util/file-matcher').filesFor,
14
Command = require('./index'),
15
configuration = require('../config');
16
17
function isAbsolute(file) {
18
if (path.isAbsolute) {
19
return path.isAbsolute(file);
20
}
21
22
return path.resolve(file) === path.normalize(file);
23
}
24
25
function CheckCoverageCommand() {
26
Command.call(this);
27
}
28
29
function removeFiles(covObj, root, files) {
30
var filesObj = {},
31
obj = {};
32
33
// Create lookup table.
34
files.forEach(function (file) {
35
filesObj[file] = true;
36
});
37
38
Object.keys(covObj).forEach(function (key) {
39
// Exclude keys will always be relative, but covObj keys can be absolute or relative
40
var excludeKey = isAbsolute(key) ? path.relative(root, key) : key;
41
// Also normalize for files that start with `./`, etc.
42
excludeKey = path.normalize(excludeKey);
43
if (filesObj[excludeKey] !== true) {
44
obj[key] = covObj[key];
45
}
46
});
47
48
return obj;
49
}
50
51
CheckCoverageCommand.TYPE = 'check-coverage';
52
util.inherits(CheckCoverageCommand, Command);
53
54
Command.mix(CheckCoverageCommand, {
55
synopsis: function () {
56
return "checks overall/per-file coverage against thresholds from coverage JSON files. Exits 1 if thresholds are not met, 0 otherwise";
57
},
58
59
usage: function () {
60
console.error('\nUsage: ' + this.toolName() + ' ' + this.type() + ' <options> [<include-pattern>]\n\nOptions are:\n\n' +
61
[
62
formatOption('--statements <threshold>', 'global statement coverage threshold'),
63
formatOption('--functions <threshold>', 'global function coverage threshold'),
64
formatOption('--branches <threshold>', 'global branch coverage threshold'),
65
formatOption('--lines <threshold>', 'global line coverage threshold')
66
].join('\n\n') + '\n');
67
68
console.error('\n\n');
69
70
console.error('Thresholds, when specified as a positive number are taken to be the minimum percentage required.');
71
console.error('When a threshold is specified as a negative number it represents the maximum number of uncovered entities allowed.\n');
72
console.error('For example, --statements 90 implies minimum statement coverage is 90%.');
73
console.error(' --statements -10 implies that no more than 10 uncovered statements are allowed\n');
74
console.error('Per-file thresholds can be specified via a configuration file.\n');
75
console.error('<include-pattern> is a fileset pattern that can be used to select one or more coverage files ' +
76
'for merge. This defaults to "**/coverage*.json"');
77
78
console.error('\n');
79
},
80
81
run: function (args, callback) {
82
83
var template = {
84
config: path,
85
root: path,
86
statements: Number,
87
lines: Number,
88
branches: Number,
89
functions: Number,
90
verbose: Boolean
91
},
92
opts = nopt(template, { v : '--verbose' }, args, 0),
93
// Translate to config opts.
94
config = configuration.loadFile(opts.config, {
95
verbose: opts.verbose,
96
check: {
97
global: {
98
statements: opts.statements,
99
lines: opts.lines,
100
branches: opts.branches,
101
functions: opts.functions
102
}
103
}
104
}),
105
includePattern = '**/coverage*.json',
106
root,
107
collector = new Collector(),
108
errors = [];
109
110
if (opts.argv.remain.length > 0) {
111
includePattern = opts.argv.remain[0];
112
}
113
114
root = opts.root || process.cwd();
115
filesFor({
116
root: root,
117
includes: [ includePattern ]
118
}, function (err, files) {
119
if (err) { throw err; }
120
if (files.length === 0) {
121
return callback('ERROR: No coverage files found.');
122
}
123
files.forEach(function (file) {
124
var coverageObject = JSON.parse(fs.readFileSync(file, 'utf8'));
125
collector.add(coverageObject);
126
});
127
var thresholds = {
128
global: {
129
statements: config.check.global.statements || 0,
130
branches: config.check.global.branches || 0,
131
lines: config.check.global.lines || 0,
132
functions: config.check.global.functions || 0,
133
excludes: config.check.global.excludes || []
134
},
135
each: {
136
statements: config.check.each.statements || 0,
137
branches: config.check.each.branches || 0,
138
lines: config.check.each.lines || 0,
139
functions: config.check.each.functions || 0,
140
excludes: config.check.each.excludes || []
141
}
142
},
143
rawCoverage = collector.getFinalCoverage(),
144
globalResults = utils.summarizeCoverage(removeFiles(rawCoverage, root, thresholds.global.excludes)),
145
eachResults = removeFiles(rawCoverage, root, thresholds.each.excludes);
146
147
// Summarize per-file results and mutate original results.
148
Object.keys(eachResults).forEach(function (key) {
149
eachResults[key] = utils.summarizeFileCoverage(eachResults[key]);
150
});
151
152
if (config.verbose) {
153
console.log('Compare actuals against thresholds');
154
console.log(JSON.stringify({ global: globalResults, each: eachResults, thresholds: thresholds }, undefined, 4));
155
}
156
157
function check(name, thresholds, actuals) {
158
[
159
"statements",
160
"branches",
161
"lines",
162
"functions"
163
].forEach(function (key) {
164
var actual = actuals[key].pct,
165
actualUncovered = actuals[key].total - actuals[key].covered,
166
threshold = thresholds[key];
167
168
if (threshold < 0) {
169
if (threshold * -1 < actualUncovered) {
170
errors.push('ERROR: Uncovered count for ' + key + ' (' + actualUncovered +
171
') exceeds ' + name + ' threshold (' + -1 * threshold + ')');
172
}
173
} else {
174
if (actual < threshold) {
175
errors.push('ERROR: Coverage for ' + key + ' (' + actual +
176
'%) does not meet ' + name + ' threshold (' + threshold + '%)');
177
}
178
}
179
});
180
}
181
182
check("global", thresholds.global, globalResults);
183
184
Object.keys(eachResults).forEach(function (key) {
185
check("per-file" + " (" + key + ") ", thresholds.each, eachResults[key]);
186
});
187
188
return callback(errors.length === 0 ? null : errors.join("\n"));
189
});
190
}
191
});
192
193
module.exports = CheckCoverageCommand;
194
195
196
197