Skip to content

Commit

Permalink
feat(thresholds): allow per file enforcement of threshold reporting
Browse files Browse the repository at this point in the history
Closes #12
  • Loading branch information
Matt Lewis committed Apr 15, 2017
1 parent 7f1cc4d commit f6d71b3
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 30 deletions.
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,18 @@ module.exports = function (config) {
// enforce percentage thresholds
// anything under these percentages will cause karma to fail with an exit code of 1 if not running in watch mode
thresholds: {
statements: 100,
lines: 100,
branches: 100,
functions: 100
global: { // thresholds for all files
statements: 100,
lines: 100,
branches: 100,
functions: 100
},
each: { // thresholds per file
statements: 100,
lines: 100,
branches: 100,
functions: 100
}
}

}
Expand Down
79 changes: 64 additions & 15 deletions src/reporter.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
'use strict';

const istanbul = require('istanbul-api');
const fixWebpackSourcePaths = require('./util').fixWebpackSourcePaths;
const util = require('./util');

const BROWSER_PLACEHOLDER = '%browser%';

function checkThresholds(thresholds, summary) {
const failedTypes = [];

Object.keys(thresholds).forEach(key => {
const coverage = summary[key].pct;
if (coverage < thresholds[key]) {
failedTypes.push(key);
}
});

return failedTypes;
}

function CoverageIstanbulReporter(baseReporterDecorator, logger, config) {
baseReporterDecorator(this);

Expand Down Expand Up @@ -48,7 +61,7 @@ function CoverageIstanbulReporter(baseReporterDecorator, logger, config) {
Object.keys(coverage).forEach(filename => {
const fileCoverage = coverage[filename];
if (fileCoverage.inputSourceMap && coverageIstanbulReporter.fixWebpackSourcePaths) {
fileCoverage.inputSourceMap = fixWebpackSourcePaths(fileCoverage.inputSourceMap);
fileCoverage.inputSourceMap = util.fixWebpackSourcePaths(fileCoverage.inputSourceMap);
}
if (
coverageIstanbulReporter.skipFilesWithNoCoverage &&
Expand All @@ -68,21 +81,57 @@ function CoverageIstanbulReporter(baseReporterDecorator, logger, config) {

reporter.write(remappedCoverageMap);

const thresholds = coverageIstanbulReporter.thresholds;
if (thresholds) {
// Adapted from https://github.com/istanbuljs/nyc/blob/98ebdff573be91e1098bb7259776a9082a5c1ce1/index.js#L463-L478
let thresholdCheckFailed = false;
const summary = remappedCoverageMap.getCoverageSummary();
Object.keys(thresholds).forEach(key => {
const coverage = summary[key].pct;
if (coverage < thresholds[key]) {
thresholdCheckFailed = true;
log.error(`Coverage for ${key} (${coverage}%) does not meet global threshold (${thresholds[key]}%)`);
const thresholds = {
global: {
statements: 0,
lines: 0,
branches: 0,
functions: 0
},
each: {
statements: 0,
lines: 0,
branches: 0,
functions: 0
}
};

const userThresholds = coverageIstanbulReporter.thresholds;

if (userThresholds) {
if (userThresholds.global || userThresholds.each) {
Object.assign(thresholds.global, userThresholds.global);
Object.assign(thresholds.each, userThresholds.each);
} else {
Object.assign(thresholds.global, userThresholds);
}
}

let thresholdCheckFailed = false;

// Adapted from https://github.com/istanbuljs/nyc/blob/98ebdff573be91e1098bb7259776a9082a5c1ce1/index.js#L463-L478
const globalSummary = remappedCoverageMap.getCoverageSummary();
const failedGlobalTypes = checkThresholds(thresholds.global, globalSummary);
failedGlobalTypes.forEach(type => {
thresholdCheckFailed = true;
log.error(`Coverage for ${type} (${globalSummary[type].pct}%) does not meet global threshold (${thresholds.global[type]}%)`);
});

remappedCoverageMap.files().forEach(file => {
const fileSummary = remappedCoverageMap.fileCoverageFor(file).toSummary().data;
const failedFileTypes = checkThresholds(thresholds.each, fileSummary);

failedFileTypes.forEach(type => {
thresholdCheckFailed = true;
if (coverageIstanbulReporter.fixWebpackSourcePaths) {
file = util.fixWebpackFilePath(file);
}
log.error(`Coverage for ${type} (${fileSummary[type].pct}%) in file ${file} does not meet per file threshold (${thresholds.each[type]}%)`);
});
if (thresholdCheckFailed && results) {
results.exitCode = 1;
}
});

if (thresholdCheckFailed && results) {
results.exitCode = 1;
}
});
};
Expand Down
31 changes: 20 additions & 11 deletions src/util.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
function fixWebpackSourcePaths(sourceMap) {
function fixWebpackFilePath(filePath) {
const isWin = process.platform.startsWith('win');

if (filePath.indexOf('!') !== -1) {
filePath = filePath.split('!').pop();
}

if (filePath.indexOf('?') !== -1) {
filePath = filePath.split('?')[0];
}

// Workaround for https://github.com/mattlewis92/karma-coverage-istanbul-reporter/issues/9
if (isWin) {
filePath = filePath.replace(/\\/g, '/');
}

return filePath;
}

function fixWebpackSourcePaths(sourceMap) {
return Object.assign({}, sourceMap, {
sources: sourceMap.sources.map(source => {
if (source.indexOf('!') !== -1) {
source = source.split('!').pop();
}
if (source.indexOf('?') !== -1) {
source = source.split('?')[0];
}
// Workaround for https://github.com/mattlewis92/karma-coverage-istanbul-reporter/issues/9
if (isWin) {
source = source.replace(/\\/g, '/');
}
source = fixWebpackFilePath(source);
if (sourceMap.sourceRoot && source.startsWith(sourceMap.sourceRoot)) {
source = source.replace(sourceMap.sourceRoot, '');
}
Expand All @@ -22,3 +30,4 @@ function fixWebpackSourcePaths(sourceMap) {
}

module.exports.fixWebpackSourcePaths = fixWebpackSourcePaths;
module.exports.fixWebpackFilePath = fixWebpackFilePath;
43 changes: 43 additions & 0 deletions test/reporter.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,5 +187,48 @@ describe('karma-coverage-istanbul-reporter', () => {
setTimeout(checkOutput, fileReadTimeout); // Hacky workaround to make sure the output file has been written
});
});

it('should enforce per file thresholds', done => {
const server = createServer({
coverageIstanbulReporter: {
reports: ['json-summary'],
dir: path.join(__dirname, 'fixtures', 'outputs'),
fixWebpackSourcePaths: true,
thresholds: {
global: {
statements: 50,
lines: 50,
branches: 50,
functions: 50
},
each: {
statements: 80,
lines: 80,
branches: 80,
functions: 60
}
}
}
});
server.start();

function checkOutput() {
const output = fs.readFileSync(OUTPUT_LOG_FILE).toString();
expect(output).not.to.contain('[ERROR] reporter.coverage-istanbul - Coverage for statements (81.82%) does not meet global threshold (50%)');
expect(output).not.to.contain('[ERROR] reporter.coverage-istanbul - Coverage for lines (81.82%) does not meet global threshold (50%)');
expect(output).not.to.contain('[ERROR] reporter.coverage-istanbul - Coverage for functions (60%) does not meet global threshold (50%)');
expect(Boolean(output.match(/\[ERROR\] reporter\.coverage-istanbul - Coverage for statements \(85\.71%\) in file \/.+test\/fixtures\/typescript\/src\/example\.ts does not meet per file threshold \(80%\)/))).to.equal(false);
expect(Boolean(output.match(/\[ERROR\] reporter\.coverage-istanbul - Coverage for lines \(85\.71%\) in file \/.+test\/fixtures\/typescript\/src\/example\.ts does not meet per file threshold \(80%\)/))).to.equal(false);
expect(Boolean(output.match(/\[ERROR\] reporter\.coverage-istanbul - Coverage for functions \(66\.67%\) in file \/.+test\/fixtures\/typescript\/src\/example\.ts does not meet per file threshold \(60%\)/))).to.equal(false);
expect(Boolean(output.match(/\[ERROR\] reporter\.coverage-istanbul - Coverage for statements \(75%\) in file \/.+test\/fixtures\/typescript\/src\/another-file\.ts does not meet per file threshold \(80%\)/))).not.to.equal(false);
expect(Boolean(output.match(/\[ERROR\] reporter\.coverage-istanbul - Coverage for lines \(75%\) in file \/.+test\/fixtures\/typescript\/src\/another-file\.ts does not meet per file threshold \(80%\)/))).not.to.equal(false);
expect(Boolean(output.match(/\[ERROR\] reporter\.coverage-istanbul - Coverage for functions \(50%\) in file \/.+test\/fixtures\/typescript\/src\/another-file\.ts does not meet per file threshold \(60%\)/))).not.to.equal(false);
done();
}

server.on('run_complete', () => {
setTimeout(checkOutput, fileReadTimeout); // Hacky workaround to make sure the output file has been written
});
});
});
});

0 comments on commit f6d71b3

Please sign in to comment.