diff --git a/.gitignore b/.gitignore index 1092015fa..d7e28ebb3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ coverage node_modules !node_modules/spawn-wrap +test/build/ +.self_coverage +*.covered.js diff --git a/.travis.yml b/.travis.yml index 43fb203a7..300f63c84 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,3 +6,6 @@ node_js: - iojs env: - secure: "SVg7NpV0Sru296kdp+eFl07RFjtJy242fWQ1KDCUdk/1EtZEOzBoSKP7Tn3zX/VLBieexL0T31EwYvRztnL97Sr8VgpYU0z95vCPO8FrixElJR6NH3dqrKeNzC3xOYdV0fy2b4UMlPJOI0aYDT1KHm1aWtkb2J8zqII+XbMtlDaelfHCDxa2+RBII9nYYDP62z+0chQFS6MGPSNwve3G2emYHZpYP5iTFmOzaFUCAjLskKvnnsY0jyx5XssqAo17747WKZl5SDgN8YHZIwhE5tB9m9j3MGjJhwdsR3kmq2om0GD1tQFFAXzWhWad3zNBUE4fLqswgASi39o5NIEzvSRzpw77ttOkkIFGem0l421Zi25W8x5n6GZvP06Y47ddmjNBlniwIzG4fb3dbIByCy/g5SjUYmfnke7stXXBKsPv0eEadlLGFWnG5RIfnyGjvUgQ//QXSAnBBzYF9IK+KUdU8c9kHF6kPybsGEzjQoX+4EJL6kZ4sNX9qxjHERUr4Jb6rAMOnKI9VtCBNqwcCC3nV5DDWHS86hKwbuTbBFkszP7majOi0kUQJTO/tZGwVVcphSDwhL5QkmMepLOqXyRICdUcB2ffXHYhZLiZPofYdom8csaDolqFkotJEBj3GM3gwHvUC3i1vxshxtjF6NHjanhpiIpHLRCs6R1RESE=" + +after_script: + - 'cat ./coverage/lcov.info | ./node_modules/.bin/coveralls' \ No newline at end of file diff --git a/bin/nyc.js b/bin/nyc.js index 94271a84c..7e60bc447 100755 --- a/bin/nyc.js +++ b/bin/nyc.js @@ -1,6 +1,12 @@ #!/usr/bin/env node var foreground = require('foreground-child') -var NYC = require('../') +var NYC +try { + NYC = require('../index.covered.js') +} catch (e) { + NYC = require('../index.js') +} + var path = require('path') var sw = require('spawn-wrap') @@ -9,11 +15,6 @@ if (process.env.NYC_CWD) { require: process.env.NYC_REQUIRE ? process.env.NYC_REQUIRE.split(',') : [] })).wrap() - // make sure we can run coverage on - // our own index.js, I like turtles. - var name = require.resolve('../') - delete require.cache[name] - sw.runMain() } else { var yargs = require('yargs') diff --git a/build-self-coverage.js b/build-self-coverage.js new file mode 100644 index 000000000..53c9345d6 --- /dev/null +++ b/build-self-coverage.js @@ -0,0 +1,22 @@ +var istanbul = require('istanbul') +var fs = require('fs') +var path = require('path') + +;[ + 'index.js', + 'lib/source-map-cache.js' +].forEach(function (name) { + var indexPath = path.join(__dirname, name) + var source = fs.readFileSync(indexPath, 'utf8') + + var instrumentor = new istanbul.Instrumenter({ + coverageVariable: '___nyc_self_coverage___', + esModules: true, + noAutoWrap: true + }) + + var instrumentedSource = instrumentor.instrumentSync(source, indexPath) + + var outputPath = path.join(__dirname, name.replace(/\.js$/, '.covered.js')) + fs.writeFileSync(outputPath, instrumentedSource) +}) diff --git a/build-tests.js b/build-tests.js new file mode 100644 index 000000000..7cc685263 --- /dev/null +++ b/build-tests.js @@ -0,0 +1,40 @@ +'use strict' + +var fs = require('fs') +var path = require('path') +var del = require('del') +var mkdirp = require('mkdirp') +var forkingTap = require('forking-tap') +var zeroFill = require('zero-fill') +var sanitizeFilename = require('sanitize-filename') + +// Delete previous files. +process.chdir(__dirname) +del.sync(['test/build']) +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 originalTestSource = fs.readFileSync(originalTestsFilename, 'utf8') +var individualTests = forkingTap(originalTestSource, { + filename: originalTestsFilename, + attachComment: true +}) + +individualTests.forEach(function (test, i) { + var filename = ['built', zeroFill(3, i)] + .concat(test.nestedName) + .join('-') + '.js' + + // file names with spaces are legal, but annoying to use w/ CLI commands + filename = filename.replace(/\s/g, '_') + + // istanbul freaks out if the there are `'` characters in the file name + filename = filename.replace(/'/g, '') + + // remove any illegal chars + filename = sanitizeFilename(filename) + + fs.writeFileSync(path.join(buildDir, filename), test.code) +}) diff --git a/index.js b/index.js index 229f2eeda..48a8a046c 100755 --- a/index.js +++ b/index.js @@ -11,6 +11,11 @@ var onExit = require('signal-exit') var stripBom = require('strip-bom') var SourceMapCache = require('./lib/source-map-cache') +/* istanbul ignore next */ +if (/index\.covered\.js$/.test(__filename)) { + require('./lib/self-coverage-helper') +} + function NYC (opts) { _.extend(this, { subprocessBin: path.resolve( diff --git a/lib/self-coverage-helper.js b/lib/self-coverage-helper.js new file mode 100644 index 000000000..dcd69bae8 --- /dev/null +++ b/lib/self-coverage-helper.js @@ -0,0 +1,20 @@ +/* global ___nyc_self_coverage___ */ + +var path = require('path') +var fs = require('fs') +var mkdirp = require('mkdirp') +var onExit = require('signal-exit') + +onExit(function () { + var coverage = global.___nyc_self_coverage___ + if (typeof ___nyc_self_coverage___ === 'object') coverage = ___nyc_self_coverage___ + if (!coverage) return + + var selfCoverageDir = path.join(__dirname, '../.self_coverage') + mkdirp.sync(selfCoverageDir) + fs.writeFileSync( + path.join(selfCoverageDir, process.pid + '.json'), + JSON.stringify(coverage), + 'utf-8' + ) +}) diff --git a/package.json b/package.json index b0d38eebe..451a31166 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,14 @@ "main": "index.js", "scripts": { "pretest": "standard", - "test": "tap --coverage ./test/*.js" + "test": "npm run cover", + "clean": "rm -rf ./.nyc_output ./.self_coverage ./test/fixtures/.nyc_output ./test/build && rm -f *covered.js ./lib/*covered.js", + "build": "node ./build-tests", + "instrument": "node ./build-self-coverage.js", + "run-tests": "tap -b ./test/build/*.js ./test/src/source-map-cache.js", + "report": "istanbul report --include=./.self_coverage/*.json lcov text", + "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" }, "bin": { "nyc": "./bin/nyc.js" @@ -17,15 +24,18 @@ "bin", "coverage", "test/fixtures/coverage.js", + "test/build/*", "test/nyc-test.js", "test/source-map-cache.js", + "index.covered.js", "test/fixtures/_generateCoverage.js" ] } }, "standard": { "ignore": [ - "**/fixtures/**" + "**/fixtures/**", + "**/test/build/*" ] }, "keywords": [ @@ -63,10 +73,16 @@ }, "devDependencies": { "chai": "^3.0.0", + "coveralls": "^2.11.4", + "del": "^2.2.0", + "forking-tap": "^0.1.1", + "sanitize-filename": "^1.5.3", "sinon": "^1.15.3", "source-map-fixtures": "^0.2.0", + "source-map-support": "^0.4.0", "standard": "^5.2.1", - "tap": "^1.3.4" + "tap": "^1.3.4", + "zero-fill": "^2.2.1" }, "bundleDependencies": [ "spawn-wrap" diff --git a/test/nyc-test.js b/test/src/nyc-test.js similarity index 89% rename from test/nyc-test.js rename to test/src/nyc-test.js index b0441721f..157ad505f 100644 --- a/test/nyc-test.js +++ b/test/src/nyc-test.js @@ -1,23 +1,31 @@ /* global describe, it */ +require('source-map-support').install() var _ = require('lodash') var fs = require('fs') -var NYC = require('../') +var NYC + +try { + NYC = require('../../index.covered.js') +} catch (e) { + NYC = require('../../') +} + var path = require('path') var rimraf = require('rimraf') var sinon = require('sinon') var spawn = require('child_process').spawn +var fixtures = path.resolve(__dirname, '../fixtures') +var bin = path.resolve(__dirname, '../../bin/nyc') require('chai').should() require('tap').mochaGlobals() describe('nyc', function () { - var fixtures = path.resolve(__dirname, './fixtures') - describe('cwd', function () { function afterEach () { delete process.env.NYC_CWD - rimraf.sync(path.resolve(fixtures, './nyc_output')) + rimraf.sync(path.resolve(fixtures, '../nyc_output')) } it('sets cwd to process.cwd() if no environment variable is set', function () { @@ -28,11 +36,11 @@ describe('nyc', function () { }) it('uses NYC_CWD environment variable for cwd if it is set', function () { - process.env.NYC_CWD = path.resolve(__dirname, './fixtures') + process.env.NYC_CWD = path.resolve(__dirname, '../fixtures') var nyc = new NYC() - nyc.cwd.should.equal(path.resolve(__dirname, './fixtures')) + nyc.cwd.should.equal(path.resolve(__dirname, '../fixtures')) afterEach() }) }) @@ -40,7 +48,7 @@ describe('nyc', function () { describe('config', function () { it("loads 'exclude' patterns from package.json", function () { var nyc = new NYC({ - cwd: path.resolve(__dirname, './fixtures') + cwd: path.resolve(__dirname, '../fixtures') }) nyc.exclude.length.should.eql(5) @@ -107,7 +115,7 @@ describe('nyc', function () { }) var shouldInstrumentFile = nyc.shouldInstrumentFile.bind(nyc) - var relPath = '../../nyc/node_modules/glob/glob.js' + var relPath = '../../../nyc/node_modules/glob/glob.js' var fullPath = '/Users/user/nyc/node_modules/glob/glob.js' shouldInstrumentFile(fullPath, relPath).should.equal(false) @@ -144,11 +152,11 @@ describe('nyc', function () { // clear the module cache so that // we pull index.js in again and wrap it. - var name = require.resolve('../') + var name = require.resolve('../../') delete require.cache[name] // when we require index.js it should be wrapped. - var index = require('../') + var index = require('../../') index.should.match(/__cov_/) }) @@ -165,14 +173,14 @@ describe('nyc', function () { // clear the module cache so that // we pull index.js in again and wrap it. - var name = require.resolve('../') + var name = require.resolve('../../') delete require.cache[name] // install the custom require hook require.extensions['.js'] = hook // when we require index.js it should be wrapped. - var index = require('../') + var index = require('../../') index.should.match(/__cov_/) // and the hook should have been called @@ -182,18 +190,18 @@ describe('nyc', function () { function testSignal (signal, done) { var nyc = (new NYC({ - cwd: process.cwd() - })).wrap() + cwd: fixtures + })) - var proc = spawn(process.execPath, ['./test/fixtures/' + signal + '.js'], { - cwd: process.cwd(), - env: process.env, - stdio: 'inherit' + var proc = spawn(process.execPath, [bin, './' + signal + '.js'], { + cwd: fixtures, + env: {}, + stdio: 'ignore' }) proc.on('close', function () { var reports = _.filter(nyc._loadReports(), function (report) { - return report['./test/fixtures/' + signal + '.js'] + return report['./' + signal + '.js'] }) reports.length.should.equal(1) return done() @@ -224,14 +232,14 @@ describe('nyc', function () { describe('report', function () { it('runs reports for all JSON in output directory', function (done) { var nyc = new NYC({ - cwd: process.cwd() + cwd: fixtures }) - var proc = spawn(process.execPath, ['./test/fixtures/sigint.js'], { - cwd: process.cwd(), - env: process.env, - stdio: 'inherit' + + var proc = spawn(process.execPath, [bin, './sigint.js'], { + cwd: fixtures, + env: {}, + stdio: 'ignore' }) - var start = fs.readdirSync(nyc.tmpDirectory()).length proc.on('close', function () { nyc.report( @@ -240,7 +248,7 @@ describe('nyc', function () { add: function (report) { // the subprocess we ran should output reports // for files in the fixtures directory. - Object.keys(report).should.match(/.\/test\/fixtures\//) + Object.keys(report).should.match(/.\/sigint\.js/) } }, { @@ -251,7 +259,7 @@ describe('nyc', function () { write: function () { // we should have output a report for the new subprocess. var stop = fs.readdirSync(nyc.tmpDirectory()).length - stop.should.be.gt(start) + stop.should.be.eql(1) return done() } } @@ -420,15 +428,15 @@ describe('nyc', function () { it('tracks coverage appropriately once the file is required', function (done) { var nyc = (new NYC({ - cwd: process.cwd() + cwd: fixtures })).wrap() - require('./fixtures/not-loaded') + require('../fixtures/not-loaded') nyc.writeCoverageFile() var reports = _.filter(nyc._loadReports(), function (report) { - return report['./test/fixtures/not-loaded.js'] + return report['./not-loaded.js'] }) - var report = reports[0]['./test/fixtures/not-loaded.js'] + var report = reports[0]['./not-loaded.js'] reports.length.should.equal(1) report.s['1'].should.equal(1) diff --git a/test/source-map-cache.js b/test/src/source-map-cache.js similarity index 91% rename from test/source-map-cache.js rename to test/src/source-map-cache.js index 5bfc96b46..2908cf327 100644 --- a/test/source-map-cache.js +++ b/test/src/source-map-cache.js @@ -14,22 +14,29 @@ var covered = _.mapValues({ return _.assign({ // Coverage for the fixture is stored relative to the root directory. Here // compute the path to the fixture file relative to the root directory. - relpath: './' + path.relative(path.join(__dirname, '..'), fixture.file), + relpath: './' + path.relative(path.join(__dirname, '../..'), fixture.file), // the sourcemap itself remaps the path. - mappedPath: './' + path.relative(path.join(__dirname, '..'), fixture.sourceFile), + mappedPath: './' + path.relative(path.join(__dirname, '../..'), fixture.sourceFile), // Compute the number of lines in the original source, excluding any line // break at the end of the file. maxLine: fixture.sourceContentSync().trimRight().split(/\r?\n/).length }, fixture) }) -var SourceMapCache = require('../lib/source-map-cache') +var SourceMapCache +try { + SourceMapCache = require('../../lib/source-map-cache.covered.js') + require('../../lib/self-coverage-helper.js') +} catch (e) { + SourceMapCache = require('../../lib/source-map-cache') +} + var sourceMapCache = new SourceMapCache() _.forOwn(covered, function (fixture) { sourceMapCache.add(fixture.relpath, fixture.contentSync()) }) -var coverage = require('./fixtures/coverage') +var coverage = require('../fixtures/coverage') var fixture = covered.inline require('chai').should()