2 Copyright (c) 2012, Yahoo! Inc. All rights reserved.
3 Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
7 * utility methods to process coverage objects. A coverage object has the following
11 * "/path/to/file1.js": { file1 coverage },
12 * "/path/to/file2.js": { file2 coverage }
15 * The internals of the file coverage object are intentionally not documented since
16 * it is not a public interface.
18 * *Note:* When a method of this module has the word `File` in it, it will accept
19 * one of the sub-objects of the main coverage object as an argument. Other
20 * methods accept the higher level coverage object with multiple keys.
22 * Works on `node` as well as the browser.
27 * var objectUtils = require('istanbul').utils;
32 * Load this file using a `script` tag or other means. This will set `window.coverageUtils`
33 * to this module's exports.
41 * adds line coverage information to a file coverage object, reverse-engineering
42 * it from statement coverage. The object passed in is updated in place.
44 * Note that if line coverage information is already present in the object,
45 * it is not recomputed.
47 * @method addDerivedInfoForFile
49 * @param {Object} fileCoverage the coverage object for a single file
51 function addDerivedInfoForFile(fileCoverage) {
52 var statementMap = fileCoverage.statementMap,
53 statements = fileCoverage.s,
56 if (!fileCoverage.l) {
57 fileCoverage.l = lineMap = {};
58 Object.keys(statements).forEach(function (st) {
59 var line = statementMap[st].start.line,
60 count = statements[st],
61 prevVal = lineMap[line];
62 if (count === 0 && statementMap[st].skip) { count = 1; }
63 if (typeof prevVal === 'undefined' || prevVal < count) {
64 lineMap[line] = count;
70 * adds line coverage information to all file coverage objects.
72 * @method addDerivedInfo
74 * @param {Object} coverage the coverage object
76 function addDerivedInfo(coverage) {
77 Object.keys(coverage).forEach(function (k) {
78 addDerivedInfoForFile(coverage[k]);
82 * removes line coverage information from all file coverage objects
83 * @method removeDerivedInfo
85 * @param {Object} coverage the coverage object
87 function removeDerivedInfo(coverage) {
88 Object.keys(coverage).forEach(function (k) {
93 function percent(covered, total) {
96 tmp = 1000 * 100 * covered / total + 5;
97 return Math.floor(tmp / 10) / 100;
103 function computeSimpleTotals(fileCoverage, property, mapProperty) {
104 var stats = fileCoverage[property],
105 map = mapProperty ? fileCoverage[mapProperty] : null,
106 ret = { total: 0, covered: 0, skipped: 0 };
108 Object.keys(stats).forEach(function (key) {
109 var covered = !!stats[key],
110 skipped = map && map[key].skip;
112 if (covered || skipped) {
115 if (!covered && skipped) {
119 ret.pct = percent(ret.covered, ret.total);
123 function computeBranchTotals(fileCoverage) {
124 var stats = fileCoverage.b,
125 branchMap = fileCoverage.branchMap,
126 ret = { total: 0, covered: 0, skipped: 0 };
128 Object.keys(stats).forEach(function (key) {
129 var branches = stats[key],
130 map = branchMap[key],
134 for (i = 0; i < branches.length; i += 1) {
135 covered = branches[i] > 0;
136 skipped = map.locations && map.locations[i] && map.locations[i].skip;
137 if (covered || skipped) {
140 if (!covered && skipped) {
144 ret.total += branches.length;
146 ret.pct = percent(ret.covered, ret.total);
150 * returns a blank summary metrics object. A metrics object has the following
154 * lines: lineMetrics,
155 * statements: statementMetrics,
156 * functions: functionMetrics,
157 * branches: branchMetrics
158 * linesCovered: lineCoveredCount
161 * Each individual metric object looks as follows:
169 * @method blankSummary
171 * @return {Object} a blank metrics object
173 function blankSummary() {
203 * returns the summary metrics given the coverage object for a single file. See `blankSummary()`
204 * to understand the format of the returned object.
206 * @method summarizeFileCoverage
208 * @param {Object} fileCoverage the coverage object for a single file.
209 * @return {Object} the summary metrics for the file
211 function summarizeFileCoverage(fileCoverage) {
212 var ret = blankSummary();
213 addDerivedInfoForFile(fileCoverage);
214 ret.lines = computeSimpleTotals(fileCoverage, 'l');
215 ret.functions = computeSimpleTotals(fileCoverage, 'f', 'fnMap');
216 ret.statements = computeSimpleTotals(fileCoverage, 's', 'statementMap');
217 ret.branches = computeBranchTotals(fileCoverage);
218 ret.linesCovered = fileCoverage.l;
222 * merges two instances of file coverage objects *for the same file*
223 * such that the execution counts are correct.
225 * @method mergeFileCoverage
227 * @param {Object} first the first file coverage object for a given file
228 * @param {Object} second the second file coverage object for the same file
229 * @return {Object} an object that is a result of merging the two. Note that
230 * the input objects are not changed in any way.
232 function mergeFileCoverage(first, second) {
233 var ret = JSON.parse(JSON.stringify(first)),
236 delete ret.l; //remove derived info
238 Object.keys(second.s).forEach(function (k) {
239 ret.s[k] += second.s[k];
241 Object.keys(second.f).forEach(function (k) {
242 ret.f[k] += second.f[k];
244 Object.keys(second.b).forEach(function (k) {
245 var retArray = ret.b[k],
246 secondArray = second.b[k];
247 for (i = 0; i < retArray.length; i += 1) {
248 retArray[i] += secondArray[i];
255 * merges multiple summary metrics objects by summing up the `totals` and
256 * `covered` fields and recomputing the percentages. This function is generic
257 * and can accept any number of arguments.
259 * @method mergeSummaryObjects
261 * @param {Object} summary... multiple summary metrics objects
262 * @return {Object} the merged summary metrics
264 function mergeSummaryObjects() {
265 var ret = blankSummary(),
266 args = Array.prototype.slice.call(arguments),
267 keys = ['lines', 'statements', 'branches', 'functions'],
268 increment = function (obj) {
270 keys.forEach(function (key) {
271 ret[key].total += obj[key].total;
272 ret[key].covered += obj[key].covered;
273 ret[key].skipped += obj[key].skipped;
276 // keep track of all lines we have coverage for.
277 Object.keys(obj.linesCovered).forEach(function (key) {
278 if (!ret.linesCovered[key]) {
279 ret.linesCovered[key] = obj.linesCovered[key];
281 ret.linesCovered[key] += obj.linesCovered[key];
286 args.forEach(function (arg) {
289 keys.forEach(function (key) {
290 ret[key].pct = percent(ret[key].covered, ret[key].total);
296 * returns the coverage summary for a single coverage object. This is
297 * wrapper over `summarizeFileCoverage` and `mergeSummaryObjects` for
298 * the common case of a single coverage object
299 * @method summarizeCoverage
301 * @param {Object} coverage the coverage object
302 * @return {Object} summary coverage metrics across all files in the coverage object
304 function summarizeCoverage(coverage) {
305 var fileSummary = [];
306 Object.keys(coverage).forEach(function (key) {
307 fileSummary.push(summarizeFileCoverage(coverage[key]));
309 return mergeSummaryObjects.apply(null, fileSummary);
313 * makes the coverage object generated by this library yuitest_coverage compatible.
314 * Note that this transformation is lossy since the returned object will not have
315 * statement and branch coverage.
317 * @method toYUICoverage
319 * @param {Object} coverage The `istanbul` coverage object
320 * @return {Object} a coverage object in `yuitest_coverage` format.
322 function toYUICoverage(coverage) {
325 addDerivedInfo(coverage);
327 Object.keys(coverage).forEach(function (k) {
328 var fileCoverage = coverage[k],
329 lines = fileCoverage.l,
330 functions = fileCoverage.f,
331 fnMap = fileCoverage.fnMap,
342 Object.keys(lines).forEach(function (k) {
343 o.lines[k] = lines[k];
349 Object.keys(functions).forEach(function (k) {
350 var name = fnMap[k].name + ':' + fnMap[k].line;
351 o.functions[name] = functions[k];
352 o.coveredFunctions += 1;
353 if (functions[k] > 0) {
354 o.calledFunctions += 1;
362 * Creates new file coverage object with incremented hits count
363 * on skipped statements, branches and functions
365 * @method incrementIgnoredTotals
367 * @param {Object} cov File coverage object
368 * @return {Object} New file coverage object
370 function incrementIgnoredTotals(cov) {
371 //TODO: This may be slow in the browser and may break in older browsers
372 // Look into using a library that works in Node and the browser
373 var fileCoverage = JSON.parse(JSON.stringify(cov));
376 {mapKey: 'statementMap', hitsKey: 's'},
377 {mapKey: 'branchMap', hitsKey: 'b'},
378 {mapKey: 'fnMap', hitsKey: 'f'}
379 ].forEach(function (keys) {
380 Object.keys(fileCoverage[keys.mapKey])
381 .forEach(function (key) {
382 var map = fileCoverage[keys.mapKey][key];
383 var hits = fileCoverage[keys.hitsKey];
385 if (keys.mapKey === 'branchMap') {
386 var locations = map.locations;
388 locations.forEach(function (location, index) {
389 if (hits[key][index] === 0 && location.skip) {
390 hits[key][index] = 1;
397 if (hits[key] === 0 && map.skip) {
407 addDerivedInfo: addDerivedInfo,
408 addDerivedInfoForFile: addDerivedInfoForFile,
409 removeDerivedInfo: removeDerivedInfo,
410 blankSummary: blankSummary,
411 summarizeFileCoverage: summarizeFileCoverage,
412 summarizeCoverage: summarizeCoverage,
413 mergeFileCoverage: mergeFileCoverage,
414 mergeSummaryObjects: mergeSummaryObjects,
415 toYUICoverage: toYUICoverage,
416 incrementIgnoredTotals: incrementIgnoredTotals
419 /* istanbul ignore else: windows */
421 module.exports = exportables;
423 window.coverageUtils = exportables;
425 }(typeof module !== 'undefined' && typeof module.exports !== 'undefined' && typeof exports !== 'undefined'));