UNPKG

12.1 kBJavaScriptView Raw
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 * Works on `node` as well as the browser.
23 *
24 * Usage on nodejs
25 * ---------------
26 *
27 * var objectUtils = require('istanbul').utils;
28 *
29 * Usage in a browser
30 * ------------------
31 *
32 * Load this file using a `script` tag or other means. This will set `window.coverageUtils`
33 * to this module's exports.
34 *
35 * @class ObjectUtils
36 * @module main
37 * @static
38 */
39(function (isNode) {
40 /**
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.
43 *
44 * Note that if line coverage information is already present in the object,
45 * it is not recomputed.
46 *
47 * @method addDerivedInfoForFile
48 * @static
49 * @param {Object} fileCoverage the coverage object for a single file
50 */
51 function addDerivedInfoForFile(fileCoverage) {
52 var statementMap = fileCoverage.statementMap,
53 statements = fileCoverage.s,
54 lineMap;
55
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;
65 }
66 });
67 }
68 }
69 /**
70 * adds line coverage information to all file coverage objects.
71 *
72 * @method addDerivedInfo
73 * @static
74 * @param {Object} coverage the coverage object
75 */
76 function addDerivedInfo(coverage) {
77 Object.keys(coverage).forEach(function (k) {
78 addDerivedInfoForFile(coverage[k]);
79 });
80 }
81 /**
82 * removes line coverage information from all file coverage objects
83 * @method removeDerivedInfo
84 * @static
85 * @param {Object} coverage the coverage object
86 */
87 function removeDerivedInfo(coverage) {
88 Object.keys(coverage).forEach(function (k) {
89 delete coverage[k].l;
90 });
91 }
92
93 function percent(covered, total) {
94 var tmp;
95 if (total > 0) {
96 tmp = 1000 * 100 * covered / total + 5;
97 return Math.floor(tmp / 10) / 100;
98 } else {
99 return 100.00;
100 }
101 }
102
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 };
107
108 Object.keys(stats).forEach(function (key) {
109 var covered = !!stats[key],
110 skipped = map && map[key].skip;
111 ret.total += 1;
112 if (covered || skipped) {
113 ret.covered += 1;
114 }
115 if (!covered && skipped) {
116 ret.skipped += 1;
117 }
118 });
119 ret.pct = percent(ret.covered, ret.total);
120 return ret;
121 }
122
123 function computeBranchTotals(fileCoverage) {
124 var stats = fileCoverage.b,
125 branchMap = fileCoverage.branchMap,
126 ret = { total: 0, covered: 0, skipped: 0 };
127
128 Object.keys(stats).forEach(function (key) {
129 var branches = stats[key],
130 map = branchMap[key],
131 covered,
132 skipped,
133 i;
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) {
138 ret.covered += 1;
139 }
140 if (!covered && skipped) {
141 ret.skipped += 1;
142 }
143 }
144 ret.total += branches.length;
145 });
146 ret.pct = percent(ret.covered, ret.total);
147 return ret;
148 }
149 /**
150 * returns a blank summary metrics object. A metrics object has the following
151 * format.
152 *
153 * {
154 * lines: lineMetrics,
155 * statements: statementMetrics,
156 * functions: functionMetrics,
157 * branches: branchMetrics
158 * }
159 *
160 * Each individual metric object looks as follows:
161 *
162 * {
163 * total: n,
164 * covered: m,
165 * pct: percent
166 * }
167 *
168 * @method blankSummary
169 * @static
170 * @return {Object} a blank metrics object
171 */
172 function blankSummary() {
173 return {
174 lines: {
175 total: 0,
176 covered: 0,
177 skipped: 0,
178 pct: 'Unknown'
179 },
180 statements: {
181 total: 0,
182 covered: 0,
183 skipped: 0,
184 pct: 'Unknown'
185 },
186 functions: {
187 total: 0,
188 covered: 0,
189 skipped: 0,
190 pct: 'Unknown'
191 },
192 branches: {
193 total: 0,
194 covered: 0,
195 skipped: 0,
196 pct: 'Unknown'
197 }
198 };
199 }
200 /**
201 * returns the summary metrics given the coverage object for a single file. See `blankSummary()`
202 * to understand the format of the returned object.
203 *
204 * @method summarizeFileCoverage
205 * @static
206 * @param {Object} fileCoverage the coverage object for a single file.
207 * @return {Object} the summary metrics for the file
208 */
209 function summarizeFileCoverage(fileCoverage) {
210 var ret = blankSummary();
211 addDerivedInfoForFile(fileCoverage);
212 ret.lines = computeSimpleTotals(fileCoverage, 'l');
213 ret.functions = computeSimpleTotals(fileCoverage, 'f', 'fnMap');
214 ret.statements = computeSimpleTotals(fileCoverage, 's', 'statementMap');
215 ret.branches = computeBranchTotals(fileCoverage);
216 return ret;
217 }
218 /**
219 * merges two instances of file coverage objects *for the same file*
220 * such that the execution counts are correct.
221 *
222 * @method mergeFileCoverage
223 * @static
224 * @param {Object} first the first file coverage object for a given file
225 * @param {Object} second the second file coverage object for the same file
226 * @return {Object} an object that is a result of merging the two. Note that
227 * the input objects are not changed in any way.
228 */
229 function mergeFileCoverage(first, second) {
230 var ret = JSON.parse(JSON.stringify(first)),
231 i;
232
233 delete ret.l; //remove derived info
234
235 Object.keys(second.s).forEach(function (k) {
236 ret.s[k] += second.s[k];
237 });
238 Object.keys(second.f).forEach(function (k) {
239 ret.f[k] += second.f[k];
240 });
241 Object.keys(second.b).forEach(function (k) {
242 var retArray = ret.b[k],
243 secondArray = second.b[k];
244 for (i = 0; i < retArray.length; i += 1) {
245 retArray[i] += secondArray[i];
246 }
247 });
248
249 return ret;
250 }
251 /**
252 * merges multiple summary metrics objects by summing up the `totals` and
253 * `covered` fields and recomputing the percentages. This function is generic
254 * and can accept any number of arguments.
255 *
256 * @method mergeSummaryObjects
257 * @static
258 * @param {Object} summary... multiple summary metrics objects
259 * @return {Object} the merged summary metrics
260 */
261 function mergeSummaryObjects() {
262 var ret = blankSummary(),
263 args = Array.prototype.slice.call(arguments),
264 keys = ['lines', 'statements', 'branches', 'functions'],
265 increment = function (obj) {
266 if (obj) {
267 keys.forEach(function (key) {
268 ret[key].total += obj[key].total;
269 ret[key].covered += obj[key].covered;
270 ret[key].skipped += obj[key].skipped;
271 });
272 }
273 };
274 args.forEach(function (arg) {
275 increment(arg);
276 });
277 keys.forEach(function (key) {
278 ret[key].pct = percent(ret[key].covered, ret[key].total);
279 });
280
281 return ret;
282 }
283 /**
284 * returns the coverage summary for a single coverage object. This is
285 * wrapper over `summarizeFileCoverage` and `mergeSummaryObjects` for
286 * the common case of a single coverage object
287 * @method summarizeCoverage
288 * @static
289 * @param {Object} coverage the coverage object
290 * @return {Object} summary coverage metrics across all files in the coverage object
291 */
292 function summarizeCoverage(coverage) {
293 var fileSummary = [];
294 Object.keys(coverage).forEach(function (key) {
295 fileSummary.push(summarizeFileCoverage(coverage[key]));
296 });
297 return mergeSummaryObjects.apply(null, fileSummary);
298 }
299
300 /**
301 * makes the coverage object generated by this library yuitest_coverage compatible.
302 * Note that this transformation is lossy since the returned object will not have
303 * statement and branch coverage.
304 *
305 * @method toYUICoverage
306 * @static
307 * @param {Object} coverage The `istanbul` coverage object
308 * @return {Object} a coverage object in `yuitest_coverage` format.
309 */
310 function toYUICoverage(coverage) {
311 var ret = {};
312
313 addDerivedInfo(coverage);
314
315 Object.keys(coverage).forEach(function (k) {
316 var fileCoverage = coverage[k],
317 lines = fileCoverage.l,
318 functions = fileCoverage.f,
319 fnMap = fileCoverage.fnMap,
320 o;
321
322 o = ret[k] = {
323 lines: {},
324 calledLines: 0,
325 coveredLines: 0,
326 functions: {},
327 calledFunctions: 0,
328 coveredFunctions: 0
329 };
330 Object.keys(lines).forEach(function (k) {
331 o.lines[k] = lines[k];
332 o.coveredLines += 1;
333 if (lines[k] > 0) {
334 o.calledLines += 1;
335 }
336 });
337 Object.keys(functions).forEach(function (k) {
338 var name = fnMap[k].name + ':' + fnMap[k].line;
339 o.functions[name] = functions[k];
340 o.coveredFunctions += 1;
341 if (functions[k] > 0) {
342 o.calledFunctions += 1;
343 }
344 });
345 });
346 return ret;
347 }
348
349 var exportables = {
350 addDerivedInfo: addDerivedInfo,
351 addDerivedInfoForFile: addDerivedInfoForFile,
352 removeDerivedInfo: removeDerivedInfo,
353 blankSummary: blankSummary,
354 summarizeFileCoverage: summarizeFileCoverage,
355 summarizeCoverage: summarizeCoverage,
356 mergeFileCoverage: mergeFileCoverage,
357 mergeSummaryObjects: mergeSummaryObjects,
358 toYUICoverage: toYUICoverage
359 };
360
361 /* istanbul ignore else: windows */
362 if (isNode) {
363 module.exports = exportables;
364 } else {
365 window.coverageUtils = exportables;
366 }
367}(typeof module !== 'undefined' && typeof module.exports !== 'undefined' && typeof exports !== 'undefined'));
368