From 2ae16ee0e7541d6b2f882dd2d42f97f6baac82e2 Mon Sep 17 00:00:00 2001 From: Bryan Forbes Date: Tue, 23 Jan 2018 10:31:43 -0600 Subject: [PATCH] feat: add combineBrowserReports option Closes #34 --- README.md | 10 +- src/reporter.js | 229 +++++++++++++++++++++++------------------- test/reporter.spec.js | 43 ++++++++ 3 files changed, 173 insertions(+), 109 deletions(-) diff --git a/README.md b/README.md index ab6777b..f0b43da 100644 --- a/README.md +++ b/README.md @@ -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` diff --git a/src/reporter.js b/src/reporter.js index 6103399..0b50896 100644 --- a/src/reporter.js +++ b/src/reporter.js @@ -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); + }); + } }; } diff --git a/test/reporter.spec.js b/test/reporter.spec.js index d4e7f34..521bfde 100644 --- a/test/reporter.spec.js +++ b/test/reporter.spec.js @@ -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: [