|
|
import { deepNormalizeScriptCov, normalizeFunctionCov, normalizeProcessCov, normalizeRangeTree, normalizeScriptCov, } from "./normalize"; import { RangeTree } from "./range-tree"; /** * Merges a list of process coverages. * * The result is normalized. * The input values may be mutated, it is not safe to use them after passing * them to this function. * The computation is synchronous. * * @param processCovs Process coverages to merge. * @return Merged process coverage. */ export function mergeProcessCovs(processCovs) { if (processCovs.length === 0) { return { result: [] }; } const urlToScripts = new Map(); for (const processCov of processCovs) { for (const scriptCov of processCov.result) { let scriptCovs = urlToScripts.get(scriptCov.url); if (scriptCovs === undefined) { scriptCovs = []; urlToScripts.set(scriptCov.url, scriptCovs); } scriptCovs.push(scriptCov); } } const result = []; for (const scripts of urlToScripts.values()) { // assert: `scripts.length > 0` result.push(mergeScriptCovs(scripts)); } const merged = { result }; normalizeProcessCov(merged); return merged; } /** * Merges a list of matching script coverages. * * Scripts are matching if they have the same `url`. * The result is normalized. * The input values may be mutated, it is not safe to use them after passing * them to this function. * The computation is synchronous. * * @param scriptCovs Process coverages to merge. * @return Merged script coverage, or `undefined` if the input list was empty. */ export function mergeScriptCovs(scriptCovs) { if (scriptCovs.length === 0) { return undefined; } else if (scriptCovs.length === 1) { const merged = scriptCovs[0]; deepNormalizeScriptCov(merged); return merged; } const first = scriptCovs[0]; const scriptId = first.scriptId; const url = first.url; const rangeToFuncs = new Map(); for (const scriptCov of scriptCovs) { for (const funcCov of scriptCov.functions) { const rootRange = stringifyFunctionRootRange(funcCov); let funcCovs = rangeToFuncs.get(rootRange); if (funcCovs === undefined || // if the entry in rangeToFuncs is function-level granularity and // the new coverage is block-level, prefer block-level. (!funcCovs[0].isBlockCoverage && funcCov.isBlockCoverage)) { funcCovs = []; rangeToFuncs.set(rootRange, funcCovs); } else if (funcCovs[0].isBlockCoverage && !funcCov.isBlockCoverage) { // if the entry in rangeToFuncs is block-level granularity, we should // not append function level granularity. continue; } funcCovs.push(funcCov); } } const functions = []; for (const funcCovs of rangeToFuncs.values()) { // assert: `funcCovs.length > 0` functions.push(mergeFunctionCovs(funcCovs)); } const merged = { scriptId, url, functions }; normalizeScriptCov(merged); return merged; } /** * Returns a string representation of the root range of the function. * * This string can be used to match function with same root range. * The string is derived from the start and end offsets of the root range of * the function. * This assumes that `ranges` is non-empty (true for valid function coverages). * * @param funcCov Function coverage with the range to stringify * @internal */ function stringifyFunctionRootRange(funcCov) { const rootRange = funcCov.ranges[0]; return `${rootRange.startOffset.toString(10)};${rootRange.endOffset.toString(10)}`; } /** * Merges a list of matching function coverages. * * Functions are matching if their root ranges have the same span. * The result is normalized. * The input values may be mutated, it is not safe to use them after passing * them to this function. * The computation is synchronous. * * @param funcCovs Function coverages to merge. * @return Merged function coverage, or `undefined` if the input list was empty. */ export function mergeFunctionCovs(funcCovs) { if (funcCovs.length === 0) { return undefined; } else if (funcCovs.length === 1) { const merged = funcCovs[0]; normalizeFunctionCov(merged); return merged; } const functionName = funcCovs[0].functionName; const trees = []; for (const funcCov of funcCovs) { // assert: `fn.ranges.length > 0` // assert: `fn.ranges` is sorted trees.push(RangeTree.fromSortedRanges(funcCov.ranges)); } // assert: `trees.length > 0` const mergedTree = mergeRangeTrees(trees); normalizeRangeTree(mergedTree); const ranges = mergedTree.toRanges(); const isBlockCoverage = !(ranges.length === 1 && ranges[0].count === 0); const merged = { functionName, ranges, isBlockCoverage }; // assert: `merged` is normalized return merged; } /** * @precondition Same `start` and `end` for all the trees */ function mergeRangeTrees(trees) { if (trees.length <= 1) { return trees[0]; } const first = trees[0]; let delta = 0; for (const tree of trees) { delta += tree.delta; } const children = mergeRangeTreeChildren(trees); return new RangeTree(first.start, first.end, delta, children); } class RangeTreeWithParent { constructor(parentIndex, tree) { this.parentIndex = parentIndex; this.tree = tree; } } class StartEvent { constructor(offset, trees) { this.offset = offset; this.trees = trees; } static compare(a, b) { return a.offset - b.offset; } } class StartEventQueue { constructor(queue) { this.queue = queue; this.nextIndex = 0; this.pendingOffset = 0; this.pendingTrees = undefined; } static fromParentTrees(parentTrees) { const startToTrees = new Map(); for (const [parentIndex, parentTree] of parentTrees.entries()) { for (const child of parentTree.children) { let trees = startToTrees.get(child.start); if (trees === undefined) { trees = []; startToTrees.set(child.start, trees); } trees.push(new RangeTreeWithParent(parentIndex, child)); } } const queue = []; for (const [startOffset, trees] of startToTrees) { queue.push(new StartEvent(startOffset, trees)); } queue.sort(StartEvent.compare); return new StartEventQueue(queue); } setPendingOffset(offset) { this.pendingOffset = offset; } pushPendingTree(tree) { if (this.pendingTrees === undefined) { this.pendingTrees = []; } this.pendingTrees.push(tree); } next() { const pendingTrees = this.pendingTrees; const nextEvent = this.queue[this.nextIndex]; if (pendingTrees === undefined) { this.nextIndex++; return nextEvent; } else if (nextEvent === undefined) { this.pendingTrees = undefined; return new StartEvent(this.pendingOffset, pendingTrees); } else { if (this.pendingOffset < nextEvent.offset) { this.pendingTrees = undefined; return new StartEvent(this.pendingOffset, pendingTrees); } else { if (this.pendingOffset === nextEvent.offset) { this.pendingTrees = undefined; for (const tree of pendingTrees) { nextEvent.trees.push(tree); } } this.nextIndex++; return nextEvent; } } } } function mergeRangeTreeChildren(parentTrees) { const result = []; const startEventQueue = StartEventQueue.fromParentTrees(parentTrees); const parentToNested = new Map(); let openRange; while (true) { const event = startEventQueue.next(); if (event === undefined) { break; } if (openRange !== undefined && openRange.end <= event.offset) { result.push(nextChild(openRange, parentToNested)); openRange = undefined; } if (openRange === undefined) { let openRangeEnd = event.offset + 1; for (const { parentIndex, tree } of event.trees) { openRangeEnd = Math.max(openRangeEnd, tree.end); insertChild(parentToNested, parentIndex, tree); } startEventQueue.setPendingOffset(openRangeEnd); openRange = { start: event.offset, end: openRangeEnd }; } else { for (const { parentIndex, tree } of event.trees) { if (tree.end > openRange.end) { const right = tree.split(openRange.end); startEventQueue.pushPendingTree(new RangeTreeWithParent(parentIndex, right)); } insertChild(parentToNested, parentIndex, tree); } } } if (openRange !== undefined) { result.push(nextChild(openRange, parentToNested)); } return result; } function insertChild(parentToNested, parentIndex, tree) { let nested = parentToNested.get(parentIndex); if (nested === undefined) { nested = []; parentToNested.set(parentIndex, nested); } nested.push(tree); } function nextChild(openRange, parentToNested) { const matchingTrees = []; for (const nested of parentToNested.values()) { if (nested.length === 1 && nested[0].start === openRange.start && nested[0].end === openRange.end) { matchingTrees.push(nested[0]); } else { matchingTrees.push(new RangeTree(openRange.start, openRange.end, 0, nested)); } } parentToNested.clear(); return mergeRangeTrees(matchingTrees); }
//# sourceMappingURL=data:application/json;charset=utf8;base64,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
|