Skip to content

Commit 9def3eb

Browse files
authoredMay 14, 2018
feat: merge together multiple istanbul format reports (#840)
1 parent 43bda0c commit 9def3eb

File tree

8 files changed

+544
-16
lines changed

8 files changed

+544
-16
lines changed
 

‎bin/nyc.js

+4-6
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,10 @@ const config = configUtil.loadConfig(yargs.parse(instrumenterArgs))
2323
configUtil.addCommandsAndHelp(yargs)
2424
const argv = yargs.config(config).parse(instrumenterArgs)
2525

26-
if (argv._[0] === 'report') {
27-
// look in lib/commands/report.js for logic.
28-
} else if (argv._[0] === 'check-coverage') {
29-
// look in lib/commands/check-coverage.js for logic.
30-
} else if (argv._[0] === 'instrument') {
31-
// look in lib/commands/instrument.js for logic.
26+
if ([
27+
'check-coverage', 'report', 'instrument', 'merge'
28+
].indexOf(argv._[0]) !== -1) {
29+
// look in lib/commands for logic.
3230
} else if (argv._.length) {
3331
// if instrument is set to false,
3432
// enable a noop instrumenter.

‎index.js

+11-9
Original file line numberDiff line numberDiff line change
@@ -421,13 +421,13 @@ function coverageFinder () {
421421
return coverage
422422
}
423423

424-
NYC.prototype._getCoverageMapFromAllCoverageFiles = function () {
424+
NYC.prototype.getCoverageMapFromAllCoverageFiles = function (baseDirectory) {
425425
var _this = this
426426
var map = libCoverage.createCoverageMap({})
427427

428-
this.eachReport(function (report) {
428+
this.eachReport(undefined, (report) => {
429429
map.merge(report)
430-
})
430+
}, baseDirectory)
431431
// depending on whether source-code is pre-instrumented
432432
// or instrumented using a JIT plugin like babel-require
433433
// you may opt to exclude files after applying
@@ -443,7 +443,7 @@ NYC.prototype._getCoverageMapFromAllCoverageFiles = function () {
443443

444444
NYC.prototype.report = function () {
445445
var tree
446-
var map = this._getCoverageMapFromAllCoverageFiles()
446+
var map = this.getCoverageMapFromAllCoverageFiles()
447447
var context = libReport.createContext({
448448
dir: this.reportDirectory(),
449449
watermarks: this.config.watermarks
@@ -469,7 +469,7 @@ NYC.prototype.showProcessTree = function () {
469469
}
470470

471471
NYC.prototype.checkCoverage = function (thresholds, perFile) {
472-
var map = this._getCoverageMapFromAllCoverageFiles()
472+
var map = this.getCoverageMapFromAllCoverageFiles()
473473
var nyc = this
474474

475475
if (perFile) {
@@ -516,20 +516,22 @@ NYC.prototype._loadProcessInfos = function () {
516516
})
517517
}
518518

519-
NYC.prototype.eachReport = function (filenames, iterator) {
519+
NYC.prototype.eachReport = function (filenames, iterator, baseDirectory) {
520+
baseDirectory = baseDirectory || this.tempDirectory()
521+
520522
if (typeof filenames === 'function') {
521523
iterator = filenames
522524
filenames = undefined
523525
}
524526

525527
var _this = this
526-
var files = filenames || fs.readdirSync(this.tempDirectory())
528+
var files = filenames || fs.readdirSync(baseDirectory)
527529

528530
files.forEach(function (f) {
529531
var report
530532
try {
531533
report = JSON.parse(fs.readFileSync(
532-
path.resolve(_this.tempDirectory(), f),
534+
path.resolve(baseDirectory, f),
533535
'utf-8'
534536
))
535537

@@ -545,7 +547,7 @@ NYC.prototype.eachReport = function (filenames, iterator) {
545547
NYC.prototype.loadReports = function (filenames) {
546548
var reports = []
547549

548-
this.eachReport(filenames, function (report) {
550+
this.eachReport(filenames, (report) => {
549551
reports.push(report)
550552
})
551553

‎lib/commands/merge.js

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
'use strict'
2+
const fs = require('fs')
3+
4+
var NYC
5+
try {
6+
NYC = require('../../index.covered.js')
7+
} catch (e) {
8+
NYC = require('../../index.js')
9+
}
10+
11+
exports.command = 'merge <input-directory> [output-file]'
12+
13+
exports.describe = 'merge istanbul format coverage output in a given folder'
14+
15+
exports.builder = function (yargs) {
16+
return yargs
17+
.positional('input-directory', {
18+
describe: 'directory containing multiple istanbul coverage files',
19+
type: 'text',
20+
default: './.nyc_output'
21+
})
22+
.positional('output-file', {
23+
describe: 'file to output combined istanbul format coverage to',
24+
type: 'text',
25+
default: 'coverage.json'
26+
})
27+
.option('temp-directory', {
28+
describe: 'directory to read raw coverage information from',
29+
default: './.nyc_output'
30+
})
31+
.example('$0 merge ./out coverage.json', 'merge together reports in ./out and output as coverage.json')
32+
}
33+
34+
exports.handler = function (argv) {
35+
process.env.NYC_CWD = process.cwd()
36+
const nyc = new NYC(argv)
37+
let inputStat
38+
try {
39+
inputStat = fs.statSync(argv.inputDirectory)
40+
if (!inputStat.isDirectory()) {
41+
console.error(`${argv.inputDirectory} was not a directory`)
42+
process.exit(1)
43+
}
44+
} catch (err) {
45+
console.error(`failed access input directory ${argv.inputDirectory} with error:\n\n${err.message}`)
46+
process.exit(1)
47+
}
48+
const map = nyc.getCoverageMapFromAllCoverageFiles(argv.inputDirectory)
49+
fs.writeFileSync(argv.outputFile, JSON.stringify(map, null, 2), 'utf8')
50+
console.info(`coverage files in ${argv.inputDirectory} merged into ${argv.outputFile}`)
51+
}

‎lib/config-util.js

+1
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ Config.addCommandsAndHelp = function (yargs) {
243243
.command(require('../lib/commands/check-coverage'))
244244
.command(require('../lib/commands/instrument'))
245245
.command(require('../lib/commands/report'))
246+
.command(require('../lib/commands/merge'))
246247
}
247248

248249
module.exports = Config

‎lib/process-args.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ const parser = require('yargs-parser')
22
const commands = [
33
'report',
44
'check-coverage',
5-
'instrument'
5+
'instrument',
6+
'merge'
67
]
78

89
module.exports = {

‎test/fixtures/cli/merge-input/a.json

+200
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
{
2+
"/private/tmp/contrived/library.js": {
3+
"path": "/private/tmp/contrived/library.js",
4+
"statementMap": {
5+
"0": {
6+
"start": {
7+
"line": 1,
8+
"column": 0
9+
},
10+
"end": {
11+
"line": 15,
12+
"column": 1
13+
}
14+
},
15+
"1": {
16+
"start": {
17+
"line": 3,
18+
"column": 4
19+
},
20+
"end": {
21+
"line": 3,
22+
"column": 16
23+
}
24+
},
25+
"2": {
26+
"start": {
27+
"line": 6,
28+
"column": 4
29+
},
30+
"end": {
31+
"line": 6,
32+
"column": 16
33+
}
34+
},
35+
"3": {
36+
"start": {
37+
"line": 9,
38+
"column": 4
39+
},
40+
"end": {
41+
"line": 13,
42+
"column": 5
43+
}
44+
},
45+
"4": {
46+
"start": {
47+
"line": 10,
48+
"column": 6
49+
},
50+
"end": {
51+
"line": 10,
52+
"column": 14
53+
}
54+
},
55+
"5": {
56+
"start": {
57+
"line": 12,
58+
"column": 6
59+
},
60+
"end": {
61+
"line": 12,
62+
"column": 14
63+
}
64+
}
65+
},
66+
"fnMap": {
67+
"0": {
68+
"name": "(anonymous_0)",
69+
"decl": {
70+
"start": {
71+
"line": 2,
72+
"column": 11
73+
},
74+
"end": {
75+
"line": 2,
76+
"column": 12
77+
}
78+
},
79+
"loc": {
80+
"start": {
81+
"line": 2,
82+
"column": 21
83+
},
84+
"end": {
85+
"line": 4,
86+
"column": 3
87+
}
88+
},
89+
"line": 2
90+
},
91+
"1": {
92+
"name": "(anonymous_1)",
93+
"decl": {
94+
"start": {
95+
"line": 5,
96+
"column": 11
97+
},
98+
"end": {
99+
"line": 5,
100+
"column": 12
101+
}
102+
},
103+
"loc": {
104+
"start": {
105+
"line": 5,
106+
"column": 21
107+
},
108+
"end": {
109+
"line": 7,
110+
"column": 3
111+
}
112+
},
113+
"line": 5
114+
},
115+
"2": {
116+
"name": "(anonymous_2)",
117+
"decl": {
118+
"start": {
119+
"line": 8,
120+
"column": 11
121+
},
122+
"end": {
123+
"line": 8,
124+
"column": 12
125+
}
126+
},
127+
"loc": {
128+
"start": {
129+
"line": 8,
130+
"column": 18
131+
},
132+
"end": {
133+
"line": 14,
134+
"column": 3
135+
}
136+
},
137+
"line": 8
138+
}
139+
},
140+
"branchMap": {
141+
"0": {
142+
"loc": {
143+
"start": {
144+
"line": 9,
145+
"column": 4
146+
},
147+
"end": {
148+
"line": 13,
149+
"column": 5
150+
}
151+
},
152+
"type": "if",
153+
"locations": [
154+
{
155+
"start": {
156+
"line": 9,
157+
"column": 4
158+
},
159+
"end": {
160+
"line": 13,
161+
"column": 5
162+
}
163+
},
164+
{
165+
"start": {
166+
"line": 9,
167+
"column": 4
168+
},
169+
"end": {
170+
"line": 13,
171+
"column": 5
172+
}
173+
}
174+
],
175+
"line": 9
176+
}
177+
},
178+
"s": {
179+
"0": 1,
180+
"1": 1,
181+
"2": 0,
182+
"3": 1,
183+
"4": 0,
184+
"5": 1
185+
},
186+
"f": {
187+
"0": 1,
188+
"1": 0,
189+
"2": 1
190+
},
191+
"b": {
192+
"0": [
193+
0,
194+
1
195+
]
196+
},
197+
"_coverageSchema": "332fd63041d2c1bcb487cc26dd0d5f7d97098a6c",
198+
"hash": "e86c0c0fa7c4fadac81e2479bfba3c0d59b657aa"
199+
}
200+
}

‎test/fixtures/cli/merge-input/b.json

+200
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
{
2+
"/private/tmp/contrived/library.js": {
3+
"path": "/private/tmp/contrived/library.js",
4+
"statementMap": {
5+
"0": {
6+
"start": {
7+
"line": 1,
8+
"column": 0
9+
},
10+
"end": {
11+
"line": 15,
12+
"column": 1
13+
}
14+
},
15+
"1": {
16+
"start": {
17+
"line": 3,
18+
"column": 4
19+
},
20+
"end": {
21+
"line": 3,
22+
"column": 16
23+
}
24+
},
25+
"2": {
26+
"start": {
27+
"line": 6,
28+
"column": 4
29+
},
30+
"end": {
31+
"line": 6,
32+
"column": 16
33+
}
34+
},
35+
"3": {
36+
"start": {
37+
"line": 9,
38+
"column": 4
39+
},
40+
"end": {
41+
"line": 13,
42+
"column": 5
43+
}
44+
},
45+
"4": {
46+
"start": {
47+
"line": 10,
48+
"column": 6
49+
},
50+
"end": {
51+
"line": 10,
52+
"column": 14
53+
}
54+
},
55+
"5": {
56+
"start": {
57+
"line": 12,
58+
"column": 6
59+
},
60+
"end": {
61+
"line": 12,
62+
"column": 14
63+
}
64+
}
65+
},
66+
"fnMap": {
67+
"0": {
68+
"name": "(anonymous_0)",
69+
"decl": {
70+
"start": {
71+
"line": 2,
72+
"column": 11
73+
},
74+
"end": {
75+
"line": 2,
76+
"column": 12
77+
}
78+
},
79+
"loc": {
80+
"start": {
81+
"line": 2,
82+
"column": 21
83+
},
84+
"end": {
85+
"line": 4,
86+
"column": 3
87+
}
88+
},
89+
"line": 2
90+
},
91+
"1": {
92+
"name": "(anonymous_1)",
93+
"decl": {
94+
"start": {
95+
"line": 5,
96+
"column": 11
97+
},
98+
"end": {
99+
"line": 5,
100+
"column": 12
101+
}
102+
},
103+
"loc": {
104+
"start": {
105+
"line": 5,
106+
"column": 21
107+
},
108+
"end": {
109+
"line": 7,
110+
"column": 3
111+
}
112+
},
113+
"line": 5
114+
},
115+
"2": {
116+
"name": "(anonymous_2)",
117+
"decl": {
118+
"start": {
119+
"line": 8,
120+
"column": 11
121+
},
122+
"end": {
123+
"line": 8,
124+
"column": 12
125+
}
126+
},
127+
"loc": {
128+
"start": {
129+
"line": 8,
130+
"column": 18
131+
},
132+
"end": {
133+
"line": 14,
134+
"column": 3
135+
}
136+
},
137+
"line": 8
138+
}
139+
},
140+
"branchMap": {
141+
"0": {
142+
"loc": {
143+
"start": {
144+
"line": 9,
145+
"column": 4
146+
},
147+
"end": {
148+
"line": 13,
149+
"column": 5
150+
}
151+
},
152+
"type": "if",
153+
"locations": [
154+
{
155+
"start": {
156+
"line": 9,
157+
"column": 4
158+
},
159+
"end": {
160+
"line": 13,
161+
"column": 5
162+
}
163+
},
164+
{
165+
"start": {
166+
"line": 9,
167+
"column": 4
168+
},
169+
"end": {
170+
"line": 13,
171+
"column": 5
172+
}
173+
}
174+
],
175+
"line": 9
176+
}
177+
},
178+
"s": {
179+
"0": 1,
180+
"1": 0,
181+
"2": 1,
182+
"3": 1,
183+
"4": 1,
184+
"5": 0
185+
},
186+
"f": {
187+
"0": 0,
188+
"1": 1,
189+
"2": 1
190+
},
191+
"b": {
192+
"0": [
193+
1,
194+
0
195+
]
196+
},
197+
"_coverageSchema": "332fd63041d2c1bcb487cc26dd0d5f7d97098a6c",
198+
"hash": "e86c0c0fa7c4fadac81e2479bfba3c0d59b657aa"
199+
}
200+
}

‎test/nyc-bin.js

+75
Original file line numberDiff line numberDiff line change
@@ -972,6 +972,81 @@ describe('the nyc cli', function () {
972972
})
973973
})
974974
})
975+
976+
describe('merge', () => {
977+
it('combines multiple coverage reports', (done) => {
978+
const args = [
979+
bin,
980+
'merge',
981+
'./merge-input'
982+
]
983+
984+
const proc = spawn(process.execPath, args, {
985+
cwd: fixturesCLI,
986+
env: env
987+
})
988+
989+
proc.on('close', function (code) {
990+
const mergedCoverage = require('./fixtures/cli/coverage')
991+
// the combined reports should have 100% function
992+
// branch and statement coverage.
993+
mergedCoverage['/private/tmp/contrived/library.js']
994+
.s.should.eql({'0': 2, '1': 1, '2': 1, '3': 2, '4': 1, '5': 1})
995+
mergedCoverage['/private/tmp/contrived/library.js']
996+
.f.should.eql({'0': 1, '1': 1, '2': 2})
997+
mergedCoverage['/private/tmp/contrived/library.js']
998+
.b.should.eql({'0': [1, 1]})
999+
rimraf.sync(path.resolve(fixturesCLI, 'coverage.json'))
1000+
return done()
1001+
})
1002+
})
1003+
1004+
it('reports error if input directory is missing', (done) => {
1005+
const args = [
1006+
bin,
1007+
'merge',
1008+
'./DIRECTORY_THAT_IS_MISSING'
1009+
]
1010+
1011+
const proc = spawn(process.execPath, args, {
1012+
cwd: fixturesCLI,
1013+
env: env
1014+
})
1015+
1016+
var stderr = ''
1017+
proc.stderr.on('data', function (chunk) {
1018+
stderr += chunk
1019+
})
1020+
1021+
proc.on('close', function (code) {
1022+
stderr.should.match(/failed access input directory/)
1023+
return done()
1024+
})
1025+
})
1026+
1027+
it('reports error if input is not a directory', (done) => {
1028+
const args = [
1029+
bin,
1030+
'merge',
1031+
'./package.json'
1032+
]
1033+
1034+
const proc = spawn(process.execPath, args, {
1035+
cwd: fixturesCLI,
1036+
env: env
1037+
})
1038+
1039+
var stderr = ''
1040+
proc.stderr.on('data', function (chunk) {
1041+
stderr += chunk
1042+
})
1043+
1044+
proc.on('close', function (code) {
1045+
stderr.should.match(/was not a directory/)
1046+
return done()
1047+
})
1048+
})
1049+
})
9751050
})
9761051

9771052
function stdoutShouldEqual (stdout, expected) {

0 commit comments

Comments
 (0)
Please sign in to comment.