diff --git a/lib/commands/check-coverage.js b/lib/commands/check-coverage.js index e2e73cc9..6e49bd4c 100644 --- a/lib/commands/check-coverage.js +++ b/lib/commands/check-coverage.js @@ -44,9 +44,16 @@ exports.checkCoverages = async function (argv, report) { } const map = await report.getCoverageMapFromAllCoverageFiles() if (argv.perFile) { - map.files().forEach(file => { - checkCoverage(map.fileCoverageFor(file).toSummary(), thresholds, file) - }) + const files = map.files() + if (files.length === 0) { + // There is nothing to check per file. Fall back to the (empty) overall + // summary so that a non-zero threshold is still reported as unmet. + checkCoverage(map.getCoverageSummary(), thresholds) + } else { + files.forEach(file => { + checkCoverage(map.fileCoverageFor(file).toSummary(), thresholds, file) + }) + } } else { checkCoverage(map.getCoverageSummary(), thresholds) } @@ -54,8 +61,14 @@ exports.checkCoverages = async function (argv, report) { function checkCoverage (summary, thresholds, file) { Object.keys(thresholds).forEach(key => { + const threshold = thresholds[key] + if (threshold === undefined) return const coverage = summary[key].pct - if (coverage < thresholds[key]) { + // When there is nothing to measure, istanbul reports the string "Unknown" + // rather than a number. A string never compares as less than a number, so + // the check below would silently pass. Treat it as unmet whenever a + // threshold was actually requested (a threshold of 0 means "don't care"). + if (coverage === 'Unknown' ? threshold > 0 : coverage < threshold) { process.exitCode = 1 if (file) { console.error( diff --git a/test/integration.js b/test/integration.js index 8e00be0b..2eefb1f3 100644 --- a/test/integration.js +++ b/test/integration.js @@ -164,7 +164,7 @@ beforeEach(function () { const { output, status } = spawnSync(nodePath, [ c8Path, 'check-coverage', - '--exclude="test/fixtures/*.js"', + '--exclude="test/fixtures/all/**"', '--temp-directory=tmp/check-coverage', '--lines=70', '--branches=55', @@ -245,6 +245,67 @@ beforeEach(function () { status.should.equal(1) output.toString('utf8').should.matchSnapshot() }) + + it('exits with 1 if there is no coverage data to check', () => { + const { output, status } = spawnSync(nodePath, [ + c8Path, + '--exclude="test/*.js"', + '--temp-directory=tmp/check-coverage-empty', + '--clean=true', + '--lines=100', + '--branches=0', + '--functions=0', + '--statements=0', + '--check-coverage', + `--merge-async=${mergeAsync}`, + nodePath, + '-e', + '1' + ]) + status.should.equal(1) + output.toString('utf8').should.matchSnapshot() + }) + + it('exits with 1 if there is no coverage data to check per file', () => { + const { output, status } = spawnSync(nodePath, [ + c8Path, + '--exclude="test/*.js"', + '--temp-directory=tmp/check-coverage-empty', + '--clean=true', + '--lines=100', + '--branches=0', + '--functions=0', + '--statements=0', + '--check-coverage', + '--per-file', + `--merge-async=${mergeAsync}`, + nodePath, + '-e', + '1' + ]) + status.should.equal(1) + output.toString('utf8').should.matchSnapshot() + }) + + it('exits with 0 if there is no coverage data but the threshold is 0', () => { + const { output, status } = spawnSync(nodePath, [ + c8Path, + '--exclude="test/*.js"', + '--temp-directory=tmp/check-coverage-empty', + '--clean=true', + '--lines=0', + '--branches=0', + '--functions=0', + '--statements=0', + '--check-coverage', + `--merge-async=${mergeAsync}`, + nodePath, + '-e', + '1' + ]) + status.should.equal(0) + output.toString('utf8').should.matchSnapshot() + }) }) describe('report', () => { diff --git a/test/integration.js.snap b/test/integration.js.snap index c2fba0a4..edfdec47 100644 --- a/test/integration.js.snap +++ b/test/integration.js.snap @@ -296,12 +296,41 @@ ERROR: Coverage for statements (83.33%) does not meet global threshold (100%) exports[`c8 check-coverage exits with 0 if coverage within threshold 1`] = `",,"`; +exports[`c8 check-coverage exits with 0 if there is no coverage data but the threshold is 0 1`] = ` +",----------|---------|----------|---------|---------|------------------- +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s +----------|---------|----------|---------|---------|------------------- +All files | 0 | 0 | 0 | 0 | +----------|---------|----------|---------|---------|------------------- +," +`; + exports[`c8 check-coverage exits with 1 if coverage is below threshold 1`] = ` ",,ERROR: Coverage for lines (83.33%) does not meet global threshold (101%) ERROR: Coverage for statements (83.33%) does not meet global threshold (95%) " `; +exports[`c8 check-coverage exits with 1 if there is no coverage data to check 1`] = ` +",----------|---------|----------|---------|---------|------------------- +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s +----------|---------|----------|---------|---------|------------------- +All files | 0 | 0 | 0 | 0 | +----------|---------|----------|---------|---------|------------------- +,ERROR: Coverage for lines (Unknown%) does not meet global threshold (100%) +" +`; + +exports[`c8 check-coverage exits with 1 if there is no coverage data to check per file 1`] = ` +",----------|---------|----------|---------|---------|------------------- +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s +----------|---------|----------|---------|---------|------------------- +All files | 0 | 0 | 0 | 0 | +----------|---------|----------|---------|---------|------------------- +,ERROR: Coverage for lines (Unknown%) does not meet global threshold (100%) +" +`; + exports[`c8 cobertura report escapes special characters 1`] = ` " @@ -662,12 +691,41 @@ ERROR: Coverage for statements (83.33%) does not meet global threshold (100%) exports[`c8 mergeAsync check-coverage exits with 0 if coverage within threshold 1`] = `",,"`; +exports[`c8 mergeAsync check-coverage exits with 0 if there is no coverage data but the threshold is 0 1`] = ` +",----------|---------|----------|---------|---------|------------------- +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s +----------|---------|----------|---------|---------|------------------- +All files | 0 | 0 | 0 | 0 | +----------|---------|----------|---------|---------|------------------- +," +`; + exports[`c8 mergeAsync check-coverage exits with 1 if coverage is below threshold 1`] = ` ",,ERROR: Coverage for lines (83.33%) does not meet global threshold (101%) ERROR: Coverage for statements (83.33%) does not meet global threshold (95%) " `; +exports[`c8 mergeAsync check-coverage exits with 1 if there is no coverage data to check 1`] = ` +",----------|---------|----------|---------|---------|------------------- +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s +----------|---------|----------|---------|---------|------------------- +All files | 0 | 0 | 0 | 0 | +----------|---------|----------|---------|---------|------------------- +,ERROR: Coverage for lines (Unknown%) does not meet global threshold (100%) +" +`; + +exports[`c8 mergeAsync check-coverage exits with 1 if there is no coverage data to check per file 1`] = ` +",----------|---------|----------|---------|---------|------------------- +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s +----------|---------|----------|---------|---------|------------------- +All files | 0 | 0 | 0 | 0 | +----------|---------|----------|---------|---------|------------------- +,ERROR: Coverage for lines (Unknown%) does not meet global threshold (100%) +" +`; + exports[`c8 mergeAsync cobertura report escapes special characters 1`] = ` "