diff --git a/bin/nyc.js b/bin/nyc.js index 01ab304c8..8f62054a3 100755 --- a/bin/nyc.js +++ b/bin/nyc.js @@ -43,7 +43,6 @@ if (argv._[0] === 'report') { if (config.all) nyc.addAllFiles() var env = { - NYC_CACHE: argv.cache ? 'enable' : 'disable', NYC_CONFIG: JSON.stringify(config), NYC_CWD: process.cwd(), NYC_ROOT_ID: nyc.rootId, diff --git a/bin/wrap.js b/bin/wrap.js index d64aceb1a..0ec3cb0cd 100644 --- a/bin/wrap.js +++ b/bin/wrap.js @@ -11,7 +11,8 @@ process.env.NYC_PARENT_PID = process.pid var config = {} if (process.env.NYC_CONFIG) config = JSON.parse(process.env.NYC_CONFIG) -config.enableCache = process.env.NYC_CACHE === 'enable' +config.isChildProcess = true + config._processInfo = { ppid: parentPid, root: process.env.NYC_ROOT_ID diff --git a/build-tests.js b/build-tests.js index 62d755d8b..83c59ebec 100644 --- a/build-tests.js +++ b/build-tests.js @@ -15,7 +15,7 @@ mkdirp.sync(path.join(__dirname, 'test/build')) var testDir = path.join(__dirname, 'test/src') var buildDir = path.join(__dirname, 'test/build') -var originalTestsFilename = path.join(testDir, 'nyc-test.js') +var originalTestsFilename = path.join(testDir, 'nyc-tap.js') var originalTestSource = fs.readFileSync(originalTestsFilename, 'utf8') var individualTests = forkingTap(originalTestSource, { filename: originalTestsFilename, diff --git a/index.js b/index.js index ec2ed2d0a..2864d14a2 100755 --- a/index.js +++ b/index.js @@ -1,25 +1,26 @@ /* global __coverage__ */ -var arrify = require('arrify') -var debugLog = require('debug-log')('nyc') -var fs = require('fs') -var glob = require('glob') -var libCoverage = require('istanbul-lib-coverage') -var libHook = require('istanbul-lib-hook') -var libReport = require('istanbul-lib-report') -var libSourceMaps = require('istanbul-lib-source-maps') -var reports = require('istanbul-reports') -var mkdirp = require('mkdirp') -var Module = require('module') -var cachingTransform = require('caching-transform') -var path = require('path') -var rimraf = require('rimraf') -var onExit = require('signal-exit') -var resolveFrom = require('resolve-from') -var convertSourceMap = require('convert-source-map') -var md5hex = require('md5-hex') -var findCacheDir = require('find-cache-dir') -var js = require('default-require-extensions/js') -var testExclude = require('test-exclude') + +const arrify = require('arrify') +const cachingTransform = require('caching-transform') +const debugLog = require('debug-log')('nyc') +const findCacheDir = require('find-cache-dir') +const fs = require('fs') +const glob = require('glob') +const Hash = require('./lib/hash') +const js = require('default-require-extensions/js') +const libCoverage = require('istanbul-lib-coverage') +const libHook = require('istanbul-lib-hook') +const libReport = require('istanbul-lib-report') +const md5hex = require('md5-hex') +const mkdirp = require('mkdirp') +const Module = require('module') +const onExit = require('signal-exit') +const path = require('path') +const reports = require('istanbul-reports') +const resolveFrom = require('resolve-from') +const rimraf = require('rimraf') +const SourceMaps = require('./lib/source-maps') +const testExclude = require('test-exclude') var ProcessInfo try { @@ -29,10 +30,6 @@ try { ProcessInfo = require('./lib/process.js') } -// bust cache whenever nyc is upgraded, this prevents -// crashers caused by instrumentation updates. -var CACHE_VERSION = require('./package.json').version - /* istanbul ignore next */ if (/index\.covered\.js$/.test(__filename)) { require('./lib/self-coverage-helper') @@ -50,12 +47,10 @@ function NYC (config) { this._showProcessTree = config.showProcessTree || false this._eagerInstantiation = config.eager || false this.cwd = config.cwd || process.cwd() - this.reporter = arrify(config.reporter || 'text') this.cacheDirectory = config.cacheDir || findCacheDir({name: 'nyc', cwd: this.cwd}) - - this.enableCache = Boolean(this.cacheDirectory && (config.enableCache === true || process.env.NYC_CACHE === 'enable')) + this.cache = Boolean(this.cacheDirectory && config.cache) this.exclude = testExclude({ cwd: this.cwd, @@ -63,6 +58,11 @@ function NYC (config) { exclude: config.exclude }) + this.sourceMaps = new SourceMaps({ + cache: this.cache, + cacheDirectory: this.cacheDirectory + }) + // require extensions can be provided as config in package.json. this.require = arrify(config.require) @@ -78,11 +78,7 @@ function NYC (config) { return transforms }.bind(this), {}) - this.sourceMapCache = libSourceMaps.createSourceMapStore() - this.hookRunInContext = config.hookRunInContext - this.hashCache = {} - this.loadedMaps = null this.fakeRequire = null this.processInfo = new ProcessInfo(config && config._processInfo) @@ -90,20 +86,16 @@ function NYC (config) { } NYC.prototype._createTransform = function (ext) { - var _this = this - var opts = { - salt: JSON.stringify({ - istanbul: require('istanbul-lib-coverage/package.json').version, - nyc: require('./package.json').version - }), + salt: Hash.salt, hash: function (code, metadata, salt) { - var hash = md5hex([code, metadata.filename, salt]) + '_' + CACHE_VERSION - _this.hashCache[metadata.filename] = hash + var hash = Hash(code, metadata.filename) return hash }, cacheDir: this.cacheDirectory, - disableCache: !this.enableCache, + // when running --all we should not load source-file from + // cache, we want to instead return the fake source. + disableCache: this._disableCachingTransform(), ext: ext } if (this._eagerInstantiation) { @@ -114,6 +106,10 @@ NYC.prototype._createTransform = function (ext) { return cachingTransform(opts) } +NYC.prototype._disableCachingTransform = function () { + return !(this.cache && this.config.isChildProcess) +} + NYC.prototype._loadAdditionalModules = function () { var _this = this this.require.forEach(function (r) { @@ -271,7 +267,7 @@ NYC.prototype._transformFactory = function (cacheDir) { var filename = metadata.filename var sourceMap = null - if (_this._sourceMap) sourceMap = _this._handleSourceMap(cacheDir, code, hash, filename) + if (_this._sourceMap) sourceMap = _this.sourceMaps.extractAndRegister(code, filename) try { instrumented = instrumenter.instrumentSync(code, filename, sourceMap) @@ -289,19 +285,6 @@ NYC.prototype._transformFactory = function (cacheDir) { } } -NYC.prototype._handleSourceMap = function (cacheDir, code, hash, filename) { - var sourceMap = convertSourceMap.fromSource(code) || convertSourceMap.fromMapFileSource(code, path.dirname(filename)) - if (sourceMap) { - if (hash) { - var mapPath = path.join(cacheDir, hash + '.map') - fs.writeFileSync(mapPath, sourceMap.toJSON()) - } else { - this.sourceMapCache.registerMap(filename, sourceMap.sourcemap) - } - } - return sourceMap -} - NYC.prototype._handleJs = function (code, filename) { var relFile = path.relative(this.cwd, filename) // ensure the path has correct casing (see istanbuljs/nyc#269 and nodejs/node#6624) @@ -333,13 +316,14 @@ NYC.prototype.cleanup = function () { } NYC.prototype.clearCache = function () { - if (this.enableCache) { + if (this.cache) { rimraf.sync(this.cacheDirectory) } } NYC.prototype.createTempDirectory = function () { mkdirp.sync(this.tempDirectory()) + mkdirp.sync(this.cacheDirectory) if (this._showProcessTree) { mkdirp.sync(this.processInfoDirectory()) @@ -379,14 +363,14 @@ NYC.prototype.writeCoverageFile = function () { var coverage = coverageFinder() if (!coverage) return - if (this.enableCache) { + if (this.cache) { Object.keys(coverage).forEach(function (absFile) { - if (this.hashCache[absFile] && coverage[absFile]) { - coverage[absFile].contentHash = this.hashCache[absFile] + if (this.sourceMaps.hashCache[absFile] && coverage[absFile]) { + coverage[absFile].contentHash = this.sourceMaps.hashCache[absFile] } }, this) } else { - coverage = this.sourceMapTransform(coverage) + coverage = this.sourceMaps.remapCoverage(coverage) } var id = this.generateUniqueID() @@ -411,13 +395,6 @@ NYC.prototype.writeCoverageFile = function () { ) } -NYC.prototype.sourceMapTransform = function (obj) { - var transformed = this.sourceMapCache.transformCoverage( - libCoverage.createCoverageMap(obj) - ) - return transformed.map.data -} - function coverageFinder () { var coverage = global.__coverage__ if (typeof __coverage__ === 'object') coverage = __coverage__ @@ -431,7 +408,7 @@ NYC.prototype._getCoverageMapFromAllCoverageFiles = function () { this.loadReports().forEach(function (report) { map.merge(report) }) - + map.data = this.sourceMaps.remapCoverage(map.data) return map } @@ -497,10 +474,6 @@ NYC.prototype.loadReports = function (filenames) { var _this = this var files = filenames || fs.readdirSync(this.tempDirectory()) - var cacheDir = _this.cacheDirectory - - var loadedMaps = this.loadedMaps || (this.loadedMaps = {}) - return files.map(function (f) { var report try { @@ -512,25 +485,7 @@ NYC.prototype.loadReports = function (filenames) { return {} } - Object.keys(report).forEach(function (absFile) { - var fileReport = report[absFile] - if (fileReport && fileReport.contentHash) { - var hash = fileReport.contentHash - if (!(hash in loadedMaps)) { - try { - var mapPath = path.join(cacheDir, hash + '.map') - loadedMaps[hash] = JSON.parse(fs.readFileSync(mapPath, 'utf8')) - } catch (e) { - // set to false to avoid repeatedly trying to load the map - loadedMaps[hash] = false - } - } - if (loadedMaps[hash]) { - _this.sourceMapCache.registerMap(absFile, loadedMaps[hash]) - } - } - }) - report = _this.sourceMapTransform(report) + _this.sourceMaps.reloadCachedSourceMaps(report) return report }) } diff --git a/lib/config-util.js b/lib/config-util.js index 245b1c323..5bdc177cf 100644 --- a/lib/config-util.js +++ b/lib/config-util.js @@ -1,9 +1,9 @@ -var arrify = require('arrify') -var fs = require('fs') -var path = require('path') -var findUp = require('find-up') -var testExclude = require('test-exclude') -var Yargs = require('yargs/yargs') +const arrify = require('arrify') +const fs = require('fs') +const path = require('path') +const findUp = require('find-up') +const testExclude = require('test-exclude') +const Yargs = require('yargs/yargs') var Config = {} diff --git a/lib/hash.js b/lib/hash.js new file mode 100644 index 000000000..a36372071 --- /dev/null +++ b/lib/hash.js @@ -0,0 +1,14 @@ +const CACHE_VERSION = require('../package.json').version +const md5hex = require('md5-hex') +const salt = JSON.stringify({ + istanbul: require('istanbul-lib-coverage/package.json').version, + nyc: CACHE_VERSION +}) + +function Hash (code, filename) { + return md5hex([code, filename, salt]) + '_' + CACHE_VERSION +} + +Hash.salt = salt + +module.exports = Hash diff --git a/lib/process-args.js b/lib/process-args.js index fdf36ccdb..df6bcaac1 100644 --- a/lib/process-args.js +++ b/lib/process-args.js @@ -1,5 +1,5 @@ -var parser = require('yargs-parser') -var commands = [ +const parser = require('yargs-parser') +const commands = [ 'report', 'check-coverage', 'instrument' diff --git a/lib/process.js b/lib/process.js index 97e811e54..3976f14ed 100644 --- a/lib/process.js +++ b/lib/process.js @@ -1,6 +1,5 @@ -'use strict' -var archy = require('archy') -var libCoverage = require('istanbul-lib-coverage') +const archy = require('archy') +const libCoverage = require('istanbul-lib-coverage') function ProcessInfo (defaults) { defaults = defaults || {} diff --git a/lib/self-coverage-helper.js b/lib/self-coverage-helper.js index ccea1db89..675f9bea9 100644 --- a/lib/self-coverage-helper.js +++ b/lib/self-coverage-helper.js @@ -1,9 +1,9 @@ /* global ___NYC_SELF_COVERAGE___ */ -var path = require('path') -var fs = require('fs') -var mkdirp = require('mkdirp') -var onExit = require('signal-exit') +const path = require('path') +const fs = require('fs') +const mkdirp = require('mkdirp') +const onExit = require('signal-exit') onExit(function () { var coverage = global.___NYC_SELF_COVERAGE___ diff --git a/lib/source-maps.js b/lib/source-maps.js new file mode 100644 index 000000000..3842626b4 --- /dev/null +++ b/lib/source-maps.js @@ -0,0 +1,61 @@ +const convertSourceMap = require('convert-source-map') +const libCoverage = require('istanbul-lib-coverage') +const libSourceMaps = require('istanbul-lib-source-maps') +const fs = require('fs') +const Hash = require('./hash') +const path = require('path') + +// TODO: write some unit tests for this class. +function SourceMaps (opts) { + this.cache = opts.cache + this.cacheDirectory = opts.cacheDirectory + this.sourceMapCache = libSourceMaps.createSourceMapStore() + this.loadedMaps = {} + this.hashCache = {} +} + +SourceMaps.prototype.extractAndRegister = function (code, filename) { + var sourceMap = convertSourceMap.fromSource(code) || convertSourceMap.fromMapFileSource(code, path.dirname(filename)) + if (sourceMap) { + var hash = Hash(code, filename) + if (this.cache && !this.hashCache[filename]) { + this.hashCache[filename] = hash + var mapPath = path.join(this.cacheDirectory, hash + '.map') + fs.writeFileSync(mapPath, sourceMap.toJSON()) + } else { + this.sourceMapCache.registerMap(filename, sourceMap.sourcemap) + } + } + return sourceMap +} + +SourceMaps.prototype.remapCoverage = function (obj) { + var transformed = this.sourceMapCache.transformCoverage( + libCoverage.createCoverageMap(obj) + ) + return transformed.map.data +} + +SourceMaps.prototype.reloadCachedSourceMaps = function (report) { + var _this = this + Object.keys(report).forEach(function (absFile) { + var fileReport = report[absFile] + if (fileReport && fileReport.contentHash) { + var hash = fileReport.contentHash + if (!(hash in _this.loadedMaps)) { + try { + var mapPath = path.join(_this.cacheDirectory, hash + '.map') + _this.loadedMaps[hash] = JSON.parse(fs.readFileSync(mapPath, 'utf8')) + } catch (e) { + // set to false to avoid repeatedly trying to load the map + _this.loadedMaps[hash] = false + } + } + if (_this.loadedMaps[hash]) { + _this.sourceMapCache.registerMap(absFile, _this.loadedMaps[hash]) + } + } + }) +} + +module.exports = SourceMaps diff --git a/package.json b/package.json index 562420500..3e55a28de 100644 --- a/package.json +++ b/package.json @@ -6,14 +6,13 @@ "scripts": { "bundle": "bundle-dependencies update", "pretest": "standard", - "test": "npm run cover", + "test": "npm run clean && npm run build && npm run instrument && npm run test-integration && npm run test-mocha && npm run report", "clean": "rimraf ./.nyc_output ./node_modules/.cache ./.self_coverage ./test/fixtures/.nyc_output ./test/fixtures/node_modules/.cache *covered.js ./lib/*covered.js", "build": "node ./build-tests", "instrument": "node ./build-self-coverage.js", - "run-tests": "tap -t120 --no-cov -b ./test/build/*.js ./test/src/nyc-bin.js ./test/src/process-args.js", + "test-integration": "tap -t120 --no-cov -b ./test/build/*.js && mocha --timeout=15000 ./test/src/nyc-bin.js", + "test-mocha": "node ./bin/nyc --silent --temp-directory=./.self_coverage mocha ./test/nyc.js ./test/process-args.js", "report": "node ./bin/nyc --temp-directory ./.self_coverage/ -r text -r lcov report", - "cover": "npm run clean && npm run build && npm run instrument && npm run run-tests && npm run report", - "dev": "npm run clean && npm run build && npm run run-tests", "release": "standard-version" }, "bin": { @@ -34,7 +33,9 @@ "coverage", "test/fixtures/coverage.js", "test/build/*", - "test/nyc-test.js", + "test/src/*", + "test/nyc.js", + "test/process-args.js", "index.covered.js", "test/fixtures/_generateCoverage.js" ] @@ -109,6 +110,7 @@ "forking-tap": "^0.1.1", "is-windows": "^1.0.0", "lodash": "^4.12.0", + "mocha": "^3.2.0", "newline-regex": "^0.2.1", "requirejs": "^2.3.0", "sanitize-filename": "^1.5.3", diff --git a/test/nyc.js b/test/nyc.js new file mode 100644 index 000000000..fe455e07c --- /dev/null +++ b/test/nyc.js @@ -0,0 +1,29 @@ +/* global describe, it */ + +const NYC = require('../') +require('chai').should() + +describe('NYC', function () { + describe('_disableCachingTransform', function () { + it('is disabled if cache is "false"', function () { + const nyc = new NYC({cache: false}) + nyc._disableCachingTransform().should.equal(true) + }) + + it('is enabled if cache is "true" and isChildProcess is "true"', function () { + const nyc = new NYC({ + cache: true, + isChildProcess: true + }) + nyc._disableCachingTransform().should.equal(false) + }) + + it('is disabled if cache is "true" and isChildProcess is "false"', function () { + const nyc = new NYC({ + cache: true, + isChildProcess: true + }) + nyc._disableCachingTransform().should.equal(false) + }) + }) +}) diff --git a/test/src/process-args.js b/test/process-args.js similarity index 91% rename from test/src/process-args.js rename to test/process-args.js index 1f6e7dc89..bab9806e8 100644 --- a/test/src/process-args.js +++ b/test/process-args.js @@ -1,9 +1,8 @@ /* global describe, it */ require('chai').should() -require('tap').mochaGlobals() -const processArgs = require('../../lib/process-args') +const processArgs = require('../lib/process-args') describe('process-args', function () { describe('hideInstrumenterArgs', function () { @@ -13,14 +12,14 @@ describe('process-args', function () { '--reporter', 'lcov', 'node', - 'test/nyc-test.js' + 'test/nyc-tap.js' ] var yargv = require('yargs/yargs')(process.argv.slice(2)).argv var munged = processArgs.hideInstrumenterArgs(yargv) - munged.should.eql(['node', 'test/nyc-test.js']) + munged.should.eql(['node', 'test/nyc-tap.js']) }) it('parses extra args directly after -- as Node execArgv', function () { @@ -46,7 +45,7 @@ describe('process-args', function () { '--reporter', 'lcov', 'node', - 'test/nyc-test.js', + 'test/nyc-tap.js', '--arg', '--' ] diff --git a/test/src/nyc-bin.js b/test/src/nyc-bin.js index 564c1030c..3c20a1064 100644 --- a/test/src/nyc-bin.js +++ b/test/src/nyc-bin.js @@ -13,7 +13,6 @@ var rimraf = require('rimraf') var spawn = require('child_process').spawn require('chai').should() -require('tap').mochaGlobals() // beforeEach rimraf.sync(path.resolve(fakebin, 'node')) diff --git a/test/src/nyc-test.js b/test/src/nyc-tap.js similarity index 100% rename from test/src/nyc-test.js rename to test/src/nyc-tap.js