Skip to content

Commit

Permalink
feat: add combineBrowserReports option
Browse files Browse the repository at this point in the history
Closes #34
  • Loading branch information
bryanforbes authored and mattlewis92 committed Jan 23, 2018
1 parent 6a98e7e commit 2ae16ee
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 109 deletions.
10 changes: 7 additions & 3 deletions README.md
Expand Up @@ -38,13 +38,17 @@ module.exports = function (config) {
// any of these options are valid: https://github.com/istanbuljs/istanbuljs/blob/aae256fb8b9a3d19414dcf069c592e88712c32c6/packages/istanbul-api/lib/config.js#L33-L39
coverageIstanbulReporter: {

// reports can be any that are listed here: https://github.com/istanbuljs/istanbuljs/tree/aae256fb8b9a3d19414dcf069c592e88712c32c6/packages/istanbul-reports/lib
// reports can be any that are listed here: https://github.com/istanbuljs/istanbuljs/tree/aae256fb8b9a3d19414dcf069c592e88712c32c6/packages/istanbul-reports/lib
reports: ['html', 'lcovonly', 'text-summary'],

// base output directory. If you include %browser% in the path it will be replaced with the karma browser name
// base output directory. If you include %browser% in the path it will be replaced with the karma browser name
dir: path.join(__dirname, 'coverage'),

// if using webpack and pre-loaders, work around webpack breaking the source path
// Combines coverage information from multiple browsers into one report rather than outputting a report
// for each browser.
combineBrowserReports: true,

// if using webpack and pre-loaders, work around webpack breaking the source path
fixWebpackSourcePaths: true,

// stop istanbul outputting messages like `File [${filename}] ignored, nothing could be mapped`
Expand Down
229 changes: 123 additions & 106 deletions src/reporter.js
Expand Up @@ -25,130 +25,147 @@ function CoverageIstanbulReporter(baseReporterDecorator, logger, config) {

const browserCoverage = new WeakMap();

this.onBrowserComplete = function (browser, result) {
if (result && result.coverage) {
browserCoverage.set(browser, result.coverage);
}
};
function addCoverage(coverageIstanbulReporter, coverageMap, browser) {
const coverage = browserCoverage.get(browser);
browserCoverage.delete(browser);

const baseReporterOnRunComplete = this.onRunComplete;
this.onRunComplete = function (browsers, results) {
baseReporterOnRunComplete.apply(this, arguments);
if (!coverage) {
return;
}

browsers.forEach(browser => {
const coverageIstanbulReporter = Object.assign({}, config.coverageIstanbulReporter);
if (coverageIstanbulReporter.dir) {
coverageIstanbulReporter.dir = coverageIstanbulReporter.dir.replace(BROWSER_PLACEHOLDER, browser.name);
Object.keys(coverage).forEach(filename => {
const fileCoverage = coverage[filename];
if (fileCoverage.inputSourceMap && coverageIstanbulReporter.fixWebpackSourcePaths) {
fileCoverage.inputSourceMap = util.fixWebpackSourcePaths(fileCoverage.inputSourceMap, config.webpack);
}
const reportConfig = istanbul.config.loadObject({
reporting: coverageIstanbulReporter
});
const reportTypes = reportConfig.reporting.config.reports;
if (
coverageIstanbulReporter.skipFilesWithNoCoverage &&
Object.keys(fileCoverage.statementMap).length === 0 &&
Object.keys(fileCoverage.fnMap).length === 0 &&
Object.keys(fileCoverage.branchMap).length === 0
) {
log.debug(`File [${filename}] ignored, nothing could be mapped`);
} else {
coverageMap.addFileCoverage(fileCoverage);
}
});
}

const coverage = browserCoverage.get(browser);
browserCoverage.delete(browser);
function logThresholdMessage(thresholds, message) {
if (thresholds.emitWarning) {
log.warn(message);
} else {
log.error(message);
}
}

if (!coverage) {
return;
}
function createReport(browserOrBrowsers, results) {
const coverageIstanbulReporter = Object.assign({}, config.coverageIstanbulReporter);

const reporter = istanbul.createReporter(reportConfig);
reporter.addAll(reportTypes);
if (!coverageIstanbulReporter.combineBrowserReports && coverageIstanbulReporter.dir) {
coverageIstanbulReporter.dir = coverageIstanbulReporter.dir.replace(BROWSER_PLACEHOLDER, browserOrBrowsers.name);
}

const coverageMap = istanbul.libCoverage.createCoverageMap();
const sourceMapStore = istanbul.libSourceMaps.createSourceMapStore();
const reportConfig = istanbul.config.loadObject({
reporting: coverageIstanbulReporter
});
const reportTypes = reportConfig.reporting.config.reports;

Object.keys(coverage).forEach(filename => {
const fileCoverage = coverage[filename];
if (fileCoverage.inputSourceMap && coverageIstanbulReporter.fixWebpackSourcePaths) {
fileCoverage.inputSourceMap = util.fixWebpackSourcePaths(fileCoverage.inputSourceMap, config.webpack);
}
if (
coverageIstanbulReporter.skipFilesWithNoCoverage &&
Object.keys(fileCoverage.statementMap).length === 0 &&
Object.keys(fileCoverage.fnMap).length === 0 &&
Object.keys(fileCoverage.branchMap).length === 0
) {
log.debug(`File [${filename}] ignored, nothing could be mapped`);
} else {
coverageMap.addFileCoverage(fileCoverage);
}
});
const reporter = istanbul.createReporter(reportConfig);
reporter.addAll(reportTypes);

const remappedCoverageMap = sourceMapStore.transformCoverage(coverageMap).map;

log.debug('Writing coverage reports:', reportTypes);

reporter.write(remappedCoverageMap);

const thresholds = {
emitWarning: false,
global: {
statements: 0,
lines: 0,
branches: 0,
functions: 0
},
each: {
statements: 0,
lines: 0,
branches: 0,
functions: 0,
overrides: {}
}
};

const userThresholds = coverageIstanbulReporter.thresholds;

if (userThresholds) {
if (userThresholds.global || userThresholds.each) {
Object.assign(thresholds.global, userThresholds.global);
Object.assign(thresholds.each, userThresholds.each);
if (userThresholds.emitWarning === true) {
thresholds.emitWarning = true;
}
} else {
Object.assign(thresholds.global, userThresholds);
}
}
const coverageMap = istanbul.libCoverage.createCoverageMap();
const sourceMapStore = istanbul.libSourceMaps.createSourceMapStore();

if (coverageIstanbulReporter.combineBrowserReports) {
browserOrBrowsers.forEach(addCoverage.bind(null, coverageIstanbulReporter, coverageMap));
} else {
addCoverage(coverageIstanbulReporter, coverageMap, browserOrBrowsers);
}

function logThresholdMessage(message) {
if (thresholds.emitWarning) {
log.warn(message);
} else {
log.error(message);
const remappedCoverageMap = sourceMapStore.transformCoverage(coverageMap).map;

log.debug('Writing coverage reports:', reportTypes);
reporter.write(remappedCoverageMap);

const userThresholds = coverageIstanbulReporter.thresholds;

const thresholds = {
emitWarning: false,
global: {
statements: 0,
lines: 0,
branches: 0,
functions: 0
},
each: {
statements: 0,
lines: 0,
branches: 0,
functions: 0,
overrides: {}
}
};

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

let thresholdCheckFailed = false;

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;
logThresholdMessage(thresholds, `Coverage for ${type} (${globalSummary[type].pct}%) does not meet global threshold (${thresholds.global[type]}%)`);
});

remappedCoverageMap.files().forEach(file => {
const fileThresholds = Object.assign({}, thresholds.each, util.overrideThresholds(file, thresholds.each.overrides, config.basePath));
delete fileThresholds.overrides;
const fileSummary = remappedCoverageMap.fileCoverageFor(file).toSummary().data;
const failedFileTypes = checkThresholds(fileThresholds, fileSummary);

// 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 => {
failedFileTypes.forEach(type => {
thresholdCheckFailed = true;
logThresholdMessage(`Coverage for ${type} (${globalSummary[type].pct}%) does not meet global threshold (${thresholds.global[type]}%)`);
if (coverageIstanbulReporter.fixWebpackSourcePaths) {
file = util.fixWebpackFilePath(file);
}
logThresholdMessage(thresholds, `Coverage for ${type} (${fileSummary[type].pct}%) in file ${file} does not meet per file threshold (${fileThresholds[type]}%)`);
});
});

remappedCoverageMap.files().forEach(file => {
const fileThresholds = Object.assign({}, thresholds.each, util.overrideThresholds(file, thresholds.each.overrides, config.basePath));
delete fileThresholds.overrides;
const fileSummary = remappedCoverageMap.fileCoverageFor(file).toSummary().data;
const failedFileTypes = checkThresholds(fileThresholds, fileSummary);

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

if (thresholdCheckFailed && results && !thresholds.emitWarning) {
results.exitCode = 1;
}
});
this.onBrowserComplete = function (browser, result) {
if (result && result.coverage) {
browserCoverage.set(browser, result.coverage);
}
};

const baseReporterOnRunComplete = this.onRunComplete;
this.onRunComplete = function (browsers, results) {
baseReporterOnRunComplete.apply(this, arguments);

if (config.coverageIstanbulReporter.combineBrowserReports) {
createReport(browsers, results);
} else {
browsers.forEach(browser => {
createReport(browser, results);
});
}
};
}

Expand Down
43 changes: 43 additions & 0 deletions test/reporter.spec.js
Expand Up @@ -106,6 +106,49 @@ describe('karma-coverage-istanbul-reporter', () => {
});
});

it('should create a combined browser report', done => {
const server = createServer({
coverageIstanbulReporter: {
reports: ['json-summary'],
dir: path.join(__dirname, 'fixtures', 'outputs'),
combineBrowserReports: true
}
});
server.start();
server.on('run_complete', () => {
setTimeout(() => { // Hacky workaround to make sure the file has been written
const summary = JSON.parse(fs.readFileSync(OUTPUT_FILE));
expect(summary.total).to.deep.equal({
lines: {
total: 11,
covered: 9,
skipped: 0,
pct: 81.82
},
statements: {
total: 11,
covered: 9,
skipped: 0,
pct: 81.82
},
functions: {
total: 5,
covered: 3,
skipped: 0,
pct: 60
},
branches: {
total: 0,
covered: 0,
skipped: 0,
pct: 100
}
});
done();
}, fileReadTimeout);
});
});

it('should not map files with no coverage', done => {
const server = createServer({
files: [
Expand Down

0 comments on commit 2ae16ee

Please sign in to comment.