monocart-coverage-reports 2.11.5 → 2.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cli.js +1 -0
- package/lib/converter/converter.js +1 -1
- package/lib/generate.js +3 -5
- package/lib/index.d.ts +2 -0
- package/lib/istanbul/istanbul.js +4 -13
- package/lib/packages/monocart-coverage-vendor.js +5 -5
- package/lib/utils/gc.js +9 -9
- package/lib/utils/merge/merge.js +439 -439
- package/lib/utils/merge/range-tree.js +123 -123
- package/lib/utils/source-path.js +41 -20
- package/package.json +117 -119
package/lib/utils/merge/merge.js
CHANGED
|
@@ -1,439 +1,439 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The `mergeV8Coverage` was extracted from following repos for fixing several issues
|
|
3
|
-
* https://github.com/bcoe/v8-coverage
|
|
4
|
-
* https://github.com/demurgos/v8-coverage
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const RangeTree = require('./range-tree.js');
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Compares two range coverages.
|
|
12
|
-
*
|
|
13
|
-
* The ranges are first ordered by ascending `startOffset` and then by
|
|
14
|
-
* descending `endOffset`.
|
|
15
|
-
* This corresponds to a pre-order tree traversal.
|
|
16
|
-
*/
|
|
17
|
-
function compareRangeCovs(a, b) {
|
|
18
|
-
if (a.startOffset !== b.startOffset) {
|
|
19
|
-
return a.startOffset - b.startOffset;
|
|
20
|
-
}
|
|
21
|
-
return b.endOffset - a.endOffset;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Compares two function coverages.
|
|
26
|
-
*
|
|
27
|
-
* The result corresponds to the comparison of the root ranges.
|
|
28
|
-
*/
|
|
29
|
-
function compareFunctionCovs(a, b) {
|
|
30
|
-
return compareRangeCovs(a.ranges[0], b.ranges[0]);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* @precodition `ranges` are well-formed and pre-order sorted
|
|
35
|
-
*/
|
|
36
|
-
function fromSortedRanges(ranges) {
|
|
37
|
-
let root;
|
|
38
|
-
// Stack of parent trees and parent counts.
|
|
39
|
-
const stack = [];
|
|
40
|
-
for (const range of ranges) {
|
|
41
|
-
const node = new RangeTree(range.startOffset, range.endOffset, range.count, []);
|
|
42
|
-
if (!root) {
|
|
43
|
-
root = node;
|
|
44
|
-
stack.push([node, range.count]);
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
let parent;
|
|
48
|
-
let parentCount;
|
|
49
|
-
while (true) {
|
|
50
|
-
[parent, parentCount] = stack[stack.length - 1];
|
|
51
|
-
// assert: `top !== undefined` (the ranges are sorted)
|
|
52
|
-
if (range.startOffset < parent.end) {
|
|
53
|
-
break;
|
|
54
|
-
} else {
|
|
55
|
-
stack.pop();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// prevent crash when v8 incorrectly merges static_initializer's
|
|
59
|
-
if (stack.length === 0) {
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
node.delta -= parentCount;
|
|
64
|
-
parent.children.push(node);
|
|
65
|
-
stack.push([node, range.count]);
|
|
66
|
-
}
|
|
67
|
-
return root;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Normalizes a function coverage.
|
|
72
|
-
*
|
|
73
|
-
* Sorts the ranges (pre-order sort).
|
|
74
|
-
* TODO: Tree-based normalization of the ranges.
|
|
75
|
-
*
|
|
76
|
-
* @param funcCov Function coverage to normalize.
|
|
77
|
-
*/
|
|
78
|
-
function normalizeFunctionCov(funcCov) {
|
|
79
|
-
funcCov.ranges.sort(compareRangeCovs);
|
|
80
|
-
const tree = fromSortedRanges(funcCov.ranges);
|
|
81
|
-
tree.normalize();
|
|
82
|
-
funcCov.ranges = tree.toRanges();
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Normalizes a script coverage.
|
|
87
|
-
*
|
|
88
|
-
* Sorts the function by root range (pre-order sort).
|
|
89
|
-
* This does not normalize the function coverages.
|
|
90
|
-
*
|
|
91
|
-
* @param scriptCov Script coverage to normalize.
|
|
92
|
-
*/
|
|
93
|
-
function normalizeScriptCov(scriptCov) {
|
|
94
|
-
scriptCov.functions.sort(compareFunctionCovs);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Normalizes a script coverage deeply.
|
|
100
|
-
*
|
|
101
|
-
* Normalizes the function coverages deeply, then normalizes the script coverage
|
|
102
|
-
* itself.
|
|
103
|
-
*
|
|
104
|
-
* @param scriptCov Script coverage to normalize.
|
|
105
|
-
*/
|
|
106
|
-
function deepNormalizeScriptCov(scriptCov) {
|
|
107
|
-
for (const funcCov of scriptCov.functions) {
|
|
108
|
-
normalizeFunctionCov(funcCov);
|
|
109
|
-
}
|
|
110
|
-
normalizeScriptCov(scriptCov);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Returns a string representation of the root range of the function.
|
|
116
|
-
*
|
|
117
|
-
* This string can be used to match function with same root range.
|
|
118
|
-
* The string is derived from the start and end offsets of the root range of
|
|
119
|
-
* the function.
|
|
120
|
-
* This assumes that `ranges` is non-empty (true for valid function coverages).
|
|
121
|
-
*
|
|
122
|
-
* @param funcCov Function coverage with the range to stringify
|
|
123
|
-
* @internal
|
|
124
|
-
*/
|
|
125
|
-
function stringifyFunctionRootRange(funcCov) {
|
|
126
|
-
const rootRange = funcCov.ranges[0];
|
|
127
|
-
return `${rootRange.startOffset.toString(10)};${rootRange.endOffset.toString(10)}`;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
function insertChild(parentToNested, parentIndex, tree) {
|
|
132
|
-
let nested = parentToNested.get(parentIndex);
|
|
133
|
-
if (!nested) {
|
|
134
|
-
nested = [];
|
|
135
|
-
parentToNested.set(parentIndex, nested);
|
|
136
|
-
}
|
|
137
|
-
nested.push(tree);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function nextChild(openRange, parentToNested) {
|
|
141
|
-
const matchingTrees = [];
|
|
142
|
-
|
|
143
|
-
for (const nested of parentToNested.values()) {
|
|
144
|
-
if (nested.length === 1 && nested[0].start === openRange.start && nested[0].end === openRange.end) {
|
|
145
|
-
matchingTrees.push(nested[0]);
|
|
146
|
-
} else {
|
|
147
|
-
matchingTrees.push(new RangeTree(
|
|
148
|
-
openRange.start,
|
|
149
|
-
openRange.end,
|
|
150
|
-
0,
|
|
151
|
-
nested
|
|
152
|
-
));
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
parentToNested.clear();
|
|
156
|
-
return mergeRangeTrees(matchingTrees);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
class StartEventQueue {
|
|
160
|
-
|
|
161
|
-
constructor(queue) {
|
|
162
|
-
this.queue = queue;
|
|
163
|
-
this.nextIndex = 0;
|
|
164
|
-
this.pendingOffset = 0;
|
|
165
|
-
this.pendingTrees = null;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
setPendingOffset(offset) {
|
|
169
|
-
this.pendingOffset = offset;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
pushPendingTree(tree) {
|
|
173
|
-
if (this.pendingTrees === null) {
|
|
174
|
-
this.pendingTrees = [];
|
|
175
|
-
}
|
|
176
|
-
this.pendingTrees.push(tree);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
next() {
|
|
180
|
-
const pendingTrees = this.pendingTrees;
|
|
181
|
-
const nextEvent = this.queue[this.nextIndex];
|
|
182
|
-
if (pendingTrees === null) {
|
|
183
|
-
this.nextIndex++;
|
|
184
|
-
return nextEvent;
|
|
185
|
-
}
|
|
186
|
-
if (!nextEvent) {
|
|
187
|
-
this.pendingTrees = null;
|
|
188
|
-
return {
|
|
189
|
-
offset: this.pendingOffset,
|
|
190
|
-
trees: pendingTrees
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
if (this.pendingOffset < nextEvent.offset) {
|
|
194
|
-
this.pendingTrees = null;
|
|
195
|
-
return {
|
|
196
|
-
offset: this.pendingOffset,
|
|
197
|
-
trees: pendingTrees
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
if (this.pendingOffset === nextEvent.offset) {
|
|
201
|
-
this.pendingTrees = null;
|
|
202
|
-
for (const tree of pendingTrees) {
|
|
203
|
-
nextEvent.trees.push(tree);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
this.nextIndex++;
|
|
207
|
-
return nextEvent;
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function fromParentTrees(parentTrees) {
|
|
214
|
-
const startToTrees = new Map();
|
|
215
|
-
for (const [parentIndex, parentTree] of parentTrees.entries()) {
|
|
216
|
-
for (const child of parentTree.children) {
|
|
217
|
-
let trees = startToTrees.get(child.start);
|
|
218
|
-
if (!trees) {
|
|
219
|
-
trees = [];
|
|
220
|
-
startToTrees.set(child.start, trees);
|
|
221
|
-
}
|
|
222
|
-
trees.push({
|
|
223
|
-
parentIndex,
|
|
224
|
-
tree: child
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
const queue = [];
|
|
229
|
-
for (const [startOffset, trees] of startToTrees) {
|
|
230
|
-
queue.push({
|
|
231
|
-
offset: startOffset,
|
|
232
|
-
trees
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
queue.sort((a, b) => {
|
|
236
|
-
return a.offset - b.offset;
|
|
237
|
-
});
|
|
238
|
-
return new StartEventQueue(queue);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// eslint-disable-next-line complexity
|
|
242
|
-
function mergeRangeTreeChildren(parentTrees) {
|
|
243
|
-
const result = [];
|
|
244
|
-
const startEventQueue = fromParentTrees(parentTrees);
|
|
245
|
-
const parentToNested = new Map();
|
|
246
|
-
let openRange = null;
|
|
247
|
-
|
|
248
|
-
while (true) {
|
|
249
|
-
const event = startEventQueue.next();
|
|
250
|
-
if (!event) {
|
|
251
|
-
break;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (openRange !== null && openRange.end <= event.offset) {
|
|
255
|
-
result.push(nextChild(openRange, parentToNested));
|
|
256
|
-
openRange = null;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
if (openRange === null) {
|
|
260
|
-
let openRangeEnd = event.offset + 1;
|
|
261
|
-
for (const { parentIndex, tree } of event.trees) {
|
|
262
|
-
openRangeEnd = Math.max(openRangeEnd, tree.end);
|
|
263
|
-
insertChild(parentToNested, parentIndex, tree);
|
|
264
|
-
}
|
|
265
|
-
startEventQueue.setPendingOffset(openRangeEnd);
|
|
266
|
-
openRange = {
|
|
267
|
-
start: event.offset, end: openRangeEnd
|
|
268
|
-
};
|
|
269
|
-
} else {
|
|
270
|
-
for (const { parentIndex, tree } of event.trees) {
|
|
271
|
-
if (tree.end > openRange.end) {
|
|
272
|
-
const right = tree.split(openRange.end);
|
|
273
|
-
startEventQueue.pushPendingTree({
|
|
274
|
-
parentIndex,
|
|
275
|
-
tree: right
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
insertChild(parentToNested, parentIndex, tree);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
if (openRange !== null) {
|
|
283
|
-
result.push(nextChild(openRange, parentToNested));
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
return result;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* @precondition Same `start` and `end` for all the trees
|
|
291
|
-
*/
|
|
292
|
-
function mergeRangeTrees(trees) {
|
|
293
|
-
if (trees.length <= 1) {
|
|
294
|
-
return trees[0];
|
|
295
|
-
}
|
|
296
|
-
const first = trees[0];
|
|
297
|
-
let delta = 0;
|
|
298
|
-
for (const tree of trees) {
|
|
299
|
-
delta += tree.delta;
|
|
300
|
-
}
|
|
301
|
-
const children = mergeRangeTreeChildren(trees);
|
|
302
|
-
return new RangeTree(first.start, first.end, delta, children);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Merges a list of matching function coverages.
|
|
307
|
-
*
|
|
308
|
-
* Functions are matching if their root ranges have the same span.
|
|
309
|
-
* The result is normalized.
|
|
310
|
-
* The input values may be mutated, it is not safe to use them after passing
|
|
311
|
-
* them to this function.
|
|
312
|
-
* The computation is synchronous.
|
|
313
|
-
*
|
|
314
|
-
* @param funcCovs Function coverages to merge.
|
|
315
|
-
* @return Merged function coverage, or `undefined` if the input list was empty.
|
|
316
|
-
*/
|
|
317
|
-
function mergeFunctionCovs(funcCovs) {
|
|
318
|
-
if (funcCovs.length === 0) {
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
if (funcCovs.length === 1) {
|
|
323
|
-
const merged = funcCovs[0];
|
|
324
|
-
normalizeFunctionCov(merged);
|
|
325
|
-
return merged;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
const first = funcCovs[0];
|
|
329
|
-
const functionName = first.functionName;
|
|
330
|
-
// assert: `first.ranges.length > 0`
|
|
331
|
-
const startOffset = first.ranges[0].startOffset;
|
|
332
|
-
const endOffset = first.ranges[0].endOffset;
|
|
333
|
-
let count = 0;
|
|
334
|
-
|
|
335
|
-
const trees = [];
|
|
336
|
-
for (const funcCov of funcCovs) {
|
|
337
|
-
// assert: `funcCov.ranges.length > 0`
|
|
338
|
-
// assert: `funcCov.ranges` is sorted
|
|
339
|
-
count += typeof funcCov.count === 'number' ? funcCov.count : funcCov.ranges[0].count;
|
|
340
|
-
if (funcCov.isBlockCoverage) {
|
|
341
|
-
trees.push(fromSortedRanges(funcCov.ranges));
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
let isBlockCoverage;
|
|
346
|
-
let ranges;
|
|
347
|
-
if (trees.length > 0) {
|
|
348
|
-
isBlockCoverage = true;
|
|
349
|
-
const mergedTree = mergeRangeTrees(trees);
|
|
350
|
-
mergedTree.normalize();
|
|
351
|
-
ranges = mergedTree.toRanges();
|
|
352
|
-
} else {
|
|
353
|
-
isBlockCoverage = false;
|
|
354
|
-
ranges = [{
|
|
355
|
-
startOffset,
|
|
356
|
-
endOffset,
|
|
357
|
-
count
|
|
358
|
-
}];
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
const merged = {
|
|
362
|
-
functionName,
|
|
363
|
-
ranges,
|
|
364
|
-
isBlockCoverage
|
|
365
|
-
};
|
|
366
|
-
if (count !== ranges[0].count) {
|
|
367
|
-
merged.count = count;
|
|
368
|
-
}
|
|
369
|
-
// assert: `merged` is normalized
|
|
370
|
-
return merged;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Merges a list of matching script coverages.
|
|
376
|
-
*
|
|
377
|
-
* Scripts are matching if they have the same `url`.
|
|
378
|
-
* The result is normalized.
|
|
379
|
-
* The input values may be mutated, it is not safe to use them after passing
|
|
380
|
-
* them to this function.
|
|
381
|
-
* The computation is synchronous.
|
|
382
|
-
*
|
|
383
|
-
* @param scriptCovs Process coverages to merge.
|
|
384
|
-
* @return Merged script coverage, or `undefined` if the input list was empty.
|
|
385
|
-
*/
|
|
386
|
-
|
|
387
|
-
// eslint-disable-next-line complexity
|
|
388
|
-
function mergeV8Coverage(scriptCovs) {
|
|
389
|
-
if (!Array.isArray(scriptCovs)) {
|
|
390
|
-
return {
|
|
391
|
-
functions: []
|
|
392
|
-
};
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
if (scriptCovs.length === 0) {
|
|
396
|
-
return {
|
|
397
|
-
functions: []
|
|
398
|
-
};
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
if (scriptCovs.length === 1) {
|
|
402
|
-
const merged = scriptCovs[0];
|
|
403
|
-
deepNormalizeScriptCov(merged);
|
|
404
|
-
return merged;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
const rangeToFuncs = new Map();
|
|
409
|
-
for (const scriptCov of scriptCovs) {
|
|
410
|
-
for (const funcCov of scriptCov.functions) {
|
|
411
|
-
const rootRange = stringifyFunctionRootRange(funcCov);
|
|
412
|
-
let funcCovs = rangeToFuncs.get(rootRange);
|
|
413
|
-
if (!funcCovs) {
|
|
414
|
-
funcCovs = [];
|
|
415
|
-
rangeToFuncs.set(rootRange, funcCovs);
|
|
416
|
-
}
|
|
417
|
-
funcCovs.push(funcCov);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
const functions = [];
|
|
422
|
-
for (const funcCovs of rangeToFuncs.values()) {
|
|
423
|
-
// assert: `funcCovs.length > 0`
|
|
424
|
-
const block = mergeFunctionCovs(funcCovs);
|
|
425
|
-
if (block) {
|
|
426
|
-
functions.push(block);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
const merged = {
|
|
431
|
-
functions
|
|
432
|
-
};
|
|
433
|
-
normalizeScriptCov(merged);
|
|
434
|
-
return merged;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
module.exports = {
|
|
438
|
-
mergeV8Coverage
|
|
439
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* The `mergeV8Coverage` was extracted from following repos for fixing several issues
|
|
3
|
+
* https://github.com/bcoe/v8-coverage
|
|
4
|
+
* https://github.com/demurgos/v8-coverage
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const RangeTree = require('./range-tree.js');
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Compares two range coverages.
|
|
12
|
+
*
|
|
13
|
+
* The ranges are first ordered by ascending `startOffset` and then by
|
|
14
|
+
* descending `endOffset`.
|
|
15
|
+
* This corresponds to a pre-order tree traversal.
|
|
16
|
+
*/
|
|
17
|
+
function compareRangeCovs(a, b) {
|
|
18
|
+
if (a.startOffset !== b.startOffset) {
|
|
19
|
+
return a.startOffset - b.startOffset;
|
|
20
|
+
}
|
|
21
|
+
return b.endOffset - a.endOffset;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Compares two function coverages.
|
|
26
|
+
*
|
|
27
|
+
* The result corresponds to the comparison of the root ranges.
|
|
28
|
+
*/
|
|
29
|
+
function compareFunctionCovs(a, b) {
|
|
30
|
+
return compareRangeCovs(a.ranges[0], b.ranges[0]);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @precodition `ranges` are well-formed and pre-order sorted
|
|
35
|
+
*/
|
|
36
|
+
function fromSortedRanges(ranges) {
|
|
37
|
+
let root;
|
|
38
|
+
// Stack of parent trees and parent counts.
|
|
39
|
+
const stack = [];
|
|
40
|
+
for (const range of ranges) {
|
|
41
|
+
const node = new RangeTree(range.startOffset, range.endOffset, range.count, []);
|
|
42
|
+
if (!root) {
|
|
43
|
+
root = node;
|
|
44
|
+
stack.push([node, range.count]);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
let parent;
|
|
48
|
+
let parentCount;
|
|
49
|
+
while (true) {
|
|
50
|
+
[parent, parentCount] = stack[stack.length - 1];
|
|
51
|
+
// assert: `top !== undefined` (the ranges are sorted)
|
|
52
|
+
if (range.startOffset < parent.end) {
|
|
53
|
+
break;
|
|
54
|
+
} else {
|
|
55
|
+
stack.pop();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// prevent crash when v8 incorrectly merges static_initializer's
|
|
59
|
+
if (stack.length === 0) {
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
node.delta -= parentCount;
|
|
64
|
+
parent.children.push(node);
|
|
65
|
+
stack.push([node, range.count]);
|
|
66
|
+
}
|
|
67
|
+
return root;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Normalizes a function coverage.
|
|
72
|
+
*
|
|
73
|
+
* Sorts the ranges (pre-order sort).
|
|
74
|
+
* TODO: Tree-based normalization of the ranges.
|
|
75
|
+
*
|
|
76
|
+
* @param funcCov Function coverage to normalize.
|
|
77
|
+
*/
|
|
78
|
+
function normalizeFunctionCov(funcCov) {
|
|
79
|
+
funcCov.ranges.sort(compareRangeCovs);
|
|
80
|
+
const tree = fromSortedRanges(funcCov.ranges);
|
|
81
|
+
tree.normalize();
|
|
82
|
+
funcCov.ranges = tree.toRanges();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Normalizes a script coverage.
|
|
87
|
+
*
|
|
88
|
+
* Sorts the function by root range (pre-order sort).
|
|
89
|
+
* This does not normalize the function coverages.
|
|
90
|
+
*
|
|
91
|
+
* @param scriptCov Script coverage to normalize.
|
|
92
|
+
*/
|
|
93
|
+
function normalizeScriptCov(scriptCov) {
|
|
94
|
+
scriptCov.functions.sort(compareFunctionCovs);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Normalizes a script coverage deeply.
|
|
100
|
+
*
|
|
101
|
+
* Normalizes the function coverages deeply, then normalizes the script coverage
|
|
102
|
+
* itself.
|
|
103
|
+
*
|
|
104
|
+
* @param scriptCov Script coverage to normalize.
|
|
105
|
+
*/
|
|
106
|
+
function deepNormalizeScriptCov(scriptCov) {
|
|
107
|
+
for (const funcCov of scriptCov.functions) {
|
|
108
|
+
normalizeFunctionCov(funcCov);
|
|
109
|
+
}
|
|
110
|
+
normalizeScriptCov(scriptCov);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Returns a string representation of the root range of the function.
|
|
116
|
+
*
|
|
117
|
+
* This string can be used to match function with same root range.
|
|
118
|
+
* The string is derived from the start and end offsets of the root range of
|
|
119
|
+
* the function.
|
|
120
|
+
* This assumes that `ranges` is non-empty (true for valid function coverages).
|
|
121
|
+
*
|
|
122
|
+
* @param funcCov Function coverage with the range to stringify
|
|
123
|
+
* @internal
|
|
124
|
+
*/
|
|
125
|
+
function stringifyFunctionRootRange(funcCov) {
|
|
126
|
+
const rootRange = funcCov.ranges[0];
|
|
127
|
+
return `${rootRange.startOffset.toString(10)};${rootRange.endOffset.toString(10)}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
function insertChild(parentToNested, parentIndex, tree) {
|
|
132
|
+
let nested = parentToNested.get(parentIndex);
|
|
133
|
+
if (!nested) {
|
|
134
|
+
nested = [];
|
|
135
|
+
parentToNested.set(parentIndex, nested);
|
|
136
|
+
}
|
|
137
|
+
nested.push(tree);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function nextChild(openRange, parentToNested) {
|
|
141
|
+
const matchingTrees = [];
|
|
142
|
+
|
|
143
|
+
for (const nested of parentToNested.values()) {
|
|
144
|
+
if (nested.length === 1 && nested[0].start === openRange.start && nested[0].end === openRange.end) {
|
|
145
|
+
matchingTrees.push(nested[0]);
|
|
146
|
+
} else {
|
|
147
|
+
matchingTrees.push(new RangeTree(
|
|
148
|
+
openRange.start,
|
|
149
|
+
openRange.end,
|
|
150
|
+
0,
|
|
151
|
+
nested
|
|
152
|
+
));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
parentToNested.clear();
|
|
156
|
+
return mergeRangeTrees(matchingTrees);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
class StartEventQueue {
|
|
160
|
+
|
|
161
|
+
constructor(queue) {
|
|
162
|
+
this.queue = queue;
|
|
163
|
+
this.nextIndex = 0;
|
|
164
|
+
this.pendingOffset = 0;
|
|
165
|
+
this.pendingTrees = null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
setPendingOffset(offset) {
|
|
169
|
+
this.pendingOffset = offset;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
pushPendingTree(tree) {
|
|
173
|
+
if (this.pendingTrees === null) {
|
|
174
|
+
this.pendingTrees = [];
|
|
175
|
+
}
|
|
176
|
+
this.pendingTrees.push(tree);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
next() {
|
|
180
|
+
const pendingTrees = this.pendingTrees;
|
|
181
|
+
const nextEvent = this.queue[this.nextIndex];
|
|
182
|
+
if (pendingTrees === null) {
|
|
183
|
+
this.nextIndex++;
|
|
184
|
+
return nextEvent;
|
|
185
|
+
}
|
|
186
|
+
if (!nextEvent) {
|
|
187
|
+
this.pendingTrees = null;
|
|
188
|
+
return {
|
|
189
|
+
offset: this.pendingOffset,
|
|
190
|
+
trees: pendingTrees
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
if (this.pendingOffset < nextEvent.offset) {
|
|
194
|
+
this.pendingTrees = null;
|
|
195
|
+
return {
|
|
196
|
+
offset: this.pendingOffset,
|
|
197
|
+
trees: pendingTrees
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
if (this.pendingOffset === nextEvent.offset) {
|
|
201
|
+
this.pendingTrees = null;
|
|
202
|
+
for (const tree of pendingTrees) {
|
|
203
|
+
nextEvent.trees.push(tree);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
this.nextIndex++;
|
|
207
|
+
return nextEvent;
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function fromParentTrees(parentTrees) {
|
|
214
|
+
const startToTrees = new Map();
|
|
215
|
+
for (const [parentIndex, parentTree] of parentTrees.entries()) {
|
|
216
|
+
for (const child of parentTree.children) {
|
|
217
|
+
let trees = startToTrees.get(child.start);
|
|
218
|
+
if (!trees) {
|
|
219
|
+
trees = [];
|
|
220
|
+
startToTrees.set(child.start, trees);
|
|
221
|
+
}
|
|
222
|
+
trees.push({
|
|
223
|
+
parentIndex,
|
|
224
|
+
tree: child
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const queue = [];
|
|
229
|
+
for (const [startOffset, trees] of startToTrees) {
|
|
230
|
+
queue.push({
|
|
231
|
+
offset: startOffset,
|
|
232
|
+
trees
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
queue.sort((a, b) => {
|
|
236
|
+
return a.offset - b.offset;
|
|
237
|
+
});
|
|
238
|
+
return new StartEventQueue(queue);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// eslint-disable-next-line complexity
|
|
242
|
+
function mergeRangeTreeChildren(parentTrees) {
|
|
243
|
+
const result = [];
|
|
244
|
+
const startEventQueue = fromParentTrees(parentTrees);
|
|
245
|
+
const parentToNested = new Map();
|
|
246
|
+
let openRange = null;
|
|
247
|
+
|
|
248
|
+
while (true) {
|
|
249
|
+
const event = startEventQueue.next();
|
|
250
|
+
if (!event) {
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (openRange !== null && openRange.end <= event.offset) {
|
|
255
|
+
result.push(nextChild(openRange, parentToNested));
|
|
256
|
+
openRange = null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (openRange === null) {
|
|
260
|
+
let openRangeEnd = event.offset + 1;
|
|
261
|
+
for (const { parentIndex, tree } of event.trees) {
|
|
262
|
+
openRangeEnd = Math.max(openRangeEnd, tree.end);
|
|
263
|
+
insertChild(parentToNested, parentIndex, tree);
|
|
264
|
+
}
|
|
265
|
+
startEventQueue.setPendingOffset(openRangeEnd);
|
|
266
|
+
openRange = {
|
|
267
|
+
start: event.offset, end: openRangeEnd
|
|
268
|
+
};
|
|
269
|
+
} else {
|
|
270
|
+
for (const { parentIndex, tree } of event.trees) {
|
|
271
|
+
if (tree.end > openRange.end) {
|
|
272
|
+
const right = tree.split(openRange.end);
|
|
273
|
+
startEventQueue.pushPendingTree({
|
|
274
|
+
parentIndex,
|
|
275
|
+
tree: right
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
insertChild(parentToNested, parentIndex, tree);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (openRange !== null) {
|
|
283
|
+
result.push(nextChild(openRange, parentToNested));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return result;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* @precondition Same `start` and `end` for all the trees
|
|
291
|
+
*/
|
|
292
|
+
function mergeRangeTrees(trees) {
|
|
293
|
+
if (trees.length <= 1) {
|
|
294
|
+
return trees[0];
|
|
295
|
+
}
|
|
296
|
+
const first = trees[0];
|
|
297
|
+
let delta = 0;
|
|
298
|
+
for (const tree of trees) {
|
|
299
|
+
delta += tree.delta;
|
|
300
|
+
}
|
|
301
|
+
const children = mergeRangeTreeChildren(trees);
|
|
302
|
+
return new RangeTree(first.start, first.end, delta, children);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Merges a list of matching function coverages.
|
|
307
|
+
*
|
|
308
|
+
* Functions are matching if their root ranges have the same span.
|
|
309
|
+
* The result is normalized.
|
|
310
|
+
* The input values may be mutated, it is not safe to use them after passing
|
|
311
|
+
* them to this function.
|
|
312
|
+
* The computation is synchronous.
|
|
313
|
+
*
|
|
314
|
+
* @param funcCovs Function coverages to merge.
|
|
315
|
+
* @return Merged function coverage, or `undefined` if the input list was empty.
|
|
316
|
+
*/
|
|
317
|
+
function mergeFunctionCovs(funcCovs) {
|
|
318
|
+
if (funcCovs.length === 0) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (funcCovs.length === 1) {
|
|
323
|
+
const merged = funcCovs[0];
|
|
324
|
+
normalizeFunctionCov(merged);
|
|
325
|
+
return merged;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const first = funcCovs[0];
|
|
329
|
+
const functionName = first.functionName;
|
|
330
|
+
// assert: `first.ranges.length > 0`
|
|
331
|
+
const startOffset = first.ranges[0].startOffset;
|
|
332
|
+
const endOffset = first.ranges[0].endOffset;
|
|
333
|
+
let count = 0;
|
|
334
|
+
|
|
335
|
+
const trees = [];
|
|
336
|
+
for (const funcCov of funcCovs) {
|
|
337
|
+
// assert: `funcCov.ranges.length > 0`
|
|
338
|
+
// assert: `funcCov.ranges` is sorted
|
|
339
|
+
count += typeof funcCov.count === 'number' ? funcCov.count : funcCov.ranges[0].count;
|
|
340
|
+
if (funcCov.isBlockCoverage) {
|
|
341
|
+
trees.push(fromSortedRanges(funcCov.ranges));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
let isBlockCoverage;
|
|
346
|
+
let ranges;
|
|
347
|
+
if (trees.length > 0) {
|
|
348
|
+
isBlockCoverage = true;
|
|
349
|
+
const mergedTree = mergeRangeTrees(trees);
|
|
350
|
+
mergedTree.normalize();
|
|
351
|
+
ranges = mergedTree.toRanges();
|
|
352
|
+
} else {
|
|
353
|
+
isBlockCoverage = false;
|
|
354
|
+
ranges = [{
|
|
355
|
+
startOffset,
|
|
356
|
+
endOffset,
|
|
357
|
+
count
|
|
358
|
+
}];
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const merged = {
|
|
362
|
+
functionName,
|
|
363
|
+
ranges,
|
|
364
|
+
isBlockCoverage
|
|
365
|
+
};
|
|
366
|
+
if (count !== ranges[0].count) {
|
|
367
|
+
merged.count = count;
|
|
368
|
+
}
|
|
369
|
+
// assert: `merged` is normalized
|
|
370
|
+
return merged;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Merges a list of matching script coverages.
|
|
376
|
+
*
|
|
377
|
+
* Scripts are matching if they have the same `url`.
|
|
378
|
+
* The result is normalized.
|
|
379
|
+
* The input values may be mutated, it is not safe to use them after passing
|
|
380
|
+
* them to this function.
|
|
381
|
+
* The computation is synchronous.
|
|
382
|
+
*
|
|
383
|
+
* @param scriptCovs Process coverages to merge.
|
|
384
|
+
* @return Merged script coverage, or `undefined` if the input list was empty.
|
|
385
|
+
*/
|
|
386
|
+
|
|
387
|
+
// eslint-disable-next-line complexity
|
|
388
|
+
function mergeV8Coverage(scriptCovs) {
|
|
389
|
+
if (!Array.isArray(scriptCovs)) {
|
|
390
|
+
return {
|
|
391
|
+
functions: []
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (scriptCovs.length === 0) {
|
|
396
|
+
return {
|
|
397
|
+
functions: []
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (scriptCovs.length === 1) {
|
|
402
|
+
const merged = scriptCovs[0];
|
|
403
|
+
deepNormalizeScriptCov(merged);
|
|
404
|
+
return merged;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
const rangeToFuncs = new Map();
|
|
409
|
+
for (const scriptCov of scriptCovs) {
|
|
410
|
+
for (const funcCov of scriptCov.functions) {
|
|
411
|
+
const rootRange = stringifyFunctionRootRange(funcCov);
|
|
412
|
+
let funcCovs = rangeToFuncs.get(rootRange);
|
|
413
|
+
if (!funcCovs) {
|
|
414
|
+
funcCovs = [];
|
|
415
|
+
rangeToFuncs.set(rootRange, funcCovs);
|
|
416
|
+
}
|
|
417
|
+
funcCovs.push(funcCov);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const functions = [];
|
|
422
|
+
for (const funcCovs of rangeToFuncs.values()) {
|
|
423
|
+
// assert: `funcCovs.length > 0`
|
|
424
|
+
const block = mergeFunctionCovs(funcCovs);
|
|
425
|
+
if (block) {
|
|
426
|
+
functions.push(block);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const merged = {
|
|
431
|
+
functions
|
|
432
|
+
};
|
|
433
|
+
normalizeScriptCov(merged);
|
|
434
|
+
return merged;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
module.exports = {
|
|
438
|
+
mergeV8Coverage
|
|
439
|
+
};
|