From ff1b5e915201e4ff8f737010509bab98d8238118 Mon Sep 17 00:00:00 2001 From: Justin Christensen Date: Mon, 13 Sep 2021 17:30:22 -0500 Subject: [PATCH] fix: merge branch/statement/functionMap's together when merging two coverage reports (#617) --- .../lib/file-coverage.js | 90 +++++++++++++++---- .../istanbul-lib-coverage/test/file.test.js | 40 ++++----- 2 files changed, 92 insertions(+), 38 deletions(-) diff --git a/packages/istanbul-lib-coverage/lib/file-coverage.js b/packages/istanbul-lib-coverage/lib/file-coverage.js index ed056a6f..541568c0 100644 --- a/packages/istanbul-lib-coverage/lib/file-coverage.js +++ b/packages/istanbul-lib-coverage/lib/file-coverage.js @@ -40,6 +40,47 @@ function assertValidObject(obj) { } } +const keyFromLoc = ({ start, end }) => + `${start.line}|${start.column}|${end.line}|${end.column}`; + +const mergeProp = (aHits, aMap, bHits, bMap, itemKey = keyFromLoc) => { + const aItems = Object.values(aHits).reduce((items, itemHits, i) => { + const item = aMap[i]; + items[itemKey(item)] = [itemHits, item]; + return items; + }, {}); + + Object.values(bHits).forEach((bItemHits, i) => { + const bItem = bMap[i]; + const k = itemKey(bItem); + + if (aItems[k]) { + const aPair = aItems[k]; + if (bItemHits.forEach) { + // should this throw an exception if aPair[0] is not an array? + bItemHits.forEach((hits, h) => { + if (aPair[0][h] !== undefined) aPair[0][h] += hits; + else aPair[0][h] = hits; + }); + } else { + aPair[0] += bItemHits; + } + } else { + aItems[k] = [bItemHits, bItem]; + } + }); + + const hits = {}; + const map = {}; + + Object.values(aItems).forEach(([itemHits, item], i) => { + hits[i] = itemHits; + map[i] = item; + }); + + return [hits, map]; +}; + /** * provides a read-only view of coverage for a single file. * The deep structure of this object is documented elsewhere. It has the following @@ -166,24 +207,37 @@ class FileCoverage { return; } - Object.entries(other.s).forEach(([k, v]) => { - this.data.s[k] += v; - }); - Object.entries(other.f).forEach(([k, v]) => { - this.data.f[k] += v; - }); - Object.entries(other.b).forEach(([k, v]) => { - let i; - const retArray = this.data.b[k]; - /* istanbul ignore if: is this even possible? */ - if (!retArray) { - this.data.b[k] = v; - return; - } - for (i = 0; i < retArray.length; i += 1) { - retArray[i] += v[i]; - } - }); + let [hits, map] = mergeProp( + this.s, + this.statementMap, + other.s, + other.statementMap + ); + this.data.s = hits; + this.data.statementMap = map; + + const keyFromLocProp = x => keyFromLoc(x.loc); + const keyFromLocationsProp = x => keyFromLoc(x.locations[0]); + + [hits, map] = mergeProp( + this.f, + this.fnMap, + other.f, + other.fnMap, + keyFromLocProp + ); + this.data.f = hits; + this.data.fnMap = map; + + [hits, map] = mergeProp( + this.b, + this.branchMap, + other.b, + other.branchMap, + keyFromLocationsProp + ); + this.data.b = hits; + this.data.branchMap = map; } computeSimpleTotals(property) { diff --git a/packages/istanbul-lib-coverage/test/file.test.js b/packages/istanbul-lib-coverage/test/file.test.js index 167779e6..f5a07ddc 100644 --- a/packages/istanbul-lib-coverage/test/file.test.js +++ b/packages/istanbul-lib-coverage/test/file.test.js @@ -153,36 +153,36 @@ describe('base coverage', () => { const template = new FileCoverage({ path: '/path/to/file', statementMap: { - 1: loc(1, 1, 1, 100), - 2: loc(2, 1, 2, 50), - 3: loc(2, 51, 2, 100), - 4: loc(2, 101, 3, 100) + 0: loc(1, 1, 1, 100), + 1: loc(2, 1, 2, 50), + 2: loc(2, 51, 2, 100), + 3: loc(2, 101, 3, 100) }, fnMap: { - 1: { + 0: { name: 'foobar', line: 1, loc: loc(1, 1, 1, 50) } }, branchMap: { - 1: { + 0: { type: 'if', line: 2, locations: [loc(2, 1, 2, 20), loc(2, 50, 2, 100)] } }, s: { + 0: 0, 1: 0, 2: 0, - 3: 0, - 4: 0 + 3: 0 }, f: { - 1: 0 + 0: 0 }, b: { - 1: [0, 0] + 0: [0, 0] } }); const clone = function(obj) { @@ -192,13 +192,13 @@ describe('base coverage', () => { const c2 = new FileCoverage(clone(template)); let summary; - c1.s[1] = 1; - c1.f[1] = 1; - c1.b[1][0] = 1; + c1.s[0] = 1; + c1.f[0] = 1; + c1.b[0][0] = 1; - c2.s[2] = 1; - c2.f[1] = 1; - c2.b[1][1] = 2; + c2.s[1] = 1; + c2.f[0] = 1; + c2.b[0][1] = 2; summary = c1.toSummary(); assert.ok(summary instanceof CoverageSummary); assert.deepEqual(summary.statements, { @@ -253,11 +253,11 @@ describe('base coverage', () => { pct: 100 }); + assert.equal(c1.s[0], 1); assert.equal(c1.s[1], 1); - assert.equal(c1.s[2], 1); - assert.equal(c1.f[1], 2); - assert.equal(c1.b[1][0], 1); - assert.equal(c1.b[1][1], 2); + assert.equal(c1.f[0], 2); + assert.equal(c1.b[0][0], 1); + assert.equal(c1.b[0][1], 2); }); it('drops all data during merges', () => {