Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80668 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
/**
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
369
370