From c1e542a5b404bfb814969b444f7872f9bdeb1b6a Mon Sep 17 00:00:00 2001 From: Nick Matantsev Date: Sun, 13 Oct 2013 00:02:58 -0400 Subject: [PATCH] fix: add in-memory source code caching to support detail reports on compiled CoffeeScript and other files with intermediate pre-processing --- lib/preprocessor.js | 7 +++++- lib/reporter.js | 29 ++++++++++------------ lib/sourceCache.js | 6 +++++ test/reporter.spec.coffee | 52 ++++++++++++--------------------------- 4 files changed, 41 insertions(+), 53 deletions(-) create mode 100644 lib/sourceCache.js diff --git a/lib/preprocessor.js b/lib/preprocessor.js index 4e88771..822a150 100644 --- a/lib/preprocessor.js +++ b/lib/preprocessor.js @@ -1,11 +1,13 @@ var istanbul = require('istanbul'), ibrik = require('ibrik'), - minimatch = require('minimatch'); + minimatch = require('minimatch'), + globalSourceCache = require('./sourceCache'); var createCoveragePreprocessor = function(logger, basePath, reporters, coverageReporter) { var log = logger.create('preprocessor.coverage'); var instrumenterOverrides = (coverageReporter && coverageReporter.instrumenter) || {}; var instrumenters = {istanbul: istanbul, ibrik: ibrik}; + var sourceCache = globalSourceCache.getByBasePath(basePath); // if coverage reporter is not used, do not preprocess the files if (reporters.indexOf('coverage') === -1) { @@ -55,6 +57,9 @@ var createCoveragePreprocessor = function(logger, basePath, reporters, coverageR file.path = file.path.replace(/\.coffee$/, '.js'); } + // remember the actual immediate instrumented JS for given original path + sourceCache[jsPath] = content; + done(instrumentedCode); }); }; diff --git a/lib/reporter.js b/lib/reporter.js index e818402..7807ec6 100644 --- a/lib/reporter.js +++ b/lib/reporter.js @@ -3,35 +3,31 @@ var fs = require('fs'); var util = require('util'); var istanbul = require('istanbul'); var dateformat = require('dateformat'); +var globalSourceCache = require('./sourceCache'); var Store = istanbul.Store; -var BasePathStore = function(opts) { +var SourceCacheStore = function(opts) { Store.call(this, opts); opts = opts || {}; - this.basePath = opts.basePath; - this.delegate = Store.create('fslookup'); + this.sourceCache = opts.sourceCache; }; -BasePathStore.TYPE = 'basePathlookup'; -util.inherits(BasePathStore, Store); +SourceCacheStore.TYPE = 'sourceCacheLookup'; +util.inherits(SourceCacheStore, Store); -Store.mix(BasePathStore, { +Store.mix(SourceCacheStore, { keys : function() { - return this.delegate.keys(); - }, - toKey : function(key) { - if (key.indexOf('./') === 0) { return path.join(this.basePath, key); } - return key; + throw 'not implemented'; }, get : function(key) { - return this.delegate.get(this.toKey(key)); + return this.sourceCache[key]; }, hasKey : function(key) { - return this.delegate.hasKey(this.toKey(key)); + return this.sourceCache.hasOwnProperty(key); }, set : function(key, contents) { - return this.delegate.set(this.toKey(key), contents); + throw 'not applicable'; } }); @@ -42,6 +38,7 @@ var CoverageReporter = function(rootConfig, helper, logger) { var config = rootConfig.coverageReporter || {}; var basePath = rootConfig.basePath; var reporters = config.reporters; + var sourceCache = globalSourceCache.getByBasePath(basePath); if (!helper.isDefined(reporters)) { reporters = [config]; @@ -132,8 +129,8 @@ var CoverageReporter = function(rootConfig, helper, logger) { log.debug('Writing coverage to %s', outputDir); var options = helper.merge({}, reporterConfig, { dir : outputDir, - sourceStore : new BasePathStore({ - basePath : basePath + sourceStore : new SourceCacheStore({ + sourceCache: sourceCache }) }); var reporter = istanbul.Report.create(reporterConfig.type || 'html', options); diff --git a/lib/sourceCache.js b/lib/sourceCache.js new file mode 100644 index 0000000..eb31b63 --- /dev/null +++ b/lib/sourceCache.js @@ -0,0 +1,6 @@ + +var cacheByBasePath = {}; + +exports.getByBasePath = function (basePath) { + return cacheByBasePath[basePath] ? cacheByBasePath[basePath] : (cacheByBasePath[basePath] = {}); +}; diff --git a/test/reporter.spec.coffee b/test/reporter.spec.coffee index 9fa8505..092e5ad 100644 --- a/test/reporter.spec.coffee +++ b/test/reporter.spec.coffee @@ -23,12 +23,6 @@ describe 'reporter', -> mockStore = sinon.spy() mockStore.mix = (fn, obj) -> istanbul.Store.mix fn, obj - mockFslookup = sinon.stub - keys: -> - get: -> - hasKey: -> - set: -> - mockStore.create = sinon.stub().returns mockFslookup mockAdd = sinon.spy() mockDispose = sinon.spy() @@ -56,40 +50,26 @@ describe 'reporter', -> beforeEach -> m = loadFile __dirname + '/../lib/reporter.js', mocks - describe 'BasePathStore', -> + describe 'SourceCacheStore', -> options = store = null beforeEach -> options = - basePath: 'path/to/coverage/' - store = new m.BasePathStore options - - describe 'toKey', -> - it 'should concat relative path and basePath', -> - expect(store.toKey './foo').to.deep.equal path.join(options.basePath, 'foo') - - it 'should does not concat absolute path and basePath', -> - expect(store.toKey '/foo').to.deep.equal '/foo' - - it 'should call keys and delegate to inline store', -> - store.keys() - expect(mockFslookup.keys).to.have.been.called - - it 'should call get and delegate to inline store', -> - key = './path/to/js' - store.get(key) - expect(mockFslookup.get).to.have.been.calledWith path.join(options.basePath, key) - - it 'should call hasKey and delegate to inline store', -> - key = './path/to/js' - store.hasKey(key) - expect(mockFslookup.hasKey).to.have.been.calledWith path.join(options.basePath, key) - - it 'should call set and delegate to inline store', -> - key = './path/to/js' - content = 'any content' - store.set key, content - expect(mockFslookup.set).to.have.been.calledWith path.join(options.basePath, key), content + 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 + + it 'should fail on call to set', -> + expect(-> store.set()).to.throw() describe 'CoverageReporter', -> rootConfig = emitter = reporter = null