Code coverage report for istanbul/lib/object-utils.js

Statements: 100% (89 / 89)      Branches: 100% (16 / 16)      Functions: 100% (27 / 27)      Lines: 100% (89 / 89)     

All files » istanbul/lib/ » object-utils.js
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 /**
7 * utility methods to process coverage objects. A coverage object has the following
8 * format.
9 *
10 * {
11 * "/path/to/file1.js": { file1 coverage },
12 * "/path/to/file2.js": { file2 coverage }
13 * }
14 *
15 * The internals of the file coverage object are intentionally not documented since
16 * it is not a public interface.
17 *
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.
21 *
22 * Usage
23 * -----
24 *
25 * var objectUtils = require('istanbul').utils;
26 *
27 * @class ObjectUtils
28 */
29
30 /**
31 * adds line coverage information to a file coverage object, reverse-engineering
32 * it from statement coverage. The object passed in is updated in place.
33 *
34 * Note that if line coverage information is already present in the object,
35 * it is not recomputed.
36 *
37 * @method addDerivedInfoForFile
38 * @static
39 * @param {Object} fileCoverage the coverage object for a single file
40 */
41 1 function addDerivedInfoForFile(fileCoverage) {
42 189 var statementMap = fileCoverage.statementMap,
43 statements = fileCoverage.s,
44 lineMap;
45
46 189 if (!fileCoverage.l) {
47 171 fileCoverage.l = lineMap = {};
48 171 Object.keys(statements).forEach(function (st) {
49 705 var line = statementMap[st].start.line,
50 count = statements[st],
51 prevVal = lineMap[line];
52 705 if (typeof prevVal === 'undefined' || prevVal < count) {
53 577 lineMap[line] = count;
54 }
55 });
56 }
57 }
58 /**
59 * adds line coverage information to all file coverage objects.
60 *
61 * @method addDerivedInfo
62 * @static
63 * @param {Object} coverage the coverage object
64 */
65 1 function addDerivedInfo(coverage) {
66 109 Object.keys(coverage).forEach(function (k) {
67 111 addDerivedInfoForFile(coverage[k]);
68 });
69 }
70 /**
71 * removes line coverage information from all file coverage objects
72 * @method removeDerivedInfo
73 * @static
74 * @param {Object} coverage the coverage object
75 */
76 1 function removeDerivedInfo(coverage) {
77 1 Object.keys(coverage).forEach(function (k) {
78 1 delete coverage[k].l;
79 });
80 }
81
82 1 function percent(covered, total) {
83 252 var tmp;
84 252 if (total > 0) {
85 239 tmp = 1000 * 100 * covered / total + 5;
86 239 return Math.floor(tmp / 10) / 100;
87 } else {
88 13 return 100.00;
89 }
90 }
91
92 1 function computeSimpleTotals(fileCoverage, property) {
93 66 var stats = fileCoverage[property],
94 ret = { total: 0, covered: 0 };
95
96 66 Object.keys(stats).forEach(function (key) {
97 198 ret.total += 1;
98 198 if (stats[key]) {
99 140 ret.covered += 1;
100 }
101 });
102 66 ret.pct = percent(ret.covered, ret.total);
103 66 return ret;
104 }
105
106 1 function computeBranchTotals(fileCoverage) {
107 22 var stats = fileCoverage.b,
108 ret = { total: 0, covered: 0 };
109
110 22 Object.keys(stats).forEach(function (key) {
111 22 var branches = stats[key],
112 49 covered = branches.filter(function (num) { return num > 0; });
113 22 ret.total += branches.length;
114 22 ret.covered += covered.length;
115 });
116 22 ret.pct = percent(ret.covered, ret.total);
117 22 return ret;
118 }
119 /**
120 * returns a blank summary metrics object. A metrics object has the following
121 * format.
122 *
123 * {
124 * lines: lineMetrics,
125 * statements: statementMetrics,
126 * functions: functionMetrics,
127 * branches: branchMetrics
128 * }
129 *
130 * Each individual metric object looks as follows:
131 *
132 * {
133 * total: n,
134 * covered: m,
135 * pct: percent
136 * }
137 *
138 * @method blankSummary
139 * @static
140 * @return {Object} a blank metrics object
141 */
142 1 function blankSummary() {
143 64 return {
144 lines: {
145 total: 0,
146 covered: 0,
147 pct: 'Unknown'
148 },
149 statements: {
150 total: 0,
151 covered: 0,
152 pct: 'Unknown'
153 },
154 functions: {
155 total: 0,
156 covered: 0,
157 pct: 'Unknown'
158 },
159 branches: {
160 total: 0,
161 covered: 0,
162 pct: 'Unknown'
163 }
164 };
165 }
166 /**
167 * returns the summary metrics given the coverage object for a single file. See `blankSummary()`
168 * to understand the format of the returned object.
169 *
170 * @method summarizeFileCoverage
171 * @static
172 * @param {Object} fileCoverage the coverage object for a single file.
173 * @return {Object} the summary metrics for the file
174 */
175 1 function summarizeFileCoverage(fileCoverage) {
176 22 var ret = blankSummary();
177 22 addDerivedInfoForFile(fileCoverage);
178 22 ret.lines = computeSimpleTotals(fileCoverage, 'l');
179 22 ret.functions = computeSimpleTotals(fileCoverage, 'f');
180 22 ret.statements = computeSimpleTotals(fileCoverage, 's');
181 22 ret.branches = computeBranchTotals(fileCoverage);
182 22 return ret;
183 }
184 /**
185 * merges two instances of file coverage objects *for the same file*
186 * such that the execution counts are correct.
187 *
188 * @method mergeFileCoverage
189 * @static
190 * @param {Object} first the first file coverage object for a given file
191 * @param {Object} second the second file coverage object for the same file
192 * @return {Object} an object that is a result of merging the two. Note that
193 * the input objects are not changed in any way.
194 */
195 1 function mergeFileCoverage(first, second) {
196 3 var ret = JSON.parse(JSON.stringify(first)),
197 i;
198
199 3 delete ret.l; //remove derived info
200
201 3 Object.keys(second.s).forEach(function (k) {
202 11 ret.s[k] += second.s[k];
203 });
204 3 Object.keys(second.f).forEach(function (k) {
205 8 ret.f[k] += second.f[k];
206 });
207 3 Object.keys(second.b).forEach(function (k) {
208 8 var retArray = ret.b[k],
209 secondArray = second.b[k];
210 8 for (i = 0; i < retArray.length; i += 1) {
211 17 retArray[i] += secondArray[i];
212 }
213 });
214
215 3 return ret;
216 }
217 /**
218 * merges multiple summary metrics objects by summing up the `totals` and
219 * `covered` fields and recomputing the percentages. This function is generic
220 * and can accept any number of arguments.
221 *
222 * @method mergeSummaryObjects
223 * @static
224 * @param {Object} summary... multiple summary metrics objects
225 * @return {Object} the merged summary metrics
226 */
227 1 function mergeSummaryObjects() {
228 41 var ret = blankSummary(),
229 args = Array.prototype.slice.call(arguments),
230 keys = ['lines', 'statements', 'branches', 'functions'],
231 increment = function (obj) {
232 69 if (obj) {
233 67 keys.forEach(function (key) {
234 268 ret[key].total += obj[key].total;
235 268 ret[key].covered += obj[key].covered;
236 });
237 }
238 };
239 41 args.forEach(function (arg) {
240 69 increment(arg);
241 });
242 41 keys.forEach(function (key) {
243 164 ret[key].pct = percent(ret[key].covered, ret[key].total);
244 });
245
246 41 return ret;
247 }
248
249 /**
250 * makes the coverage object generated by this library yuitest_coverage compatible.
251 * Note that this transformation is lossy since the returned object will not have
252 * statement and branch coverage.
253 *
254 * @method toYUICoverage
255 * @static
256 * @param {Object} coverage The `istanbul` coverage object
257 * @return {Object} a coverage object in `yuitest_coverage` format.
258 */
259 1 function toYUICoverage(coverage) {
260 1 var ret = {};
261
262 1 addDerivedInfo(coverage);
263
264 1 Object.keys(coverage).forEach(function (k) {
265 1 var fileCoverage = coverage[k],
266 lines = fileCoverage.l,
267 functions = fileCoverage.f,
268 fnMap = fileCoverage.fnMap,
269 o;
270
271 1 o = ret[k] = {
272 lines: {},
273 calledLines: 0,
274 coveredLines: 0,
275 functions: {},
276 calledFunctions: 0,
277 coveredFunctions: 0
278 };
279 1 Object.keys(lines).forEach(function (k) {
280 4 o.lines[k] = lines[k];
281 4 o.coveredLines += 1;
282 4 if (lines[k] > 0) {
283 3 o.calledLines += 1;
284 }
285 });
286 1 Object.keys(functions).forEach(function (k) {
287 2 var name = fnMap[k].name + ':' + fnMap[k].line;
288 2 o.functions[name] = functions[k];
289 2 o.coveredFunctions += 1;
290 2 if (functions[k] > 0) {
291 1 o.calledFunctions += 1;
292 }
293 });
294 });
295 1 return ret;
296 }
297
298 1 module.exports = {
299 addDerivedInfo: addDerivedInfo,
300 addDerivedInfoForFile: addDerivedInfoForFile,
301 removeDerivedInfo: removeDerivedInfo,
302 blankSummary: blankSummary,
303 summarizeFileCoverage: summarizeFileCoverage,
304 mergeFileCoverage: mergeFileCoverage,
305 mergeSummaryObjects: mergeSummaryObjects,
306 toYUICoverage: toYUICoverage
307 };
308
309