From ef5436e5796026ba865a90a130b5f89237d8c591 Mon Sep 17 00:00:00 2001 From: Srinivas Dhanwada Date: Tue, 14 May 2019 20:37:12 -0500 Subject: [PATCH 1/9] chore: Add Updated Istanbul Dependencies The istanbul package is deprecated in favor several split packages that control different aspects of how istanbul works. This commit adds the recommended packages that will be used in future commits as karma-coverage's usage of istanbul is updated to the latest api. --- package.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/package.json b/package.json index a095d0e..c403a19 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,11 @@ "dependencies": { "dateformat": "^1.0.6", "istanbul": "^0.4.0", + "istanbul-lib-coverage": "^2.0.5", + "istanbul-lib-instrument": "^3.3.0", + "istanbul-lib-report": "^2.0.8", + "istanbul-lib-source-maps": "^3.0.6", + "istanbul-reports": "^2.2.4", "lodash": "^4.17.11", "minimatch": "^3.0.0", "source-map": "^0.5.1" From c30a8dbecb2a61e228fe0ad807151e930388d0b5 Mon Sep 17 00:00:00 2001 From: Srinivas Dhanwada Date: Tue, 14 May 2019 22:13:27 -0500 Subject: [PATCH 2/9] refactor(reporter): Follow new report API This commit refactors the in memory report implementation to use the new istanbul report API. Report creation is removed from newer versions of the istanbul API, so this commit adds a set of utility functions to wrap around the new API and provide similar functionality as the old API. The top level export uses the new utility function to register the in-memory report. --- lib/in-memory-report.js | 17 +++++++++++------ lib/index.js | 2 +- lib/report-creator.js | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 lib/report-creator.js diff --git a/lib/in-memory-report.js b/lib/in-memory-report.js index f938c6a..0fac999 100644 --- a/lib/in-memory-report.js +++ b/lib/in-memory-report.js @@ -1,17 +1,22 @@ -var Report = require('istanbul').Report -var util = require('util') - function InMemoryReport (opt) { this.opt = opt } -util.inherits(InMemoryReport, Report) +InMemoryReport.prototype.onStart = function (root, context) { + this.data = {} +} + +InMemoryReport.prototype.onDetail = function (node) { + const fc = node.getFileCoverage() + const key = fc.path + this.data[key] = fc.toJSON() +} -InMemoryReport.prototype.writeReport = function (collector, sync) { +InMemoryReport.prototype.onEnd = function () { if (!this.opt.emitter || !this.opt.emitter.emit) { console.error('Could not raise "coverage_complete" event, missing emitter because it was not supplied during initialization of the reporter') } else { - this.opt.emitter.emit('coverage_complete', this.opt.browser, collector.getFinalCoverage()) + this.opt.emitter.emit('coverage_complete', this.opt.browser, this.data) } } diff --git a/lib/index.js b/lib/index.js index 2f34cc6..ff9df46 100644 --- a/lib/index.js +++ b/lib/index.js @@ -5,7 +5,7 @@ // Exposes the preprocessor and reporter plugins. // Registering one additional (karma specific) reporter: in-memory -require('istanbul').Report.register(require('./in-memory-report')) +require('./report-creator').register(require('./in-memory-report')) module.exports = { 'preprocessor:coverage': ['factory', require('./preprocessor')], diff --git a/lib/report-creator.js b/lib/report-creator.js new file mode 100644 index 0000000..8085af2 --- /dev/null +++ b/lib/report-creator.js @@ -0,0 +1,36 @@ +// Report Creator +// ============== +// +// Wrapper of Istanbul's report creator to allow registering +// custom reporters + +// Dependencies +// ------------ +var istanbulReports = require('istanbul-reports') + +var customReporterMap = {} + +function register (reporter) { + var registeredType = reporter.TYPE + if (!registeredType) { + throw new Error('Registering a custom reporter requires a type!') + } + + customReporterMap[registeredType] = reporter + return registeredType +} + +function create (type, opts) { + var Reporter = customReporterMap[type] + if (Reporter) { + return new Reporter(opts) + } + + // fallback to istanbul's report creator if reporter isn't found + return istanbulReports.create(type, opts) +} + +module.exports = { + create: create, + register: register +} From 7d71d85267aefd9cbc83382ae99c1da853ddd445 Mon Sep 17 00:00:00 2001 From: Srinivas Dhanwada Date: Tue, 14 May 2019 22:24:37 -0500 Subject: [PATCH 3/9] refactor(preprocessor): Switch to istanbul-lib-instrument This commit updates the preprocessor to use istanbul-lib-instrument instead of the deprecated istanbul package. The biggest change in this refactor is using a callable function instead of a constructor when creating instrumenters The old istanbul package exposed the Instrumenter directly, allowing the preprocessor to create an instance of it. istanbul-lib-instrument, however, exposes a callable function that creates an Instrumenter. This commit updates the preprocessor to follow this new pattern of using a callable function. In order to ensure backwards compatibility, a utility function is added to wrap constructors with a callable function for creation automatically. This change allows the following configuration for creating instrumenters: 1. An object that contains an Instrumenter constructor 2. An Instrumenter constructor itself 3. A callable function that returns an Instrumenter instance. This commit also uses the istanbul-lib-source-maps package to handle storing source maps. A global source map store registers source maps so they can be used later on in the reporter. --- lib/preprocessor.js | 63 +++++++++++++++++++++++++++++++++-------- lib/source-map-store.js | 14 +++++++++ 2 files changed, 65 insertions(+), 12 deletions(-) create mode 100644 lib/source-map-store.js diff --git a/lib/preprocessor.js b/lib/preprocessor.js index 3bee536..b80cfbb 100644 --- a/lib/preprocessor.js +++ b/lib/preprocessor.js @@ -6,14 +6,14 @@ // Dependencies // ------------ -var istanbul = require('istanbul') +var { createInstrumenter } = require('istanbul-lib-instrument') var minimatch = require('minimatch') var path = require('path') var _ = require('lodash') var SourceMapConsumer = require('source-map').SourceMapConsumer var SourceMapGenerator = require('source-map').SourceMapGenerator -var globalSourceCache = require('./source-cache') -var coverageMap = require('./coverage-map') +var globalSourceMapStore = require('./source-map-store') +var globalCoverageMap = require('./coverage-map') // Regexes // ------- @@ -27,21 +27,60 @@ function createCoveragePreprocessor (logger, helper, basePath, reporters, covera // Options // ------- + function isConstructor (Func) { + try { + // eslint-disable-next-line + new Func() + } catch (err) { + // error message should be of the form: "TypeError: func is not a constructor" + // test for this type of message to ensure we failed due to the function not being + // constructable + if (/TypeError.*constructor/.test(err.message)) { + return false + } + } + return true + } + + function getCreatorFunction (Obj) { + if (Obj.Instrumenter) { + return function (opts) { + return new Obj.Instrumenter(opts) + } + } + if (!_.isFunction(Obj)) { + // Object doesn't have old instrumenter variable and isn't a + // constructor, so we can't use it to create an instrumenter + return null + } + if (isConstructor(Obj)) { + return function (opts) { + return new Obj(opts) + } + } + return Obj + } + var instrumenterOverrides = {} - var instrumenters = { istanbul: istanbul } + var instrumenters = { istanbul: createInstrumenter } var includeAllSources = false var useJSExtensionForCoffeeScript = false if (coverageReporter) { instrumenterOverrides = coverageReporter.instrumenter - instrumenters = Object.assign({}, { istanbul: istanbul }, coverageReporter.instrumenters) + _.forEach(coverageReporter.instrumenters, function (instrumenter, literal) { + var creatorFunction = getCreatorFunction(instrumenter) + if (creatorFunction) { + instrumenters[literal] = creatorFunction + } + }) includeAllSources = coverageReporter.includeAllSources === true useJSExtensionForCoffeeScript = coverageReporter.useJSExtensionForCoffeeScript === true } - var sourceCache = globalSourceCache.get(basePath) + var sourceMapStore = globalSourceMapStore.get(basePath) - var instrumentersOptions = _.reduce(instrumenters, function getInstumenterOptions (memo, instrument, name) { + var instrumentersOptions = _.reduce(instrumenters, function getInstrumenterOptions (memo, instrument, name) { memo[name] = {} if (coverageReporter && coverageReporter.instrumenterOptions) { @@ -88,7 +127,7 @@ function createCoveragePreprocessor (logger, helper, basePath, reporters, covera } }) - var InstrumenterConstructor = instrumenters[instrumenterLiteral].Instrumenter + var instrumenterCreator = instrumenters[instrumenterLiteral] var constructOptions = instrumentersOptions[instrumenterLiteral] || {} var codeGenerationOptions = null @@ -107,7 +146,7 @@ function createCoveragePreprocessor (logger, helper, basePath, reporters, covera var options = Object.assign({}, constructOptions) options = Object.assign({}, options, { codeGenerationOptions: codeGenerationOptions }) - var instrumenter = new InstrumenterConstructor(options) + var instrumenter = instrumenterCreator(options) instrumenter.instrument(content, jsPath, function (err, instrumentedCode) { if (err) { log.error('%s\n at %s', err.message, file.originalPath) @@ -122,8 +161,8 @@ function createCoveragePreprocessor (logger, helper, basePath, reporters, covera instrumentedCode += Buffer.from(JSON.stringify(file.sourceMap)).toString('base64') + '\n' } - // remember the actual immediate instrumented JS for given original path - sourceCache[jsPath] = content + // Register the sourceMap for transformation during reporting + sourceMapStore.registerMap(jsPath, file.sourceMap) if (includeAllSources) { // reset stateful regex @@ -134,7 +173,7 @@ function createCoveragePreprocessor (logger, helper, basePath, reporters, covera if (coverageObjMatch !== null) { var coverageObj = JSON.parse(coverageObjMatch[0]) - coverageMap.add(coverageObj) + globalCoverageMap.add(coverageObj) } } diff --git a/lib/source-map-store.js b/lib/source-map-store.js new file mode 100644 index 0000000..8b1b1c4 --- /dev/null +++ b/lib/source-map-store.js @@ -0,0 +1,14 @@ +var istanbulLibSourceMaps = require('istanbul-lib-source-maps') + +var cache = {} + +function get (basePath, opts) { + if (!cache[basePath]) { + cache[basePath] = istanbulLibSourceMaps.createSourceMapStore(opts) + } + return cache[basePath] +} + +module.exports = { + get: get +} From d28674ad3a1bf8b88e57b4bafdbf878018a9fe8d Mon Sep 17 00:00:00 2001 From: Srinivas Dhanwada Date: Tue, 14 May 2019 22:52:32 -0500 Subject: [PATCH 4/9] refactor(reporter): Switch to istanbul-lib-coverage This commit updates the reporter by using the istanbul-lib-coverage package api for handling coverage checking/management and the istanbul-lib-report package api for handling reporting. The new apis remove the need for collectors and remove the need to handle disposing collectors. --- lib/reporter.js | 158 ++++++++++++++++++++---------------------------- 1 file changed, 66 insertions(+), 92 deletions(-) diff --git a/lib/reporter.js b/lib/reporter.js index 72980f7..e43a2e6 100644 --- a/lib/reporter.js +++ b/lib/reporter.js @@ -11,13 +11,14 @@ // ------------ var path = require('path') -var istanbul = require('istanbul') +var istanbulLibCoverage = require('istanbul-lib-coverage') +var istanbulLibReport = require('istanbul-lib-report') var minimatch = require('minimatch') var _ = require('lodash') -var globalSourceCache = require('./source-cache') -var coverageMap = require('./coverage-map') -var SourceCacheStore = require('./source-cache-store') +var globalSourceMapStore = require('./source-map-store') +var globalCoverageMap = require('./coverage-map') +var reports = require('./report-creator') function isAbsolute (file) { if (path.isAbsolute) { @@ -42,46 +43,18 @@ var CoverageReporter = function (rootConfig, helper, logger, emitter) { var config = rootConfig.coverageReporter || {} var basePath = rootConfig.basePath var reporters = config.reporters - var sourceCache = globalSourceCache.get(basePath) + var sourceMapStore = globalSourceMapStore.get(basePath) var includeAllSources = config.includeAllSources === true if (config.watermarks) { - config.watermarks = helper.merge({}, istanbul.config.defaultConfig().reporting.watermarks, config.watermarks) + config.watermarks = helper.merge({}, istanbulLibReport.getDefaultWatermarks(), config.watermarks) } if (!helper.isDefined(reporters)) { reporters = [config] } - var collectors - var pendingFileWritings = 0 - var fileWritingFinished = function () {} - - function writeReport (reporter, collector) { - try { - if (typeof config._onWriteReport === 'function') { - var newCollector = config._onWriteReport(collector) - if (typeof newCollector === 'object') { - collector = newCollector - } - } - reporter.writeReport(collector, true) - } catch (e) { - log.error(e) - } - - --pendingFileWritings - } - - function disposeCollectors () { - if (pendingFileWritings <= 0) { - _.forEach(collectors, function (collector) { - collector.dispose() - }) - - fileWritingFinished() - } - } + var coverageMaps function normalize (key) { // Exclude keys will always be relative, but covObj keys can be absolute or relative @@ -92,10 +65,10 @@ var CoverageReporter = function (rootConfig, helper, logger, emitter) { return excludeKey } - function removeFiles (covObj, patterns) { - var obj = {} + function getTrackedFiles (coverageMap, patterns) { + var files = [] - Object.keys(covObj).forEach(function (key) { + coverageMap.files().forEach(function (key) { // Do any patterns match the resolved key var found = patterns.some(function (pattern) { return minimatch(normalize(key), pattern, { dot: true }) @@ -103,11 +76,11 @@ var CoverageReporter = function (rootConfig, helper, logger, emitter) { // if no patterns match, keep the key if (!found) { - obj[key] = covObj[key] + files.push(key) } }) - return obj + return files } function overrideThresholds (key, overrides) { @@ -124,7 +97,7 @@ var CoverageReporter = function (rootConfig, helper, logger, emitter) { return thresholds } - function checkCoverage (browser, collector) { + function checkCoverage (browser, coverageMap) { var defaultThresholds = { global: { statements: 0, @@ -145,13 +118,19 @@ var CoverageReporter = function (rootConfig, helper, logger, emitter) { var thresholds = helper.merge({}, defaultThresholds, config.check) - var rawCoverage = collector.getFinalCoverage() - var globalResults = istanbul.utils.summarizeCoverage(removeFiles(rawCoverage, thresholds.global.excludes)) - var eachResults = removeFiles(rawCoverage, thresholds.each.excludes) - - // Summarize per-file results and mutate original results. - Object.keys(eachResults).forEach(function (key) { - eachResults[key] = istanbul.utils.summarizeFileCoverage(eachResults[key]) + var globalTrackedFiles = getTrackedFiles(coverageMap, thresholds.global.excludes) + var eachTrackedFiles = getTrackedFiles(coverageMap, thresholds.each.excludes) + var globalResults = istanbulLibCoverage.createCoverageSummary() + var eachResults = istanbulLibCoverage.createCoverageSummary() + globalTrackedFiles.forEach(function (f) { + var fileCoverage = coverageMap.fileCoverageFor(f) + var summary = fileCoverage.toSummary() + globalResults.merge(summary) + }) + eachTrackedFiles.forEach(function (f) { + var fileCoverage = coverageMap.fileCoverageFor(f) + var summary = fileCoverage.toSummary() + eachResults.merge(summary) }) var coverageFailed = false @@ -209,7 +188,7 @@ var CoverageReporter = function (rootConfig, helper, logger, emitter) { } this.onRunStart = function (browsers) { - collectors = Object.create(null) + coverageMaps = Object.create(null) // TODO(vojta): remove once we don't care about Karma 0.10 if (browsers) { @@ -218,26 +197,30 @@ var CoverageReporter = function (rootConfig, helper, logger, emitter) { } this.onBrowserStart = function (browser) { - collectors[browser.id] = new istanbul.Collector() - - if (!includeAllSources) return + var startingMap = {} + if (includeAllSources) { + startingMap = globalCoverageMap.get() + } - collectors[browser.id].add(coverageMap.get()) + coverageMaps[browser.id] = istanbulLibCoverage.createCoverageMap(startingMap) } this.onBrowserComplete = function (browser, result) { - var collector = collectors[browser.id] + var coverageMap = coverageMaps[browser.id] - if (!collector) return + if (!coverageMap) return if (!result || !result.coverage) return - collector.add(result.coverage) + coverageMap.merge(result.coverage) } this.onSpecComplete = function (browser, result) { + var coverageMap = coverageMaps[browser.id] + + if (!coverageMap) return if (!result.coverage) return - collectors[browser.id].add(result.coverage) + coverageMap.merge(result.coverage) } this.onRunComplete = function (browsers, results) { @@ -245,25 +228,12 @@ var CoverageReporter = function (rootConfig, helper, logger, emitter) { reporters.forEach(function (reporterConfig) { browsers.forEach(function (browser) { - var collector = collectors[browser.id] + var coverageMap = coverageMaps[browser.id] - if (!collector) { + if (!coverageMap) { return } - // If config.check is defined, check coverage levels for each browser - if (config.hasOwnProperty('check') && !checkedCoverage[browser.id]) { - checkedCoverage[browser.id] = true - var coverageFailed = checkCoverage(browser, collector) - if (coverageFailed) { - if (results) { - results.exitCode = 1 - } - } - } - - pendingFileWritings++ - var mainDir = reporterConfig.dir || config.dir var subDir = reporterConfig.subdir || config.subdir var browserName = browser.name.replace(':', '') @@ -271,47 +241,51 @@ var CoverageReporter = function (rootConfig, helper, logger, emitter) { var resolvedOutputDir = path.resolve(basePath, simpleOutputDir) var outputDir = helper.normalizeWinPath(resolvedOutputDir) - var sourceStore = _.isEmpty(sourceCache) ? null : new SourceCacheStore({ - sourceCache: sourceCache - }) - var options = helper.merge({ - sourceStore: sourceStore - }, config, reporterConfig, { + var options = helper.merge(config, reporterConfig, { dir: outputDir, + subdir: '', browser: browser, emitter: emitter }) - var reporter = istanbul.Report.create(reporterConfig.type || 'html', options) + var remappedCoverageMap = sourceMapStore.transformCoverage(coverageMap).map + + // If config.check is defined, check coverage levels for each browser + if (config.hasOwnProperty('check') && !checkedCoverage[browser.id]) { + checkedCoverage[browser.id] = true + var coverageFailed = checkCoverage(browser, remappedCoverageMap) + if (coverageFailed) { + if (results) { + results.exitCode = 1 + } + } + } - // If reporting to console or in-memory skip directory creation + var context = istanbulLibReport.createContext(options) + var tree = istanbulLibReport.summarizers.pkg(remappedCoverageMap) + var report = reports.create(reporterConfig.type || 'html', options) + + // // If reporting to console or in-memory skip directory creation var toDisk = !reporterConfig.type || !reporterConfig.type.match(/^(text|text-summary|in-memory)$/) var hasNoFile = _.isUndefined(reporterConfig.file) if (!toDisk && hasNoFile) { - writeReport(reporter, collector) + tree.visit(report, context) return } helper.mkdirIfNotExists(outputDir, function () { log.debug('Writing coverage to %s', outputDir) - writeReport(reporter, collector) - disposeCollectors() + tree.visit(report, context) }) }) }) - - disposeCollectors() } this.onExit = function (done) { - if (pendingFileWritings) { - fileWritingFinished = ( - typeof config._onExit === 'function' - ? (function (done) { return function () { config._onExit(done) } }(done)) - : done - ) + if (typeof config._onExit === 'function') { + config._onExit(done) } else { - (typeof config._onExit === 'function' ? config._onExit(done) : done()) + done() } } } From 0585334d43d865d6585097e769724e1f081ce65e Mon Sep 17 00:00:00 2001 From: Srinivas Dhanwada Date: Tue, 14 May 2019 22:59:07 -0500 Subject: [PATCH 5/9] refactor: Remove unused source cache utilities This commit removes the source-cache-store and source-cache files as they are no longer being used. The source-map-store and istanbul-lib-source-maps are used instead, so these files are no longer needed. --- lib/source-cache-store.js | 40 --------------------------------------- lib/source-cache.js | 16 ---------------- 2 files changed, 56 deletions(-) delete mode 100644 lib/source-cache-store.js delete mode 100644 lib/source-cache.js diff --git a/lib/source-cache-store.js b/lib/source-cache-store.js deleted file mode 100644 index 5149b45..0000000 --- a/lib/source-cache-store.js +++ /dev/null @@ -1,40 +0,0 @@ -// Source Cache Store -// ================== -// -// Used by lib/reporter - -// Dependencies -// ------------ - -var util = require('util') -var Store = require('istanbul').Store - -// Constructor -var SourceCacheStore = module.exports = function (opts) { - Store.call(this, opts) - opts = opts || {} - this.sourceCache = opts.sourceCache -} - -// Class Constants -// --------------- -SourceCacheStore.TYPE = 'sourceCacheLookup' - -// Inherits from an Istanbul.Store -util.inherits(SourceCacheStore, Store) - -// Implement needed methods -Store.mix(SourceCacheStore, { - keys: function () { - throw new Error('Not implemented') - }, - get: function (key) { - return this.sourceCache[key] - }, - hasKey: function (key) { - return this.sourceCache.hasOwnProperty(key) - }, - set: function (key, contents) { - throw new Error('Not applicable') - } -}) diff --git a/lib/source-cache.js b/lib/source-cache.js deleted file mode 100644 index 2de3e81..0000000 --- a/lib/source-cache.js +++ /dev/null @@ -1,16 +0,0 @@ -// Source Cache -// ============ - -var cache = {} - -function get (basePath) { - if (!cache[basePath]) { - cache[basePath] = {} - } - - return cache[basePath] -} - -module.exports = { - get: get -} From 0175e52737dddd4967658ae66dcb5f9c2c0071df Mon Sep 17 00:00:00 2001 From: Srinivas Dhanwada Date: Wed, 15 May 2019 17:21:17 -0500 Subject: [PATCH 6/9] feat(util): Add Reset Functionality This commit updates the report creator utility to allow resetting the custom reporter map. --- lib/report-creator.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/report-creator.js b/lib/report-creator.js index 8085af2..3bb7999 100644 --- a/lib/report-creator.js +++ b/lib/report-creator.js @@ -30,7 +30,12 @@ function create (type, opts) { return istanbulReports.create(type, opts) } +function reset () { + customReporterMap = {} +} + module.exports = { create: create, - register: register + register: register, + reset: reset } From 9a64bca0f765cfcbf49d7e15448768df74e2d33d Mon Sep 17 00:00:00 2001 From: Srinivas Dhanwada Date: Wed, 15 May 2019 23:51:13 -0500 Subject: [PATCH 7/9] fix(preprocessor): Track Coverage Maps Properly This commit updates the preprocessor to properly access file coverage when storing it in the global coverage map (when includeAllSources is true). The previous method did not work because the returned instrumented code from the default istanbul instrumenter returns the coverage map in a POJO object instead of JSON notation. This breaks the coverage regex used to match and parse the coverage map. The istanbul instrumenter offers the ability to receive the coverage map for the last instrumented file through a separate function, so that is tested for and used if it is supported. The original method is used as a fallback for backwards compatibility. This commit also addresses changes from the v0 instanbul instrumenter options. The changes are additive only to maintain backwards compatibility for other instrumenters. --- lib/preprocessor.js | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/lib/preprocessor.js b/lib/preprocessor.js index b80cfbb..5f51b46 100644 --- a/lib/preprocessor.js +++ b/lib/preprocessor.js @@ -129,7 +129,9 @@ function createCoveragePreprocessor (logger, helper, basePath, reporters, covera var instrumenterCreator = instrumenters[instrumenterLiteral] var constructOptions = instrumentersOptions[instrumenterLiteral] || {} + var options = Object.assign({}, constructOptions) var codeGenerationOptions = null + options.autoWrap = options.autoWrap || !options.noAutoWrap if (file.sourceMap) { log.debug('Enabling source map generation for "%s".', file.originalPath) @@ -141,9 +143,9 @@ function createCoveragePreprocessor (logger, helper, basePath, reporters, covera sourceMapWithCode: true, file: file.path }, constructOptions.codeGenerationOptions || {}) + options.produceSourceMap = true } - var options = Object.assign({}, constructOptions) options = Object.assign({}, options, { codeGenerationOptions: codeGenerationOptions }) var instrumenter = instrumenterCreator(options) @@ -165,15 +167,21 @@ function createCoveragePreprocessor (logger, helper, basePath, reporters, covera sourceMapStore.registerMap(jsPath, file.sourceMap) if (includeAllSources) { - // reset stateful regex - coverageObjRegex.lastIndex = 0 - - var coverageObjMatch = coverageObjRegex.exec(instrumentedCode) - - if (coverageObjMatch !== null) { - var coverageObj = JSON.parse(coverageObjMatch[0]) - + var coverageObj + // Check if the file coverage object is exposed from the instrumenter directly + if (instrumenter.lastFileCoverage) { + coverageObj = instrumenter.lastFileCoverage() globalCoverageMap.add(coverageObj) + } else { + // Attempt to match and parse coverage object from instrumented code + + // reset stateful regex + coverageObjRegex.lastIndex = 0 + var coverageObjMatch = coverageObjRegex.exec(instrumentedCode) + if (coverageObjMatch !== null) { + coverageObj = JSON.parse(coverageObjMatch[0]) + globalCoverageMap.add(coverageObj) + } } } From b78c15b0a269c0b1e1634641804ba346626527d8 Mon Sep 17 00:00:00 2001 From: Srinivas Dhanwada Date: Wed, 15 May 2019 23:56:36 -0500 Subject: [PATCH 8/9] fix(reporter): Access Data Properly to Check Coverage This commit fixes errors with accessing data properly during the checkCoverage method. A previous commit updated the implementation to use istanbul-lib-coverage, but this involved an api change to access the raw coverage data (which checkCoverage uses). This commit also fixes the checking coverage for each file by using a map to store file coverage summaries instead of merging summaries like the global results. Per file coverage now works as expected. --- lib/reporter.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/reporter.js b/lib/reporter.js index e43a2e6..9ab9321 100644 --- a/lib/reporter.js +++ b/lib/reporter.js @@ -121,7 +121,7 @@ var CoverageReporter = function (rootConfig, helper, logger, emitter) { var globalTrackedFiles = getTrackedFiles(coverageMap, thresholds.global.excludes) var eachTrackedFiles = getTrackedFiles(coverageMap, thresholds.each.excludes) var globalResults = istanbulLibCoverage.createCoverageSummary() - var eachResults = istanbulLibCoverage.createCoverageSummary() + var eachResults = {} globalTrackedFiles.forEach(function (f) { var fileCoverage = coverageMap.fileCoverageFor(f) var summary = fileCoverage.toSummary() @@ -130,7 +130,7 @@ var CoverageReporter = function (rootConfig, helper, logger, emitter) { eachTrackedFiles.forEach(function (f) { var fileCoverage = coverageMap.fileCoverageFor(f) var summary = fileCoverage.toSummary() - eachResults.merge(summary) + eachResults[f] = summary }) var coverageFailed = false @@ -164,11 +164,11 @@ var CoverageReporter = function (rootConfig, helper, logger, emitter) { }) } - check('global', thresholds.global, globalResults) + check('global', thresholds.global, globalResults.toJSON()) - Object.keys(eachResults).forEach(function (key) { + eachTrackedFiles.forEach(function (key) { var keyThreshold = helper.merge(thresholds.each, overrideThresholds(key, thresholds.each.overrides)) - check('per-file' + ' (' + key + ') ', keyThreshold, eachResults[key]) + check('per-file' + ' (' + key + ') ', keyThreshold, eachResults[key].toJSON()) }) return coverageFailed From ef45b732a1e96cb958b6dfe9a69883e3f26a99e1 Mon Sep 17 00:00:00 2001 From: Srinivas Dhanwada Date: Thu, 16 May 2019 00:01:34 -0500 Subject: [PATCH 9/9] test: Update Unit Tests to use new Istanbul API This commit updates the mocking done in unit tests to properly mock the new istanbul API. Additionally, new unit test suites are added for the utility methods report-creator and source-map-store. --- test/in-memory-report.spec.coffee | 20 +- test/index.spec.coffee | 6 +- test/report-creator.spec.coffee | 45 +++++ test/reporter.spec.coffee | 298 +++++++++++++++--------------- test/source-map-store.spec.coffee | 22 +++ 5 files changed, 231 insertions(+), 160 deletions(-) create mode 100644 test/report-creator.spec.coffee create mode 100644 test/source-map-store.spec.coffee diff --git a/test/in-memory-report.spec.coffee b/test/in-memory-report.spec.coffee index 01e4a7d..c22feff 100644 --- a/test/in-memory-report.spec.coffee +++ b/test/in-memory-report.spec.coffee @@ -1,19 +1,24 @@ InMemoryReport = require '../lib/in-memory-report' -istanbul = require 'istanbul'; describe 'InMemoryReport', -> emitter = emit: sinon.stub() browser = { name: 'firefox' } - result = { coverage: 'result' } - collector = - getFinalCoverage: sinon.stub().returns result + result = { test: { data: 'result' } } + fc = { + path: 'test' + toJSON: sinon.stub().returns { data: 'result' } + } + node = getFileCoverage: sinon.stub().returns fc it 'should raise an "coverage_complete" event.', -> sut = new InMemoryReport { browser: browser, emitter: emitter} - sut.writeReport collector - expect(collector.getFinalCoverage).to.have.been.called + sut.onStart() + sut.onDetail(node) + sut.onEnd() + expect(node.getFileCoverage).to.have.been.called + expect(fc.toJSON).to.have.been.called expect(emitter.emit).to.have.been.calledWith('coverage_complete', browser, result) it 'should be of type "in-memory"', -> @@ -21,6 +26,3 @@ describe 'InMemoryReport', -> it 'should not fail when created without arguments', -> expect(new InMemoryReport()).to.be.ok - - it 'should inherit from Report', -> - expect(new InMemoryReport()).to.be.an.instanceof(istanbul.Report) \ No newline at end of file diff --git a/test/index.spec.coffee b/test/index.spec.coffee index a68d977..d929ec8 100644 --- a/test/index.spec.coffee +++ b/test/index.spec.coffee @@ -1,7 +1,7 @@ index = require '../lib/index' InMemoryReport = require '../lib/in-memory-report' -istanbul = require 'istanbul' +reportCreator = require '../lib/report-creator' describe 'Index', -> - it 'should register "InMemoryReport" to istanbul', -> - expect(istanbul.Report.create('in-memory', {})).to.be.an.instanceof(InMemoryReport) \ No newline at end of file + it 'should register "InMemoryReport" to Report Creator', -> + expect(reportCreator.create('in-memory', {})).to.be.an.instanceof(InMemoryReport) \ No newline at end of file diff --git a/test/report-creator.spec.coffee b/test/report-creator.spec.coffee new file mode 100644 index 0000000..1ae95d8 --- /dev/null +++ b/test/report-creator.spec.coffee @@ -0,0 +1,45 @@ +istanbulReports = require 'istanbul-reports' + +reportCreator = require '../lib/report-creator' + +describe 'Report Creator', -> + + afterEach -> + reportCreator.reset() + + describe 'register', -> + + it 'should throw when reporter does not include type', -> + reporter = {} + expect(() -> + reportCreator.register(reporter) + ).to.throw + + it 'should complete when report includes a type', -> + reporter = { TYPE: 'test' } + expect(reportCreator.register(reporter)).to.be.equal('test') + + describe 'create', -> + + it 'should return custom reporter if registered', -> + Reporter = sinon.stub() + Reporter.TYPE = 'test' + reportCreator.register(Reporter) + fallbackCreateStub = sinon.stub(istanbulReports, 'create') + reporterOpts = { test: 'options' } + + reporter = reportCreator.create('test', reporterOpts) + + expect(fallbackCreateStub).not.to.be.called + expect(Reporter.calledWithNew()).to.be.true + expect(Reporter).to.be.calledWith(reporterOpts) + + it 'should proxy call to istanbul if custom reporter is not registered', -> + fallbackCreateStub = sinon.stub(istanbulReports, 'create') + fallbackCreateStub.returnsThis() + reporterOpts = { test: 'options' } + + reporter = reportCreator.create('test', reporterOpts) + + expect() + expect(fallbackCreateStub).to.be.calledWith('test', reporterOpts) diff --git a/test/reporter.spec.coffee b/test/reporter.spec.coffee index 9638991..929ff91 100644 --- a/test/reporter.spec.coffee +++ b/test/reporter.spec.coffee @@ -5,7 +5,13 @@ describe 'reporter', -> _ = require 'lodash' events = require 'events' path = require 'path' - istanbul = require 'istanbul' + + istanbulLibCoverage = require 'istanbul-lib-coverage' + istanbulLibReport = require 'istanbul-lib-report' + + globalSourceMapStore = require '../lib/source-map-store' + globalCoverageMap = require '../lib/coverage-map' + reports = require '../lib/report-creator' # TODO(vojta): remove the dependency on karma helper = require '../node_modules/karma/lib/helper' @@ -18,81 +24,89 @@ describe 'reporter', -> loadFile = nodeMocks.loadFile m = null - mockFs = - writeFile: sinon.spy() - - mockStore = sinon.spy() - mockStore.mix = (fn, obj) -> - istanbul.Store.mix fn, obj - - mockAdd = sinon.spy() - mockDispose = sinon.spy() - mockGetFinalCoverage = sinon.stub().returns {} - mockCollector = class Collector - add: mockAdd - dispose: mockDispose - getFinalCoverage: mockGetFinalCoverage - mockWriteReport = sinon.spy() - mockReportCreate = sinon.stub().returns writeReport: mockWriteReport - mockMkdir = sinon.spy() + mkdirIfNotExistsStub = sinon.stub() mockHelper = _: helper._ isDefined: (v) -> helper.isDefined v merge: (v...) -> helper.merge v... - mkdirIfNotExists: mockMkdir + mkdirIfNotExists: mkdirIfNotExistsStub normalizeWinPath: (path) -> helper.normalizeWinPath path - mockCoverageMap = - add: sinon.spy() - get: sinon.spy() - mockDefaultWatermarks = - statements: [50, 80] - branches: [50, 80] - functions: [50, 80] - lines: [50, 80] - - mockSummarizeCoverage = sinon.stub().returns { - lines: {total: 5, covered: 1, skipped: 0, pct: 20}, - statements: {total: 5, covered: 1, skipped: 0, pct: 20}, - functions: {total: 5, covered: 1, skipped: 0, pct: 20}, - branches: {total: 5, covered: 1, skipped: 0, pct: 20} - } + + # Mock Objects + mockCoverageSummary = null + mockFileCoverage = null + mockCoverageMap = null + mockDefaultWatermarks = null + mockPackageSummary = null + mockSourceMapStore = null + mockGlobalCoverageMap = null + + # Stubs + createCoverageSummaryStub = null + createCoverageMapStub = null + createContextStub = null + packageSummaryStub = null + getDefaultWatermarkStub = null + sourceMapStoreGetStub = null + globalCoverageMapGetStub = null + globalCoverageMapAddStub = null + reportCreateStub = null + + mockFs = + writeFile: sinon.spy() mocks = fs: mockFs - istanbul: - Store: mockStore - Collector: mockCollector - Report: create: mockReportCreate - config: defaultConfig: sinon.stub().returns(reporting: watermarks: mockDefaultWatermarks) - utils: - summarizeCoverage: mockSummarizeCoverage - summarizeFileCoverage: mockSummarizeCoverage dateformat: require 'dateformat' - './coverage-map': mockCoverageMap beforeEach -> - m = loadFile __dirname + '/../lib/reporter.js', mocks - - describe 'SourceCacheStore', -> - options = store = null - - beforeEach -> - options = - sourceCache: { './foo': 'TEST_SRC_DATA' } - store = new m.SourceCacheStore options - - it 'should fail on call to keys', -> - expect(-> store.keys()).to.throw() - - it 'should call get and check cache data', -> - expect(store.get('./foo')).to.equal 'TEST_SRC_DATA' - - it 'should call hasKey and check cache data', -> - expect(store.hasKey('./foo')).to.be.true - expect(store.hasKey('./bar')).to.be.false + mockCoverageSummary = + merge: sinon.stub() + toJSON: sinon.stub() + mockFileCoverage = + merge: sinon.stub() + toJSON: sinon.stub() + toSummary: sinon.stub() + mockFileCoverage.toSummary.returns mockCoverageSummary + mockCoverageMap = + fileCoverageFor: sinon.stub() + files: sinon.stub() + merge: sinon.stub() + toJSON: sinon.stub() + mockCoverageMap.fileCoverageFor.returns mockFileCoverage + createCoverageSummaryStub = sinon.stub(istanbulLibCoverage, 'createCoverageSummary') + createCoverageSummaryStub.returns mockCoverageSummary + createCoverageMapStub = sinon.stub(istanbulLibCoverage, 'createCoverageMap') + createCoverageMapStub.returns mockCoverageMap + + mockDefaultWatermarks = + statements: [50, 80] + branches: [50, 80] + functions: [50, 80] + lines: [50, 80] + mockPackageSummary = + visit: sinon.stub() + createContextStub = sinon.stub(istanbulLibReport, 'createContext') + packageSummaryStub = sinon.stub(istanbulLibReport.summarizers, 'pkg') + packageSummaryStub.returns mockPackageSummary + getDefaultWatermarkStub = sinon.stub(istanbulLibReport, 'getDefaultWatermarks') + getDefaultWatermarkStub.returns mockDefaultWatermarks + + mockSourceMapStore = { + transformCoverage: sinon.stub() + } + mockSourceMapStore.transformCoverage.returns { map: mockCoverageMap } + sourceMapStoreGetStub = sinon.stub(globalSourceMapStore, 'get') + sourceMapStoreGetStub.returns mockSourceMapStore + + mockGlobalCoverageMap = {} + globalCoverageMapGetStub = sinon.stub(globalCoverageMap, 'get') + globalCoverageMapGetStub.returns mockGlobalCoverageMap + globalCoverageMapAddStub = sinon.stub(globalCoverageMap, 'add') + + reportCreateStub = sinon.stub(reports, 'create') - it 'should fail on call to set', -> - expect(-> store.set()).to.throw() + m = loadFile __dirname + '/../lib/reporter.js', mocks describe 'CoverageReporter', -> rootConfig = emitter = reporter = null @@ -118,7 +132,7 @@ describe 'reporter', -> browsers.add fakeOpera reporter.onRunStart() browsers.forEach (b) -> reporter.onBrowserStart b - mockMkdir.resetHistory() + mkdirIfNotExistsStub.resetHistory() it 'has no pending file writings', -> done = sinon.spy() @@ -129,22 +143,22 @@ describe 'reporter', -> result = coverage: null reporter.onBrowserComplete fakeChrome, result - expect(mockAdd).not.to.have.been.called + expect(mockCoverageMap.merge).not.to.have.been.called it 'should handle no result', -> reporter.onBrowserComplete fakeChrome, undefined - expect(mockAdd).not.to.have.been.called + expect(mockCoverageMap.merge).not.to.have.been.called it 'should make reports', -> reporter.onRunComplete browsers - expect(mockMkdir).to.have.been.calledTwice + expect(mkdirIfNotExistsStub).to.have.been.calledTwice dir = rootConfig.coverageReporter.dir - expect(mockMkdir.getCall(0).args[0]).to.deep.equal resolve('/base', dir, fakeChrome.name) - expect(mockMkdir.getCall(1).args[0]).to.deep.equal resolve('/base', dir, fakeOpera.name) - mockMkdir.getCall(0).args[1]() - expect(mockReportCreate).to.have.been.called - expect(mockWriteReport).to.have.been.called - createArgs = mockReportCreate.getCall(0).args + expect(mkdirIfNotExistsStub.getCall(0).args[0]).to.deep.equal resolve('/base', dir, fakeChrome.name) + expect(mkdirIfNotExistsStub.getCall(1).args[0]).to.deep.equal resolve('/base', dir, fakeOpera.name) + mkdirIfNotExistsStub.getCall(0).args[1]() + expect(reportCreateStub).to.have.been.called + expect(mockPackageSummary.visit).to.have.been.called + createArgs = reportCreateStub.getCall(0).args expect(createArgs[0]).to.be.equal 'html' expect(createArgs[1].browser).to.be.equal fakeChrome expect(createArgs[1].emitter).to.be.equal emitter @@ -159,14 +173,14 @@ describe 'reporter', -> browsers.forEach (b) -> reporter.onBrowserStart b reporter.onRunComplete browsers - expect(mockMkdir).to.have.been.calledTwice + expect(mkdirIfNotExistsStub).to.have.been.calledTwice dir = customConfig.coverageReporter.dir subdir = customConfig.coverageReporter.subdir - expect(mockMkdir.getCall(0).args[0]).to.deep.equal resolve('/base', dir, subdir) - expect(mockMkdir.getCall(1).args[0]).to.deep.equal resolve('/base', dir, subdir) - mockMkdir.getCall(0).args[1]() - expect(mockReportCreate).to.have.been.called - expect(mockWriteReport).to.have.been.called + expect(mkdirIfNotExistsStub.getCall(0).args[0]).to.deep.equal resolve('/base', dir, subdir) + expect(mkdirIfNotExistsStub.getCall(1).args[0]).to.deep.equal resolve('/base', dir, subdir) + mkdirIfNotExistsStub.getCall(0).args[1]() + expect(reportCreateStub).to.have.been.called + expect(mockPackageSummary.visit).to.have.been.called it 'should support a function for the subdir option', -> customConfig = _.merge {}, rootConfig, @@ -178,13 +192,13 @@ describe 'reporter', -> browsers.forEach (b) -> reporter.onBrowserStart b reporter.onRunComplete browsers - expect(mockMkdir).to.have.been.calledTwice + expect(mkdirIfNotExistsStub).to.have.been.calledTwice dir = customConfig.coverageReporter.dir - expect(mockMkdir.getCall(0).args[0]).to.deep.equal resolve('/base', dir, 'chrome') - expect(mockMkdir.getCall(1).args[0]).to.deep.equal resolve('/base', dir, 'opera') - mockMkdir.getCall(0).args[1]() - expect(mockReportCreate).to.have.been.called - expect(mockWriteReport).to.have.been.called + expect(mkdirIfNotExistsStub.getCall(0).args[0]).to.deep.equal resolve('/base', dir, 'chrome') + expect(mkdirIfNotExistsStub.getCall(1).args[0]).to.deep.equal resolve('/base', dir, 'opera') + mkdirIfNotExistsStub.getCall(0).args[1]() + expect(reportCreateStub).to.have.been.called + expect(mockPackageSummary.visit).to.have.been.called it 'should support a specific dir and subdir per reporter', -> customConfig = _.merge {}, rootConfig, @@ -207,14 +221,14 @@ describe 'reporter', -> browsers.forEach (b) -> reporter.onBrowserStart b reporter.onRunComplete browsers - expect(mockMkdir.callCount).to.equal 4 - expect(mockMkdir.getCall(0).args[0]).to.deep.equal resolve('/base', 'reporter1', 'chrome') - expect(mockMkdir.getCall(1).args[0]).to.deep.equal resolve('/base', 'reporter1', 'opera') - expect(mockMkdir.getCall(2).args[0]).to.deep.equal resolve('/base', 'reporter2', 'CHROME') - expect(mockMkdir.getCall(3).args[0]).to.deep.equal resolve('/base', 'reporter2', 'OPERA') - mockMkdir.getCall(0).args[1]() - expect(mockReportCreate).to.have.been.called - expect(mockWriteReport).to.have.been.called + expect(mkdirIfNotExistsStub.callCount).to.equal 4 + expect(mkdirIfNotExistsStub.getCall(0).args[0]).to.deep.equal resolve('/base', 'reporter1', 'chrome') + expect(mkdirIfNotExistsStub.getCall(1).args[0]).to.deep.equal resolve('/base', 'reporter1', 'opera') + expect(mkdirIfNotExistsStub.getCall(2).args[0]).to.deep.equal resolve('/base', 'reporter2', 'CHROME') + expect(mkdirIfNotExistsStub.getCall(3).args[0]).to.deep.equal resolve('/base', 'reporter2', 'OPERA') + mkdirIfNotExistsStub.getCall(0).args[1]() + expect(reportCreateStub).to.have.been.called + expect(mockPackageSummary.visit).to.have.been.called it 'should fallback to the default dir/subdir if not provided', -> customConfig = _.merge {}, rootConfig, @@ -235,14 +249,14 @@ describe 'reporter', -> browsers.forEach (b) -> reporter.onBrowserStart b reporter.onRunComplete browsers - expect(mockMkdir.callCount).to.equal 4 - expect(mockMkdir.getCall(0).args[0]).to.deep.equal resolve('/base', 'reporter1', 'defaultsubdir') - expect(mockMkdir.getCall(1).args[0]).to.deep.equal resolve('/base', 'reporter1', 'defaultsubdir') - expect(mockMkdir.getCall(2).args[0]).to.deep.equal resolve('/base', 'defaultdir', 'CHROME') - expect(mockMkdir.getCall(3).args[0]).to.deep.equal resolve('/base', 'defaultdir', 'OPERA') - mockMkdir.getCall(0).args[1]() - expect(mockReportCreate).to.have.been.called - expect(mockWriteReport).to.have.been.called + expect(mkdirIfNotExistsStub.callCount).to.equal 4 + expect(mkdirIfNotExistsStub.getCall(0).args[0]).to.deep.equal resolve('/base', 'reporter1', 'defaultsubdir') + expect(mkdirIfNotExistsStub.getCall(1).args[0]).to.deep.equal resolve('/base', 'reporter1', 'defaultsubdir') + expect(mkdirIfNotExistsStub.getCall(2).args[0]).to.deep.equal resolve('/base', 'defaultdir', 'CHROME') + expect(mkdirIfNotExistsStub.getCall(3).args[0]).to.deep.equal resolve('/base', 'defaultdir', 'OPERA') + mkdirIfNotExistsStub.getCall(0).args[1]() + expect(reportCreateStub).to.have.been.called + expect(mockPackageSummary.visit).to.have.been.called it 'should not create directory if reporting text* to console', -> run = -> @@ -256,7 +270,7 @@ describe 'reporter', -> { type: 'text-summary' } ] run() - expect(mockMkdir).not.to.have.been.called + expect(mkdirIfNotExistsStub).not.to.have.been.called it 'should create directory if reporting text* to file', -> run = -> @@ -267,12 +281,12 @@ describe 'reporter', -> rootConfig.coverageReporter.reporters = [{ type: 'text', file: 'file' }] run() - expect(mockMkdir).to.have.been.calledTwice + expect(mkdirIfNotExistsStub).to.have.been.calledTwice - mockMkdir.resetHistory() + mkdirIfNotExistsStub.resetHistory() rootConfig.coverageReporter.reporters = [{ type: 'text-summary', file: 'file' }] run() - expect(mockMkdir).to.have.been.calledTwice + expect(mkdirIfNotExistsStub).to.have.been.calledTwice it 'should support including all sources', -> customConfig = _.merge {}, rootConfig, @@ -280,15 +294,15 @@ describe 'reporter', -> dir: 'defaultdir' includeAllSources: true - mockCoverageMap.get.resetHistory() - mockAdd.resetHistory() + globalCoverageMapGetStub.resetHistory() + createCoverageMapStub.resetHistory() reporter = new m.CoverageReporter customConfig, mockHelper, mockLogger reporter.onRunStart() browsers.forEach (b) -> reporter.onBrowserStart b - expect(mockCoverageMap.get).to.have.been.called - expect(mockAdd).to.have.been.calledWith mockCoverageMap.get.returnValues[0] + expect(globalCoverageMapGetStub).to.have.been.called + expect(createCoverageMapStub).to.have.been.calledWith globalCoverageMapGetStub.returnValues[0] it 'should not retrieve the coverageMap if we aren\'t including all sources', -> customConfig = _.merge {}, rootConfig, @@ -296,26 +310,26 @@ describe 'reporter', -> dir: 'defaultdir' includeAllSources: false - mockCoverageMap.get.resetHistory() + globalCoverageMapGetStub.resetHistory() reporter = new m.CoverageReporter customConfig, mockHelper, mockLogger reporter.onRunStart() browsers.forEach (b) -> reporter.onBrowserStart b - expect(mockCoverageMap.get).not.to.have.been.called + expect(globalCoverageMapGetStub).not.to.have.been.called it 'should default to not including all sources', -> customConfig = _.merge {}, rootConfig, coverageReporter: dir: 'defaultdir' - mockCoverageMap.get.resetHistory() + globalCoverageMapGetStub.resetHistory() reporter = new m.CoverageReporter customConfig, mockHelper, mockLogger reporter.onRunStart() browsers.forEach (b) -> reporter.onBrowserStart b - expect(mockCoverageMap.get).not.to.have.been.called + expect(globalCoverageMapGetStub).not.to.have.been.called it 'should pass watermarks to istanbul', -> watermarks = @@ -333,15 +347,17 @@ describe 'reporter', -> ] watermarks: watermarks - mockReportCreate.reset() + reportCreateStub.resetHistory() + createContextStub.resetHistory() reporter = new m.CoverageReporter customConfig, mockHelper, mockLogger reporter.onRunStart() browsers.forEach (b) -> reporter.onBrowserStart b reporter.onRunComplete browsers - expect(mockReportCreate).to.have.been.called - options = mockReportCreate.getCall(0) + expect(createContextStub).to.have.been.called + expect(reportCreateStub).to.have.been.called + options = reportCreateStub.getCall(0) expect(options.args[1].watermarks).to.deep.equal(watermarks) it 'should merge with istanbul default watermarks', -> @@ -358,42 +374,22 @@ describe 'reporter', -> ] watermarks: watermarks - mockReportCreate.reset() + reportCreateStub.resetHistory() + createContextStub.resetHistory() reporter = new m.CoverageReporter customConfig, mockHelper, mockLogger reporter.onRunStart() browsers.forEach (b) -> reporter.onBrowserStart b reporter.onRunComplete browsers - expect(mockReportCreate).to.have.been.called - options = mockReportCreate.getCall(0) + expect(createContextStub).to.have.been.called + expect(reportCreateStub).to.have.been.called + options = reportCreateStub.getCall(0) expect(options.args[1].watermarks.statements).to.deep.equal(watermarks.statements) expect(options.args[1].watermarks.branches).to.deep.equal(mockDefaultWatermarks.branches) expect(options.args[1].watermarks.functions).to.deep.equal(mockDefaultWatermarks.functions) expect(options.args[1].watermarks.lines).to.deep.equal(watermarks.lines) - it 'should not write reports after disposing the collector', -> - run = -> - reporter = new m.CoverageReporter rootConfig, mockHelper, mockLogger - reporter.onRunStart() - browsers.forEach (b) -> reporter.onBrowserStart b - reporter.onRunComplete browsers - - rootConfig.coverageReporter.reporters = [ - { type: 'text' } - { type: 'html' } - ] - - mockDispose.resetHistory() - mockWriteReport.resetHistory() - mockMkdir.resetHistory() - - run() - - mockMkdir.getCall(0).args[1]() - - expect(mockDispose).not.to.have.been.calledBefore mockWriteReport - it 'should log errors on low coverage and fail the build', -> customConfig = _.merge {}, rootConfig, coverageReporter: @@ -401,9 +397,12 @@ describe 'reporter', -> each: statements: 50 - mockGetFinalCoverage.returns - './foo/bar.js': {} - './foo/baz.js': {} + mockCoverageMap.files.returns ['./foo/bar.js', './foo/baz.js'] + mockCoverageSummary.toJSON.returns + lines: {total: 5, covered: 1, skipped: 0, pct: 20}, + statements: {total: 5, covered: 1, skipped: 0, pct: 20}, + functions: {total: 5, covered: 1, skipped: 0, pct: 20}, + branches: {total: 5, covered: 1, skipped: 0, pct: 20} spy1 = sinon.spy() @@ -431,9 +430,12 @@ describe 'reporter', -> each: statements: 10 - mockGetFinalCoverage.returns - './foo/bar.js': {} - './foo/baz.js': {} + mockCoverageMap.files.returns ['./foo/bar.js', './foo/baz.js'] + mockCoverageSummary.toJSON.returns + lines: {total: 5, covered: 1, skipped: 0, pct: 20}, + statements: {total: 5, covered: 1, skipped: 0, pct: 20}, + functions: {total: 5, covered: 1, skipped: 0, pct: 20}, + branches: {total: 5, covered: 1, skipped: 0, pct: 20} spy1 = sinon.spy() diff --git a/test/source-map-store.spec.coffee b/test/source-map-store.spec.coffee new file mode 100644 index 0000000..191c03b --- /dev/null +++ b/test/source-map-store.spec.coffee @@ -0,0 +1,22 @@ +istanbulLibSourceMaps = require 'istanbul-lib-source-maps' + +globalSourceMapStore = require '../lib/source-map-store' + +describe 'Source Map Store', -> + + it 'should create a source map store for path if it did not exist previously', -> + createSourceMapStoreStub = sinon.stub(istanbulLibSourceMaps, 'createSourceMapStore') + createSourceMapStoreStub.returns({}) + + globalSourceMapStore.get('__test', { opts: 'test' }) + expect(createSourceMapStoreStub).to.be.calledWith({ opts: 'test' }) + + it 'should not create a source map store for path if it previously was called', -> + createSourceMapStoreStub = sinon.stub(istanbulLibSourceMaps, 'createSourceMapStore') + createSourceMapStoreStub.returns({}) + + globalSourceMapStore.get('__test2', { opts: 'test2' }) + globalSourceMapStore.get('__test2', { opts: 'test3' }) + + expect(createSourceMapStoreStub.callCount).to.be.equal(1) + expect(createSourceMapStoreStub).to.be.calledWith({ opts: 'test2' })