From 11c7d55d5e8bda80cb691b2760f7c46f47242eb7 Mon Sep 17 00:00:00 2001 From: James Talmage Date: Wed, 9 Dec 2015 01:09:39 -0500 Subject: [PATCH 1/5] implementation of a test rewriter using recast This adds a build step to the test process that rewrites `test/nyc-test.js` into multiple files with one test per file. Since taps forks for each test file, this provides a pristine environment for testing our various system hooks. squashed commits: Working test suite without coverage tweak build script --- .gitignore | 1 + build-tests.js | 127 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 15 +++++- test/nyc-test.js | 43 ++++++++-------- 4 files changed, 163 insertions(+), 23 deletions(-) create mode 100644 build-tests.js diff --git a/.gitignore b/.gitignore index 1092015fa..a24022987 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ coverage node_modules !node_modules/spawn-wrap +test/built-* diff --git a/build-tests.js b/build-tests.js new file mode 100644 index 000000000..9539b00e1 --- /dev/null +++ b/build-tests.js @@ -0,0 +1,127 @@ +'use strict'; + +var fs = require('fs'); +var Path = require('path'); +var del = require('del'); +var recast = require('recast'); +var makeSourcemapComment = require('inline-source-map-comment'); +var types = recast.types; +var n = types.namedTypes; + +// Delete previous files. +process.chdir(__dirname); +del.sync(['test/built-*']); + +var outputDir = Path.join(__dirname, 'test'); +var inputPath = Path.join(outputDir, 'nyc-test.js'); +var inputSource = fs.readFileSync(inputPath, 'utf8'); + +function parse() { + return recast.parse(inputSource, { + sourceFileName: inputPath + }); +} + +function print(ast) { + var result = recast.print(rootNode(ast), { + sourceMapName: inputPath + '.map' + }); + + return result.code + '\n' + makeSourcemapComment(result.map); +} + +var i = 0; + +types.visit(parse(), { + visitExpressionStatement: function (path) { + var node = path.node; + if (isIt(node)) { + fs.writeFileSync( + Path.join(outputDir, name(path, i)), + print(copy(path)) + ); + i++; + return false; + } + this.traverse(path); + } +}); + +function copy(path) { + var copied; + if (path.parentPath) { + copied = copy(path.parentPath).get(path.name); + } else { + copied = new types.NodePath({root: parse()}); + } + + var parent = copied.parent; + var node = copied.value; + if (!(n.Node.check(node) && parent && (n.BlockStatement.check(parent.node) || n.Program.check(parent.node)))) { + return copied; + } + + var body = parent.get('body').value; + var keeper = parent.get('body', path.name).node; + + var statementIdx = 0; + + while (statementIdx < body.length) { + var statement = body[statementIdx]; + if ((isDescribe(statement) || isIt(statement)) && statement !== keeper) { + parent.get('body', statementIdx).replace(); + } else { + statementIdx++; + } + } + + return copied; +} + +function isDescribe(node) { + if (!n.ExpressionStatement.check(node)) { + return false; + } + node = node.expression; + return n.CallExpression.check(node) && n.Identifier.check(node.callee) && (node.callee.name === 'describe'); +} + +function isIt(node) { + if (!n.ExpressionStatement.check(node)) { + return false; + } + node = node.expression; + return n.CallExpression.check(node) && n.Identifier.check(node.callee) && (node.callee.name === 'it'); +} + +// Walks the path up to the root. +function rootNode(path) { + while (path.parent) { + path = path.parent; + } + return path; +} + +// Picks a file name for the test, by walking up the tree and looking at describe / require calls. +function name(path, i) { + var arr = []; + _name(path, arr); + var testName = arr.reverse().join(' '); + var filename = i + '-' + testName.replace(/\s/g, '_') + '.js'; + if (i < 100) { + filename = (i < 10 ? '00' : '0') + filename; + } + return 'built-' + filename; +} + +function _name(path, arr) { + if (!path) { + return; + } + if (isDescribe(path.node) || isIt(path.node)) { + var firstArg = path.get('expression', 'arguments', 0).node; + n.Literal.assert(firstArg); + arr.push(firstArg.value); + } + _name(path.parent, arr); +} diff --git a/package.json b/package.json index b0d38eebe..7693e45ed 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,12 @@ "description": "a code coverage tool that works well with subprocesses.", "main": "index.js", "scripts": { + "clean": "rm -rf ./.nyc_output && rm -rf ./test/fixtures/.nyc_output", + "build": "node ./build-tests", "pretest": "standard", - "test": "tap --coverage ./test/*.js" + "test": "npm run dev", + "cover": "npm run clean && npm run build && tap --coverage ./test/built-*.js ./test/source-map-cache.js", + "dev": "npm run clean && npm run build && tap -b ./test/built-*.js ./test/source-map-cache.js" }, "bin": { "nyc": "./bin/nyc.js" @@ -17,6 +21,7 @@ "bin", "coverage", "test/fixtures/coverage.js", + "test/built-*", "test/nyc-test.js", "test/source-map-cache.js", "test/fixtures/_generateCoverage.js" @@ -25,7 +30,9 @@ }, "standard": { "ignore": [ - "**/fixtures/**" + "**/fixtures/**", + "**/build-tests.js", + "**/test/built-*" ] }, "keywords": [ @@ -63,8 +70,12 @@ }, "devDependencies": { "chai": "^3.0.0", + "del": "^2.2.0", + "inline-source-map-comment": "^1.0.5", + "recast": "^0.10.39", "sinon": "^1.15.3", "source-map-fixtures": "^0.2.0", + "source-map-support": "^0.4.0", "standard": "^5.2.1", "tap": "^1.3.4" }, diff --git a/test/nyc-test.js b/test/nyc-test.js index b0441721f..5a2246e97 100644 --- a/test/nyc-test.js +++ b/test/nyc-test.js @@ -1,5 +1,6 @@ /* global describe, it */ +require('source-map-support').install() var _ = require('lodash') var fs = require('fs') var NYC = require('../') @@ -7,13 +8,13 @@ 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 @@ -38,7 +39,7 @@ describe('nyc', function () { }) describe('config', function () { - it("loads 'exclude' patterns from package.json", function () { + it('loads exclude patterns from package.json', function () { var nyc = new NYC({ cwd: path.resolve(__dirname, './fixtures') }) @@ -182,18 +183,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 +225,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 +241,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 +252,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 +421,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') 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) From ed07e703af6db3fe621e230c5ff385d46f40391d Mon Sep 17 00:00:00 2001 From: James Talmage Date: Wed, 9 Dec 2015 06:09:42 -0500 Subject: [PATCH 2/5] add self coverage add back coveralls report --- .gitignore | 2 ++ .travis.yml | 3 +++ bin/nyc.js | 13 +++++++------ index.js | 5 +++++ instrument-index.js | 22 ++++++++++++++++++++++ lib/self-coverage-helper.js | 20 ++++++++++++++++++++ package.json | 13 +++++++++---- test/nyc-test.js | 9 ++++++++- test/source-map-cache.js | 9 ++++++++- 9 files changed, 84 insertions(+), 12 deletions(-) create mode 100644 instrument-index.js create mode 100644 lib/self-coverage-helper.js diff --git a/.gitignore b/.gitignore index a24022987..a4312877f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ coverage node_modules !node_modules/spawn-wrap test/built-* +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/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/instrument-index.js b/instrument-index.js new file mode 100644 index 000000000..53c9345d6 --- /dev/null +++ b/instrument-index.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/lib/self-coverage-helper.js b/lib/self-coverage-helper.js new file mode 100644 index 000000000..a4d12e1b0 --- /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 7693e45ed..a266bf3d2 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,15 @@ "description": "a code coverage tool that works well with subprocesses.", "main": "index.js", "scripts": { - "clean": "rm -rf ./.nyc_output && rm -rf ./test/fixtures/.nyc_output", + "clean": "rm -rf ./.nyc_output && rm -rf ./test/fixtures/.nyc_output && rm -f ./index.covered.js ./lib/source-map-cache.covered.js && rm -rf ./self_coverage", "build": "node ./build-tests", "pretest": "standard", - "test": "npm run dev", - "cover": "npm run clean && npm run build && tap --coverage ./test/built-*.js ./test/source-map-cache.js", - "dev": "npm run clean && npm run build && tap -b ./test/built-*.js ./test/source-map-cache.js" + "test": "npm run cover", + "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", + "instrument": "node ./instrument-index.js", + "report": "istanbul report --include=self_coverage/*.json lcov text", + "run-tests": "tap -b ./test/built-*.js ./test/source-map-cache.js" }, "bin": { "nyc": "./bin/nyc.js" @@ -24,6 +27,7 @@ "test/built-*", "test/nyc-test.js", "test/source-map-cache.js", + "index.covered.js", "test/fixtures/_generateCoverage.js" ] } @@ -70,6 +74,7 @@ }, "devDependencies": { "chai": "^3.0.0", + "coveralls": "^2.11.4", "del": "^2.2.0", "inline-source-map-comment": "^1.0.5", "recast": "^0.10.39", diff --git a/test/nyc-test.js b/test/nyc-test.js index 5a2246e97..a8b6e2f81 100644 --- a/test/nyc-test.js +++ b/test/nyc-test.js @@ -3,7 +3,14 @@ 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') diff --git a/test/source-map-cache.js b/test/source-map-cache.js index 5bfc96b46..a8e8028dd 100644 --- a/test/source-map-cache.js +++ b/test/source-map-cache.js @@ -23,7 +23,14 @@ var covered = _.mapValues({ }, 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()) From 8d098615b307cb0a5531b60607ba49519e02fcde Mon Sep 17 00:00:00 2001 From: James Talmage Date: Wed, 9 Dec 2015 06:35:13 -0500 Subject: [PATCH 3/5] apply standard style to build-tests.js --- build-tests.js | 126 ++++++++++++++++++++++++------------------------- package.json | 1 - 2 files changed, 63 insertions(+), 64 deletions(-) diff --git a/build-tests.js b/build-tests.js index 9539b00e1..58d9fccb4 100644 --- a/build-tests.js +++ b/build-tests.js @@ -1,127 +1,127 @@ -'use strict'; +'use strict' -var fs = require('fs'); -var Path = require('path'); -var del = require('del'); -var recast = require('recast'); -var makeSourcemapComment = require('inline-source-map-comment'); -var types = recast.types; -var n = types.namedTypes; +var fs = require('fs') +var Path = require('path') +var del = require('del') +var recast = require('recast') +var makeSourcemapComment = require('inline-source-map-comment') +var types = recast.types +var n = types.namedTypes // Delete previous files. -process.chdir(__dirname); -del.sync(['test/built-*']); +process.chdir(__dirname) +del.sync(['test/built-*']) -var outputDir = Path.join(__dirname, 'test'); -var inputPath = Path.join(outputDir, 'nyc-test.js'); -var inputSource = fs.readFileSync(inputPath, 'utf8'); +var outputDir = Path.join(__dirname, 'test') +var inputPath = Path.join(outputDir, 'nyc-test.js') +var inputSource = fs.readFileSync(inputPath, 'utf8') -function parse() { +function parse () { return recast.parse(inputSource, { sourceFileName: inputPath - }); + }) } -function print(ast) { +function print (ast) { var result = recast.print(rootNode(ast), { sourceMapName: inputPath + '.map' - }); + }) - return result.code + '\n' + makeSourcemapComment(result.map); + return result.code + '\n' + makeSourcemapComment(result.map) } -var i = 0; +var i = 0 types.visit(parse(), { visitExpressionStatement: function (path) { - var node = path.node; + var node = path.node if (isIt(node)) { fs.writeFileSync( Path.join(outputDir, name(path, i)), print(copy(path)) - ); - i++; - return false; + ) + i++ + return false } - this.traverse(path); + this.traverse(path) } -}); +}) -function copy(path) { - var copied; +function copy (path) { + var copied if (path.parentPath) { - copied = copy(path.parentPath).get(path.name); + copied = copy(path.parentPath).get(path.name) } else { - copied = new types.NodePath({root: parse()}); + copied = new types.NodePath({root: parse()}) } - var parent = copied.parent; - var node = copied.value; + var parent = copied.parent + var node = copied.value if (!(n.Node.check(node) && parent && (n.BlockStatement.check(parent.node) || n.Program.check(parent.node)))) { - return copied; + return copied } - var body = parent.get('body').value; - var keeper = parent.get('body', path.name).node; + var body = parent.get('body').value + var keeper = parent.get('body', path.name).node - var statementIdx = 0; + var statementIdx = 0 while (statementIdx < body.length) { - var statement = body[statementIdx]; + var statement = body[statementIdx] if ((isDescribe(statement) || isIt(statement)) && statement !== keeper) { - parent.get('body', statementIdx).replace(); + parent.get('body', statementIdx).replace() } else { - statementIdx++; + statementIdx++ } } - return copied; + return copied } -function isDescribe(node) { +function isDescribe (node) { if (!n.ExpressionStatement.check(node)) { - return false; + return false } - node = node.expression; - return n.CallExpression.check(node) && n.Identifier.check(node.callee) && (node.callee.name === 'describe'); + node = node.expression + return n.CallExpression.check(node) && n.Identifier.check(node.callee) && (node.callee.name === 'describe') } -function isIt(node) { +function isIt (node) { if (!n.ExpressionStatement.check(node)) { - return false; + return false } - node = node.expression; - return n.CallExpression.check(node) && n.Identifier.check(node.callee) && (node.callee.name === 'it'); + node = node.expression + return n.CallExpression.check(node) && n.Identifier.check(node.callee) && (node.callee.name === 'it') } // Walks the path up to the root. -function rootNode(path) { +function rootNode (path) { while (path.parent) { - path = path.parent; + path = path.parent } - return path; + return path } // Picks a file name for the test, by walking up the tree and looking at describe / require calls. -function name(path, i) { - var arr = []; - _name(path, arr); - var testName = arr.reverse().join(' '); - var filename = i + '-' + testName.replace(/\s/g, '_') + '.js'; +function name (path, i) { + var arr = [] + _name(path, arr) + var testName = arr.reverse().join(' ') + var filename = i + '-' + testName.replace(/\s/g, '_') + '.js' if (i < 100) { - filename = (i < 10 ? '00' : '0') + filename; + filename = (i < 10 ? '00' : '0') + filename } - return 'built-' + filename; + return 'built-' + filename } -function _name(path, arr) { +function _name (path, arr) { if (!path) { - return; + return } if (isDescribe(path.node) || isIt(path.node)) { - var firstArg = path.get('expression', 'arguments', 0).node; - n.Literal.assert(firstArg); - arr.push(firstArg.value); + var firstArg = path.get('expression', 'arguments', 0).node + n.Literal.assert(firstArg) + arr.push(firstArg.value) } - _name(path.parent, arr); + _name(path.parent, arr) } diff --git a/package.json b/package.json index a266bf3d2..7b8eac644 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,6 @@ "standard": { "ignore": [ "**/fixtures/**", - "**/build-tests.js", "**/test/built-*" ] }, From b4cc1e6595b7348699771647c3d0bd7699fcc29f Mon Sep 17 00:00:00 2001 From: James Talmage Date: Thu, 10 Dec 2015 14:01:50 -0500 Subject: [PATCH 4/5] extract forking-tap module --- build-tests.js | 134 ++++++++--------------------------------------- package.json | 7 +-- test/nyc-test.js | 2 +- 3 files changed, 27 insertions(+), 116 deletions(-) diff --git a/build-tests.js b/build-tests.js index 58d9fccb4..34e9feb24 100644 --- a/build-tests.js +++ b/build-tests.js @@ -1,127 +1,37 @@ 'use strict' var fs = require('fs') -var Path = require('path') +var path = require('path') var del = require('del') -var recast = require('recast') -var makeSourcemapComment = require('inline-source-map-comment') -var types = recast.types -var n = types.namedTypes +var forkingTap = require('forking-tap') +var zeroFill = require('zero-fill') +var sanitizeFilename = require('sanitize-filename') // Delete previous files. process.chdir(__dirname) del.sync(['test/built-*']) -var outputDir = Path.join(__dirname, 'test') -var inputPath = Path.join(outputDir, 'nyc-test.js') -var inputSource = fs.readFileSync(inputPath, 'utf8') - -function parse () { - return recast.parse(inputSource, { - sourceFileName: inputPath - }) -} - -function print (ast) { - var result = recast.print(rootNode(ast), { - sourceMapName: inputPath + '.map' - }) - - return result.code + '\n' + makeSourcemapComment(result.map) -} - -var i = 0 - -types.visit(parse(), { - visitExpressionStatement: function (path) { - var node = path.node - if (isIt(node)) { - fs.writeFileSync( - Path.join(outputDir, name(path, i)), - print(copy(path)) - ) - i++ - return false - } - this.traverse(path) - } +var testDir = path.join(__dirname, 'test') +var originalTestsFilename = path.join(testDir, 'nyc-test.js') +var originalTestSource = fs.readFileSync(originalTestsFilename, 'utf8') +var individualTests = forkingTap(originalTestSource, { + filename: originalTestsFilename, + attachComment: true }) -function copy (path) { - var copied - if (path.parentPath) { - copied = copy(path.parentPath).get(path.name) - } else { - copied = new types.NodePath({root: parse()}) - } - - var parent = copied.parent - var node = copied.value - if (!(n.Node.check(node) && parent && (n.BlockStatement.check(parent.node) || n.Program.check(parent.node)))) { - return copied - } +individualTests.forEach(function (test, i) { + var filename = ['built', zeroFill(3, i)] + .concat(test.nestedName) + .join('-') + '.js' - var body = parent.get('body').value - var keeper = parent.get('body', path.name).node + // file names with spaces are legal, but annoying to use w/ CLI commands + filename = filename.replace(/\s/g, '_') - var statementIdx = 0 + // istanbul freaks out if the there are `'` characters in the file name + filename = filename.replace(/'/g, '') - while (statementIdx < body.length) { - var statement = body[statementIdx] - if ((isDescribe(statement) || isIt(statement)) && statement !== keeper) { - parent.get('body', statementIdx).replace() - } else { - statementIdx++ - } - } + // remove any illegal chars + filename = sanitizeFilename(filename) - return copied -} - -function isDescribe (node) { - if (!n.ExpressionStatement.check(node)) { - return false - } - node = node.expression - return n.CallExpression.check(node) && n.Identifier.check(node.callee) && (node.callee.name === 'describe') -} - -function isIt (node) { - if (!n.ExpressionStatement.check(node)) { - return false - } - node = node.expression - return n.CallExpression.check(node) && n.Identifier.check(node.callee) && (node.callee.name === 'it') -} - -// Walks the path up to the root. -function rootNode (path) { - while (path.parent) { - path = path.parent - } - return path -} - -// Picks a file name for the test, by walking up the tree and looking at describe / require calls. -function name (path, i) { - var arr = [] - _name(path, arr) - var testName = arr.reverse().join(' ') - var filename = i + '-' + testName.replace(/\s/g, '_') + '.js' - if (i < 100) { - filename = (i < 10 ? '00' : '0') + filename - } - return 'built-' + filename -} - -function _name (path, arr) { - if (!path) { - return - } - if (isDescribe(path.node) || isIt(path.node)) { - var firstArg = path.get('expression', 'arguments', 0).node - n.Literal.assert(firstArg) - arr.push(firstArg.value) - } - _name(path.parent, arr) -} + fs.writeFileSync(path.join(testDir, filename), test.code) +}) diff --git a/package.json b/package.json index 7b8eac644..4bc55d2f2 100644 --- a/package.json +++ b/package.json @@ -75,13 +75,14 @@ "chai": "^3.0.0", "coveralls": "^2.11.4", "del": "^2.2.0", - "inline-source-map-comment": "^1.0.5", - "recast": "^0.10.39", + "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/nyc-test.js index a8b6e2f81..0b9b9512d 100644 --- a/test/nyc-test.js +++ b/test/nyc-test.js @@ -46,7 +46,7 @@ describe('nyc', function () { }) describe('config', function () { - it('loads exclude patterns from package.json', function () { + it("loads 'exclude' patterns from package.json", function () { var nyc = new NYC({ cwd: path.resolve(__dirname, './fixtures') }) From 3eb8cbf6dd23fe298a08eeb7c3269ed5ae647975 Mon Sep 17 00:00:00 2001 From: James Talmage Date: Thu, 10 Dec 2015 14:19:34 -0500 Subject: [PATCH 5/5] minor fixups / filename changes squashed commits: rename instrument-index to build-self-coverage (more accurate description) add leading `.` to self_coverage output directory minor fixups to npm scripts fix `npm run report` - it was using the old coverage folder name move built tests to test/build/, originals sources to test/src fix test script --- .gitignore | 4 +-- instrument-index.js => build-self-coverage.js | 0 build-tests.js | 9 ++++-- lib/self-coverage-helper.js | 2 +- package.json | 16 +++++------ test/{ => src}/nyc-test.js | 28 +++++++++---------- test/{ => src}/source-map-cache.js | 12 ++++---- 7 files changed, 37 insertions(+), 34 deletions(-) rename instrument-index.js => build-self-coverage.js (100%) rename test/{ => src}/nyc-test.js (94%) rename test/{ => src}/source-map-cache.js (92%) diff --git a/.gitignore b/.gitignore index a4312877f..d7e28ebb3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,6 @@ coverage node_modules !node_modules/spawn-wrap -test/built-* -self_coverage +test/build/ +.self_coverage *.covered.js diff --git a/instrument-index.js b/build-self-coverage.js similarity index 100% rename from instrument-index.js rename to build-self-coverage.js diff --git a/build-tests.js b/build-tests.js index 34e9feb24..7cc685263 100644 --- a/build-tests.js +++ b/build-tests.js @@ -3,15 +3,18 @@ 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/built-*']) +del.sync(['test/build']) +mkdirp.sync(path.join(__dirname, 'test/build')) -var testDir = path.join(__dirname, 'test') +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, { @@ -33,5 +36,5 @@ individualTests.forEach(function (test, i) { // remove any illegal chars filename = sanitizeFilename(filename) - fs.writeFileSync(path.join(testDir, filename), test.code) + fs.writeFileSync(path.join(buildDir, filename), test.code) }) diff --git a/lib/self-coverage-helper.js b/lib/self-coverage-helper.js index a4d12e1b0..dcd69bae8 100644 --- a/lib/self-coverage-helper.js +++ b/lib/self-coverage-helper.js @@ -10,7 +10,7 @@ onExit(function () { if (typeof ___nyc_self_coverage___ === 'object') coverage = ___nyc_self_coverage___ if (!coverage) return - var selfCoverageDir = path.join(__dirname, '../self_coverage') + var selfCoverageDir = path.join(__dirname, '../.self_coverage') mkdirp.sync(selfCoverageDir) fs.writeFileSync( path.join(selfCoverageDir, process.pid + '.json'), diff --git a/package.json b/package.json index 4bc55d2f2..451a31166 100644 --- a/package.json +++ b/package.json @@ -4,15 +4,15 @@ "description": "a code coverage tool that works well with subprocesses.", "main": "index.js", "scripts": { - "clean": "rm -rf ./.nyc_output && rm -rf ./test/fixtures/.nyc_output && rm -f ./index.covered.js ./lib/source-map-cache.covered.js && rm -rf ./self_coverage", - "build": "node ./build-tests", "pretest": "standard", "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", - "instrument": "node ./instrument-index.js", - "report": "istanbul report --include=self_coverage/*.json lcov text", - "run-tests": "tap -b ./test/built-*.js ./test/source-map-cache.js" + "dev": "npm run clean && npm run build && npm run run-tests" }, "bin": { "nyc": "./bin/nyc.js" @@ -24,7 +24,7 @@ "bin", "coverage", "test/fixtures/coverage.js", - "test/built-*", + "test/build/*", "test/nyc-test.js", "test/source-map-cache.js", "index.covered.js", @@ -35,7 +35,7 @@ "standard": { "ignore": [ "**/fixtures/**", - "**/test/built-*" + "**/test/build/*" ] }, "keywords": [ diff --git a/test/nyc-test.js b/test/src/nyc-test.js similarity index 94% rename from test/nyc-test.js rename to test/src/nyc-test.js index 0b9b9512d..157ad505f 100644 --- a/test/nyc-test.js +++ b/test/src/nyc-test.js @@ -6,17 +6,17 @@ var fs = require('fs') var NYC try { - NYC = require('../index.covered.js') + NYC = require('../../index.covered.js') } catch (e) { - NYC = require('../') + 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') +var fixtures = path.resolve(__dirname, '../fixtures') +var bin = path.resolve(__dirname, '../../bin/nyc') require('chai').should() require('tap').mochaGlobals() @@ -25,7 +25,7 @@ describe('nyc', function () { 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 () { @@ -36,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() }) }) @@ -48,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) @@ -115,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) @@ -152,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_/) }) @@ -173,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 @@ -430,7 +430,7 @@ describe('nyc', function () { var nyc = (new NYC({ cwd: fixtures })).wrap() - require('./fixtures/not-loaded') + require('../fixtures/not-loaded') nyc.writeCoverageFile() var reports = _.filter(nyc._loadReports(), function (report) { diff --git a/test/source-map-cache.js b/test/src/source-map-cache.js similarity index 92% rename from test/source-map-cache.js rename to test/src/source-map-cache.js index a8e8028dd..2908cf327 100644 --- a/test/source-map-cache.js +++ b/test/src/source-map-cache.js @@ -14,9 +14,9 @@ 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 @@ -25,10 +25,10 @@ var covered = _.mapValues({ var SourceMapCache try { - SourceMapCache = require('../lib/source-map-cache.covered.js') - require('../lib/self-coverage-helper.js') + SourceMapCache = require('../../lib/source-map-cache.covered.js') + require('../../lib/self-coverage-helper.js') } catch (e) { - SourceMapCache = require('../lib/source-map-cache') + SourceMapCache = require('../../lib/source-map-cache') } var sourceMapCache = new SourceMapCache() @@ -36,7 +36,7 @@ _.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()