diff --git a/README.md b/README.md index 33d234c..e5f9146 100644 --- a/README.md +++ b/README.md @@ -209,6 +209,20 @@ coverageReporter: { } ``` +#### includeAllSources +**Type:** Boolean + +You can opt to include all sources files, as indicated by the coverage preprocessor, in your code coverage data, even if there are no tests covering them. (Default `false`) + +```javascript +coverageReporter: { + type : 'text', + dir : 'coverage/', + file : 'coverage.txt', + includeAllSources: true +} +``` + #### multiple reporters You can use multiple reporters, by providing array of options. diff --git a/lib/coverageMap.js b/lib/coverageMap.js new file mode 100644 index 0000000..5146096 --- /dev/null +++ b/lib/coverageMap.js @@ -0,0 +1,19 @@ +var coverageMap = {}; + +function addCoverage(coverageObj){ + coverageMap[coverageObj.path] = coverageObj; +} + +function getCoverageMap(){ + return coverageMap; +} + +function resetCoverage(){ + coverageMap = {}; +} + +module.exports = { + add: addCoverage, + get: getCoverageMap, + reset: resetCoverage + }; diff --git a/lib/preprocessor.js b/lib/preprocessor.js index ac61eea..39b2a23 100644 --- a/lib/preprocessor.js +++ b/lib/preprocessor.js @@ -1,13 +1,15 @@ var istanbul = require('istanbul'), minimatch = require('minimatch'), globalSourceCache = require('./sourceCache'), - extend = require('util')._extend; + extend = require('util')._extend, + coverageMap = require('./coverageMap'); var createCoveragePreprocessor = function(logger, basePath, reporters, coverageReporter) { var log = logger.create('preprocessor.coverage'); var instrumenterOverrides = (coverageReporter && coverageReporter.instrumenter) || {}; var instrumenters = extend({istanbul: istanbul}, (coverageReporter && coverageReporter.instrumenters)); var sourceCache = globalSourceCache.getByBasePath(basePath); + var includeAllSources = coverageReporter && coverageReporter.includeAllSources === true; var instrumentersOptions = Object.keys(instrumenters).reduce(function getInstumenterOptions(memo, instrumenterName){ memo[instrumenterName] = (coverageReporter && coverageReporter.instrumenterOptions && coverageReporter.instrumenterOptions[instrumenterName]) || {}; return memo; @@ -62,6 +64,17 @@ var createCoveragePreprocessor = function(logger, basePath, reporters, coverageR // remember the actual immediate instrumented JS for given original path sourceCache[jsPath] = content; + if (includeAllSources) { + var coverageObjRegex = /\{.*"path".*"fnMap".*"statementMap".*"branchMap".*\}/g; + var coverageObjMatch = coverageObjRegex.exec(instrumentedCode); + + if (coverageObjMatch !== null) { + var coverageObj = JSON.parse(coverageObjMatch[0]); + + coverageMap.add(coverageObj); + } + } + done(instrumentedCode); }); }; diff --git a/lib/reporter.js b/lib/reporter.js index d00c35d..3312f12 100644 --- a/lib/reporter.js +++ b/lib/reporter.js @@ -4,6 +4,7 @@ var util = require('util'); var istanbul = require('istanbul'); var dateformat = require('dateformat'); var globalSourceCache = require('./sourceCache'); +var coverageMap = require('./coverageMap'); var Store = istanbul.Store; @@ -39,6 +40,7 @@ var CoverageReporter = function(rootConfig, helper, logger) { var basePath = rootConfig.basePath; var reporters = config.reporters; var sourceCache = globalSourceCache.getByBasePath(basePath); + var includeAllSources = config.includeAllSources === true; if (!helper.isDefined(reporters)) { reporters = [config]; @@ -91,14 +93,15 @@ var CoverageReporter = function(rootConfig, helper, logger) { // TODO(vojta): remove once we don't care about Karma 0.10 if (browsers) { - browsers.forEach(function(browser) { - collectors[browser.id] = new istanbul.Collector(); - }); + browsers.forEach(this.onBrowserStart.bind(this)); } }; this.onBrowserStart = function(browser) { collectors[browser.id] = new istanbul.Collector(); + if(includeAllSources){ + collectors[browser.id].add(coverageMap.get()); + } }; this.onBrowserComplete = function(browser, result) { diff --git a/test/coverageMap.spec.coffee b/test/coverageMap.spec.coffee new file mode 100644 index 0000000..588307b --- /dev/null +++ b/test/coverageMap.spec.coffee @@ -0,0 +1,28 @@ +coverageMap = require '../lib/coverageMap' +coverageObj = { path: './path.js', otherThings: 'that are in instrumented code' } + +describe 'coverageMap', -> + it 'should add coverageMap and get them', -> + coverageMap.add(coverageObj) + + expect(coverageMap.get()['./path.js']).to.equal coverageObj + + it 'should be able to be reset', -> + coverageMap.reset() + + expect(coverageMap.get()['./path.js']).to.not.exist + + coverageMap.add(coverageObj) + + expect(coverageMap.get()['./path.js']).to.equal coverageObj + + coverageMap.reset() + + expect(coverageMap.get()['./path.js']).to.not.exist + + it 'should be able to have multiple coverageMap', -> + coverageMap.reset() + coverageMap.add(coverageObj) + coverageMap.add({ path: './anotherFile.js', moarKeys: [1, 2, 3] }) + + expect(Object.keys(coverageMap.get()).length).to.equal 2 diff --git a/test/preprocessor.spec.coffee b/test/preprocessor.spec.coffee index 81da4ae..026b33c 100644 --- a/test/preprocessor.spec.coffee +++ b/test/preprocessor.spec.coffee @@ -1,6 +1,8 @@ vm = require 'vm' util = require 'util' +coverageMap = require '../lib/coverageMap' + describe 'preprocessor', -> createPreprocessor = require '../lib/preprocessor' @@ -124,3 +126,33 @@ describe 'preprocessor', -> '**/*.coffee': 'madeup' expect(work).to.throw() done() + + it 'should add coverageMap when including all sources', (done) -> + process = createPreprocessor mockLogger, '/base/path', ['coverage'], { includeAllSources: true } + file = new File '/base/path/file.js' + + coverageMap.reset() + + process ORIGINAL_CODE, file, (preprocessedCode) -> + expect(coverageMap.get()['./file.js']).to.exist + done() + + it 'should not add coverageMap when not including all sources', (done) -> + process = createPreprocessor mockLogger, '/base/path', ['coverage'], { includeAllSources: false } + file = new File '/base/path/file.js' + + coverageMap.reset() + + process ORIGINAL_CODE, file, (preprocessedCode) -> + expect(coverageMap.get()['./file.js']).to.not.exist + done() + + it 'should not add coverageMap in the default state', (done) -> + process = createPreprocessor mockLogger, '/base/path', ['coverage'], {} + file = new File '/base/path/file.js' + + coverageMap.reset() + + process ORIGINAL_CODE, file, (preprocessedCode) -> + expect(coverageMap.get()['./file.js']).to.not.exist + done() diff --git a/test/reporter.spec.coffee b/test/reporter.spec.coffee index c2e31bf..7520a92 100644 --- a/test/reporter.spec.coffee +++ b/test/reporter.spec.coffee @@ -38,6 +38,9 @@ describe 'reporter', -> merge: (v...) -> helper.merge v... mkdirIfNotExists: mockMkdir normalizeWinPath: (path) -> helper.normalizeWinPath path + mockCoverageMap = + add: sinon.spy() + get: sinon.spy() mocks = fs: mockFs @@ -46,6 +49,7 @@ describe 'reporter', -> Collector: mockCollector Report: create: mockReportCreate dateformat: require 'dateformat' + './coverageMap': mockCoverageMap beforeEach -> m = loadFile __dirname + '/../lib/reporter.js', mocks @@ -246,3 +250,46 @@ describe 'reporter', -> rootConfig.coverageReporter.reporters = [{ type: 'text-summary', file: 'file' }] run() expect(mockMkdir).to.have.been.calledTwice + + it 'should support including all sources', -> + customConfig = _.merge {}, rootConfig, + coverageReporter: + dir: 'defaultdir' + includeAllSources: true + + mockCoverageMap.get.reset() + mockAdd.reset() + + 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] + + it 'should not retrieve the coverageMap if we aren\'t including all sources', -> + customConfig = _.merge {}, rootConfig, + coverageReporter: + dir: 'defaultdir' + includeAllSources: false + + mockCoverageMap.get.reset() + + reporter = new m.CoverageReporter customConfig, mockHelper, mockLogger + reporter.onRunStart() + browsers.forEach (b) -> reporter.onBrowserStart b + + expect(mockCoverageMap.get).not.to.have.been.called + + it 'should default to not including all sources', -> + customConfig = _.merge {}, rootConfig, + coverageReporter: + dir: 'defaultdir' + + mockCoverageMap.get.reset() + + reporter = new m.CoverageReporter customConfig, mockHelper, mockLogger + reporter.onRunStart() + browsers.forEach (b) -> reporter.onBrowserStart b + + expect(mockCoverageMap.get).not.to.have.been.called