├── test ├── fixtures │ ├── not-loaded.js │ ├── .istanbul.yml │ ├── sigint.js │ ├── sigterm.js │ └── package.json └── nyc-test.js ├── .gitignore ├── LICENSE.txt ├── .travis.yml ├── package.json ├── bin └── nyc.js ├── README.md ├── CHANGELOG.md └── index.js /test/fixtures/not-loaded.js: -------------------------------------------------------------------------------- 1 | var i = 3 + 5 2 | i++ 3 | -------------------------------------------------------------------------------- /test/fixtures/.istanbul.yml: -------------------------------------------------------------------------------- 1 | instrumentation: 2 | preserve-comments: true 3 | -------------------------------------------------------------------------------- /test/fixtures/sigint.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | process.kill(process.pid, 'SIGINT') 4 | -------------------------------------------------------------------------------- /test/fixtures/sigterm.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | process.kill(process.pid, 'SIGTERM') 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output 2 | coverage 3 | node_modules 4 | !node_modules/spawn-wrap 5 | !node_modules/foreground-child 6 | -------------------------------------------------------------------------------- /test/fixtures/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nyc", 3 | "version": "1.1.1", 4 | "description": "forking code-coverage using istanbul.", 5 | "main": "index.js", 6 | "bin": { 7 | "nyc": "./bin/nyc.js", 8 | "nyc-report": "./bin/nyc-report.js" 9 | }, 10 | "config": { 11 | "nyc": { 12 | "exclude": [ 13 | "node_modules/", 14 | "blarg/", 15 | "blerg/" 16 | ] 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Contributors 2 | 3 | Permission to use, copy, modify, and/or distribute this software 4 | for any purpose with or without fee is hereby granted, provided 5 | that the above copyright notice and this permission notice 6 | appear in all copies. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES 10 | OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE 11 | LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES 12 | OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 13 | WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 14 | ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '0.12' 5 | - '0.10' 6 | - iojs 7 | env: 8 | - secure: "SVg7NpV0Sru296kdp+eFl07RFjtJy242fWQ1KDCUdk/1EtZEOzBoSKP7Tn3zX/VLBieexL0T31EwYvRztnL97Sr8VgpYU0z95vCPO8FrixElJR6NH3dqrKeNzC3xOYdV0fy2b4UMlPJOI0aYDT1KHm1aWtkb2J8zqII+XbMtlDaelfHCDxa2+RBII9nYYDP62z+0chQFS6MGPSNwve3G2emYHZpYP5iTFmOzaFUCAjLskKvnnsY0jyx5XssqAo17747WKZl5SDgN8YHZIwhE5tB9m9j3MGjJhwdsR3kmq2om0GD1tQFFAXzWhWad3zNBUE4fLqswgASi39o5NIEzvSRzpw77ttOkkIFGem0l421Zi25W8x5n6GZvP06Y47ddmjNBlniwIzG4fb3dbIByCy/g5SjUYmfnke7stXXBKsPv0eEadlLGFWnG5RIfnyGjvUgQ//QXSAnBBzYF9IK+KUdU8c9kHF6kPybsGEzjQoX+4EJL6kZ4sNX9qxjHERUr4Jb6rAMOnKI9VtCBNqwcCC3nV5DDWHS86hKwbuTbBFkszP7majOi0kUQJTO/tZGwVVcphSDwhL5QkmMepLOqXyRICdUcB2ffXHYhZLiZPofYdom8csaDolqFkotJEBj3GM3gwHvUC3i1vxshxtjF6NHjanhpiIpHLRCs6R1RESE=" 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nyc", 3 | "version": "3.2.2", 4 | "description": "a code coverage tool that works well with subprocesses.", 5 | "main": "index.js", 6 | "scripts": { 7 | "istanbul": "istanbul", 8 | "test": "standard && tap --coverage ./test/nyc-test.js" 9 | }, 10 | "bin": { 11 | "nyc": "./bin/nyc.js" 12 | }, 13 | "config": { 14 | "nyc": { 15 | "exclude": [ 16 | "node_modules", 17 | "bin" 18 | ] 19 | } 20 | }, 21 | "keywords": [ 22 | "coverage", 23 | "reporter", 24 | "subprocess", 25 | "testing" 26 | ], 27 | "contributors": [ 28 | { 29 | "name": "Isaac Schlueter", 30 | "website": "https://github.com/isaacs" 31 | }, 32 | { 33 | "name": "Ollie Buck", 34 | "website": "https://github.com/shackpank" 35 | } 36 | ], 37 | "author": "Ben Coe ", 38 | "license": "ISC", 39 | "dependencies": { 40 | "foreground-child": "1.3.0", 41 | "glob": "^5.0.14", 42 | "istanbul": "^0.3.19", 43 | "lodash": "^3.10.0", 44 | "mkdirp": "^0.5.0", 45 | "rimraf": "^2.4.2", 46 | "signal-exit": "^2.1.1", 47 | "spawn-wrap": "^1.0.1", 48 | "strip-bom": "^2.0.0", 49 | "yargs": "^3.15.0" 50 | }, 51 | "devDependencies": { 52 | "chai": "^3.0.0", 53 | "sinon": "^1.15.3", 54 | "standard": "^5.2.1", 55 | "tap": "^1.3.4" 56 | }, 57 | "bundleDependencies": [ 58 | "foreground-child", 59 | "spawn-wrap" 60 | ], 61 | "repository": { 62 | "type": "git", 63 | "url": "git@github.com:bcoe/nyc.git" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /bin/nyc.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var foreground = require('foreground-child') 3 | var NYC = require('../') 4 | var path = require('path') 5 | var sw = require('spawn-wrap') 6 | 7 | if (process.env.NYC_CWD) { 8 | ;(new NYC()).wrap() 9 | 10 | // make sure we can run coverage on 11 | // our own index.js, I like turtles. 12 | var name = require.resolve('../') 13 | delete require.cache[name] 14 | 15 | sw.runMain() 16 | } else { 17 | var yargs = require('yargs') 18 | .usage('$0 [command] [options]\n\nrun your tests with the nyc bin to instrument them with coverage') 19 | .command('report', 'run coverage report for .nyc_output', function (yargs) { 20 | yargs 21 | .usage('$0 report [options]') 22 | .option('r', { 23 | alias: 'reporter', 24 | describe: 'coverage reporter(s) to use', 25 | default: 'text' 26 | }) 27 | .help('h') 28 | .alias('h', 'help') 29 | .example('$0 report --reporter=lcov', 'output an HTML lcov report to ./coverage') 30 | }) 31 | .command('check-coverage', 'check whether coverage is within thresholds provided', function (yargs) { 32 | yargs 33 | .usage('$0 check-coverage [options]') 34 | .option('b', { 35 | alias: 'branches', 36 | default: 0, 37 | description: 'what % of branches must be covered?' 38 | }) 39 | .option('f', { 40 | alias: 'functions', 41 | default: 0, 42 | description: 'what % of functions must be covered?' 43 | }) 44 | .option('l', { 45 | alias: 'lines', 46 | default: 90, 47 | description: 'what % of lines must be covered?' 48 | }) 49 | .option('s', { 50 | alias: 'statements', 51 | default: 0, 52 | description: 'what % of statements must be covered?' 53 | }) 54 | .help('h') 55 | .alias('h', 'help') 56 | .example('$0 check-coverage --lines 95', "check whether the JSON in nyc's output folder meets the thresholds provided") 57 | }) 58 | .option('r', { 59 | alias: 'reporter', 60 | describe: 'coverage reporter(s) to use', 61 | default: 'text' 62 | }) 63 | .option('s', { 64 | alias: 'silent', 65 | default: false, 66 | type: 'boolean', 67 | describe: "don't output a report after tests finish running" 68 | }) 69 | .option('a', { 70 | alias: 'all', 71 | default: false, 72 | type: 'boolean', 73 | describe: 'whether or not to instrument all files of the project (not just the ones touched by your test suite)' 74 | }) 75 | .help('h') 76 | .alias('h', 'help') 77 | .version(require('../package.json').version) 78 | .example('$0 npm test', 'instrument your tests with coverage') 79 | .example('$0 report --reporter=text-lcov', 'output lcov report after running your tests') 80 | .epilog('visit http://git.io/vTJJB for list of available reporters') 81 | var argv = yargs.argv 82 | 83 | if (~argv._.indexOf('report')) { 84 | // run a report. 85 | process.env.NYC_CWD = process.cwd() 86 | 87 | report(argv) 88 | } else if (~argv._.indexOf('check-coverage')) { 89 | foreground( 90 | process.execPath, 91 | [ 92 | require.resolve('istanbul/lib/cli'), 93 | 'check-coverage', 94 | '--lines=' + argv.lines, 95 | '--functions=' + argv.functions, 96 | '--branches=' + argv.branches, 97 | '--statements=' + argv.statements, 98 | path.resolve(process.cwd(), './.nyc_output/*.json') 99 | ] 100 | ) 101 | } else if (argv._.length) { 102 | // wrap subprocesses and execute argv[1] 103 | var nyc = (new NYC()) 104 | nyc.cleanup() 105 | 106 | if (argv.all) nyc.addAllFiles() 107 | 108 | sw([__filename], { 109 | NYC_CWD: process.cwd() 110 | }) 111 | 112 | foreground(nyc.mungeArgs(argv), function (done) { 113 | if (!argv.silent) report(argv) 114 | return done() 115 | }) 116 | } else { 117 | // I don't have a clue what you're doing. 118 | yargs.showHelp() 119 | } 120 | } 121 | 122 | function report (argv) { 123 | process.env.NYC_CWD = process.cwd() 124 | 125 | ;(new NYC({ 126 | reporter: argv.reporter 127 | })).report() 128 | } 129 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nyc 2 | 3 | [![Build Status](https://travis-ci.org/bcoe/nyc.png)](https://travis-ci.org/bcoe/nyc) 4 | [![Coverage Status](https://coveralls.io/repos/bcoe/nyc/badge.svg?branch=)](https://coveralls.io/r/bcoe/nyc?branch=) 5 | [![NPM version](https://img.shields.io/npm/v/nyc.svg)](https://www.npmjs.com/package/nyc) 6 | 7 | ```shell 8 | nyc npm test 9 | ``` 10 | 11 | a code coverage tool built on [istanbul](https://www.npmjs.com/package/istanbul) 12 | that works for applications that spawn subprocesses. 13 | 14 | ## Instrumenting Your Code 15 | 16 | Simply run your tests with `nyc`, and it will collect coverage information for 17 | each process and store it in `.nyc_output`. 18 | 19 | ```shell 20 | nyc npm test 21 | ``` 22 | 23 | you can pass a list of Istanbul reporters that you'd like to run: 24 | 25 | ```shell 26 | nyc --reporter=lcov --reporter=text-lcov npm test 27 | ``` 28 | 29 | If you're so inclined, you can simply add nyc to the test stanza in your package.json: 30 | 31 | ```json 32 | { 33 | "script": { 34 | "test": "nyc tap ./test/*.js" 35 | } 36 | } 37 | ``` 38 | 39 | ## Checking Coverage 40 | 41 | nyc exposes istanbul's check-coverage tool. After running your tests with nyc, 42 | simply run: 43 | 44 | ```shell 45 | nyc check-coverage --lines 95 --functions 95 --branches 95 46 | ``` 47 | 48 | This feature makes it easy to fail your tests if coverage drops below a given threshold. 49 | 50 | ## Running Reports 51 | 52 | Once you've run your tests with nyc, simply run: 53 | 54 | ```bash 55 | nyc report 56 | ``` 57 | 58 | To view your coverage report: 59 | 60 | ```shell 61 | --------------------|-----------|-----------|-----------|-----------| 62 | File | % Stmts |% Branches | % Funcs | % Lines | 63 | --------------------|-----------|-----------|-----------|-----------| 64 | ./ | 85.96 | 50 | 75 | 92.31 | 65 | index.js | 85.96 | 50 | 75 | 92.31 | 66 | ./test/ | 98.08 | 50 | 95 | 98.04 | 67 | nyc-test.js | 98.08 | 50 | 95 | 98.04 | 68 | ./test/fixtures/ | 100 | 100 | 100 | 100 | 69 | sigint.js | 100 | 100 | 100 | 100 | 70 | sigterm.js | 100 | 100 | 100 | 100 | 71 | --------------------|-----------|-----------|-----------|-----------| 72 | All files | 91.89 | 50 | 86.11 | 95.24 | 73 | --------------------|-----------|-----------|-----------|-----------| 74 | ``` 75 | 76 | you can use any reporters that are supported by istanbul: 77 | 78 | ```bash 79 | nyc report --reporter=lcov 80 | ``` 81 | 82 | ## Including and Excluding Files 83 | 84 | By default nyc does not instrument files in `node_modules`, or `test` 85 | for coverage. You can override this setting in your package.json, by 86 | adding the following configuration: 87 | 88 | ```js 89 | {"config": { 90 | "nyc": { 91 | "exclude": [ 92 | "node_modules/" 93 | ] 94 | } 95 | }} 96 | ``` 97 | 98 | or if you have some directory/files in `node_modules` you wish to include 99 | you can add the following configuration as well: 100 | 101 | ```js 102 | {"config": { 103 | "nyc": { 104 | "include": [ 105 | "node_modules/utils/" 106 | ] 107 | } 108 | }} 109 | ``` 110 | 111 | 112 | ## Include Reports For Files That Are Not Required 113 | 114 | By default nyc does not collect coverage for files that have not 115 | been required, run nyc with the flag `--all` to enable this. 116 | 117 | ## Configuring Istanbul 118 | 119 | Behind the scenes nyc uses [istanbul](https://www.npmjs.com/package/istanbul). You 120 | can place a `.istanbul.yml` file in your project's root directory to pass config 121 | setings to istanbul's code instrumenter: 122 | 123 | ```yml 124 | instrumentation: 125 | preserve-comments: true 126 | ``` 127 | 128 | ## Integrating With Coveralls 129 | 130 | [coveralls.io](https://coveralls.io) is a great tool for adding 131 | coverage reports to your GitHub project. Here's how to get nyc 132 | integrated with coveralls and travis-ci.org: 133 | 134 | 1. add the coveralls and nyc dependencies to your module: 135 | 136 | ```shell 137 | npm install coveralls nyc --save 138 | ``` 139 | 140 | 2. update the scripts in your package.json to include these bins: 141 | 142 | ```bash 143 | { 144 | "script": { 145 | "test": "nyc tap ./test/*.js", 146 | "coverage": "nyc report --reporter=text-lcov | coveralls", 147 | } 148 | } 149 | ``` 150 | 151 | 3. add the environment variable `COVERALLS_REPO_TOKEN` to travis, this is used by 152 | the coveralls bin. 153 | 154 | 4. add the following to your `.travis.yml`: 155 | 156 | ```yaml 157 | after_success: npm run coverage 158 | ``` 159 | 160 | That's all there is to it! 161 | 162 | _Note: by default coveralls.io adds comments to pull-requests on GitHub, this can 163 | feel intrusive. To disable this, click on your repo on coveralls.io and uncheck `LEAVE COMMENTS?`._ 164 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Change Log 2 | 3 | ### v3.2.2 (2015/09/11 22:02 -07:00) 4 | 5 | - [#47](https://github.com/bcoe/nyc/pull/47) make the default exclude rules work on Windows (@bcoe) 6 | - [#45](https://github.com/bcoe/nyc/pull/45) pull in patched versions of spawn-wrap and foreground-child, which support Windows (@bcoe) 7 | - [#44](https://github.com/bcoe/nyc/pull/44) Adds --all option which adds 0% coverage reports for all files in project, regardless of whether code touches them (@ronkorving) 8 | 9 | ### v3.1.0 (2015/08/02 19:04 +00:00) 10 | 11 | - [#38](https://github.com/bcoe/nyc/pull/38) fixes for windows spawning (@rmg) 12 | 13 | ### v3.0.1 (2015/07/25 20:51 +00:00) 14 | - [#33](https://github.com/bcoe/nyc/pull/33) spawn istanbul in a way that is less likely to break npm@3.x (@bcoe) 15 | 16 | ### v3.0.0 (2015/06/28 19:49 +00:00) 17 | 18 | - [#31](https://github.com/bcoe/nyc/pull/31) Combine instrumentation and reporting steps, based 19 | on @Raynos' suggestion (@bcoe) 20 | 21 | ### v2.4.0 (2015/06/24 15:57 +00:00) 22 | - [#30](https://github.com/bcoe/nyc/pull/30) Added check-coverage functionality, thanks 23 | @Raynos! (@bcoe) 24 | 25 | ### v2.3.0 (2015/06/04 06:43 +00:00) 26 | - [#27](https://github.com/bcoe/nyc/pull/27) upgraded tap, and switched tests to using tap --coverage (@bcoe) 27 | - [#25](https://github.com/bcoe/nyc/pull/25) support added for multiple reporters, thanks @jasisk! (@jasisk) 28 | 29 | ### v2.2.0 (2015/05/25 21:05 +00:00) 30 | - [b2e4707](https://github.com/bcoe/nyc/commit/b2e4707ca16750fe274f61039baf1cabdd6b0149) change location of nyc_output to .nyc_output. Added note about coveralls comments. (@sindresorhus) 31 | 32 | ### v2.1.3 (2015/05/25 06:30 +00:00) 33 | - [376e328](https://github.com/bcoe/nyc/commit/376e32871d2d65ca31e7d8ba691293ac3ba6117e) handle corrupt JSON files in nyc_output (@bcoe) 34 | 35 | ### v2.1.1 (2015/05/25 02:52 +00:00) 36 | - [b39dec5](https://github.com/bcoe/nyc/commit/b39dec5a7fb9004be72d024d5d1df2984dd21a52) new signal-exit handles process.exit() in process.on('exit') (@isaacs) 37 | 38 | ### v2.1.0 (2015/05/23 20:55 +00:00) 39 | - [ad13b30](https://github.com/bcoe/nyc/commit/ad13b30cf263ccc3607e1707ebdf582345ce90fe) added CHANGELOG.md \o/ (@bcoe) 40 | - [53fef48](https://github.com/bcoe/nyc/commit/53fef4820e7b502d00561fb5d16f5bfb4b641192) put tests around @shackpank's work on .istanbul.yml (@bcoe) 41 | - [da81c54](https://github.com/bcoe/nyc/commit/da81c5427c2dee38496def9741fdde5524fa0942) upgrade spawn-wrap and foreground-child (@isaacs) 42 | - [4f69327](https://github.com/bcoe/nyc/commit/4f69327b5e6247770bf299fab86abb67a042b26a) pin tap until new version of nyc can be pulled in (@bcoe) 43 | 44 | ### v2.0.6 (2015/05/23 06:52 +00:00) 45 | - [cd70a41](https://github.com/bcoe/nyc/commit/cd70a414adc12b79770eaca9e8ca0e5f954924f3) upgrade signal-exit (@bcoe) 46 | 47 | ### v2.0.5 (2015/05/20 05:44 +00:00) 48 | - [#11](https://github.com/bcoe/nyc/pull/11) Merge pull request #11 from bcoe/exlude-docs (@bcoe) 49 | 50 | ### v2.0.4 (2015/05/19 04:58 +00:00) 51 | - [4d920ef](https://github.com/bcoe/nyc/commit/4d920ef6e0843729a911ca1cf6deaf6645e21f60) ensure that writing code coverage always happens last (@bcoe) 52 | 53 | ### v2.0.3 (2015/05/18 01:52 +00:00) 54 | - [94d2693](https://github.com/bcoe/nyc/commit/94d2693739cf7145333d941c88e0d3af9592c1d6) spawn-wrap@0.1.1 (@isaacs) 55 | 56 | ### v2.0.1 (2015/05/18 01:46 +00:00) 57 | - [62c2cb0](https://github.com/bcoe/nyc/commit/62c2cb0941fbda8aa5ef6ba4877c02a046b68c6c) upgrade signal-exit dependency (@bcoe) 58 | 59 | ### v2.0.0 (2015/05/16 21:38 +00:00) 60 | - [d27794e](https://github.com/bcoe/nyc/commit/d27794e3c527ccf743501f328b9749f1bcf9cefe) got rid of nyc-report bin (@bcoe) 61 | - [64c9824](https://github.com/bcoe/nyc/commit/64c98241db36331b611cf990343da40d5f45685a) added better documentation and CLI. (@bcoe) 62 | 63 | ### v1.4.1 (2015/05/16 19:23 +00:00) 64 | - [ae05346](https://github.com/bcoe/nyc/commit/ae0534617a59c86905f1da290d067945bf7d1bb9) pulled in new version of signal-exit (@bcoe) 65 | 66 | ### v1.4.0 (2015/05/16 09:11 +00:00) 67 | - [8ca6e16](https://github.com/bcoe/nyc/commit/8ca6e16f6ecb7fa488944cd00d84ae5d355345d2) pulled in signal-exit module (@bcoe) 68 | 69 | ### v1.3.0 (2015/05/15 15:56 +00:00) 70 | - [0f701da](https://github.com/bcoe/nyc/commit/0f701da5aa3ad8a02872c4c6c8c37d0deb2c5877) pulled in new spawn-wrap, various bug fixes (@isaacs) 71 | 72 | ### v1.2.0 (2015/05/13 20:21 +00:00) 73 | - [2611ba4](https://github.com/bcoe/nyc/commit/2611ba44f12a25c12c0f95a9bdcfbf905dbb070f) handle signals when writing coverage report (@bcoe) 74 | 75 | ### v1.1.3 (2015/05/11 18:31 +00:00) 76 | - [8b362d6](https://github.com/bcoe/nyc/commit/8b362d600845722943c1da8213f0406d6b3a3874) istanbul has a text lcov report now \o/ (@bcoe) 77 | 78 | ### v1.1.2 (2015/05/11 06:52 +00:00) 79 | - [48b21cf](https://github.com/bcoe/nyc/commit/48b21cf3b35f6d14d35ac9afdd423ead09a2368e) added coverage and build badges (@bcoe) 80 | 81 | ### v1.1.0 (2015/05/10 01:32 +00:00) 82 | - [6c3f8a6](https://github.com/bcoe/nyc/commit/6c3f8a6147c376e87a22c4a72a1ab28ab4177349) pulled in @isaacs spawn-wrap module (@isaacs) 83 | - [d8956f1](https://github.com/bcoe/nyc/commit/d8956f170f12a8a27cc3f7611f78230393bf105b) we now pass cwd around using the process.env.NYC_CWD variable (@bcoe) 84 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* global __coverage__ */ 2 | var _ = require('lodash') 3 | var fs = require('fs') 4 | var glob = require('glob') 5 | var mkdirp = require('mkdirp') 6 | var path = require('path') 7 | var rimraf = require('rimraf') 8 | var onExit = require('signal-exit') 9 | var stripBom = require('strip-bom') 10 | 11 | function NYC (opts) { 12 | _.extend(this, { 13 | subprocessBin: path.resolve( 14 | __dirname, 15 | './bin/nyc.js' 16 | ), 17 | tempDirectory: './.nyc_output', 18 | cwd: process.env.NYC_CWD || process.cwd(), 19 | reporter: 'text', 20 | istanbul: require('istanbul') 21 | }, opts) 22 | 23 | this.reporter = this._toArray(this.reporter) 24 | 25 | var config = require(path.resolve(this.cwd, './package.json')).config || {} 26 | config = config.nyc || {} 27 | 28 | this.exclude = config.exclude || ['node_modules[\/\\\\]', 'test[\/\\\\]', 'test\\.js'] 29 | this.include = config.include || [] 30 | 31 | this.exclude = this._createRegexPath(this._toArray(this.exclude)) 32 | this.include = this._createRegexPath(this._toArray(this.include)) 33 | 34 | this.instrumenter = this._createInstrumenter() 35 | this._createOutputDirectory() 36 | } 37 | 38 | NYC.prototype._createRegexPath = function (array) { 39 | return _.map(array, function (path) { 40 | return new RegExp(path) 41 | }) 42 | } 43 | 44 | NYC.prototype._toArray = function (item) { 45 | return !Array.isArray(item) ? [item] : item 46 | } 47 | 48 | NYC.prototype._createInstrumenter = function () { 49 | var configFile = path.resolve(this.cwd, './.istanbul.yml') 50 | 51 | if (!fs.existsSync(configFile)) configFile = undefined 52 | 53 | var instrumenterConfig = this.istanbul.config.loadFile(configFile).instrumentation.config 54 | 55 | return new this.istanbul.Instrumenter({ 56 | coverageVariable: '__coverage__', 57 | embedSource: instrumenterConfig['embed-source'], 58 | noCompact: !instrumenterConfig.compact, 59 | preserveComments: instrumenterConfig['preserve-comments'] 60 | }) 61 | } 62 | 63 | NYC.prototype.addFile = function (filename, returnImmediately) { 64 | var instrument = true 65 | var relFile = path.relative(this.cwd, filename) 66 | var include 67 | 68 | for (var i = 0, exclude; (exclude = this.exclude[i]) !== undefined; i++) { 69 | include = this.include[i] 70 | 71 | if (exclude.test(relFile)) { 72 | if (returnImmediately) return {} 73 | instrument = false 74 | } 75 | if (include && include.test(relFile)) { 76 | instrument = true 77 | } 78 | } 79 | 80 | var content = stripBom(fs.readFileSync(filename, 'utf8')) 81 | 82 | if (instrument) { 83 | content = this.instrumenter.instrumentSync(content, './' + relFile) 84 | } 85 | 86 | return { 87 | instrument: instrument, 88 | content: content, 89 | relFile: relFile 90 | } 91 | } 92 | 93 | NYC.prototype.addAllFiles = function () { 94 | var _this = this 95 | 96 | this._createOutputDirectory() 97 | 98 | glob.sync('**/*.js', {nodir: true}).forEach(function (filename) { 99 | var obj = _this.addFile(filename, true) 100 | if (obj.instrument) { 101 | module._compile( 102 | _this.instrumenter.getPreamble(obj.content, obj.relFile), 103 | filename 104 | ) 105 | } 106 | }) 107 | 108 | this.writeCoverageFile() 109 | } 110 | 111 | NYC.prototype._wrapRequire = function () { 112 | var _this = this 113 | 114 | // any JS you require should get coverage added. 115 | require.extensions['.js'] = function (module, filename) { 116 | var obj = _this.addFile(filename) 117 | module._compile(obj.content, filename) 118 | } 119 | } 120 | 121 | NYC.prototype.cleanup = function () { 122 | if (!process.env.NYC_CWD) rimraf.sync(this.tmpDirectory()) 123 | } 124 | 125 | NYC.prototype._createOutputDirectory = function () { 126 | mkdirp.sync(this.tmpDirectory()) 127 | } 128 | 129 | NYC.prototype._wrapExit = function () { 130 | var _this = this 131 | 132 | // we always want to write coverage 133 | // regardless of how the process exits. 134 | onExit(function () { 135 | _this.writeCoverageFile() 136 | }, {alwaysLast: true}) 137 | } 138 | 139 | NYC.prototype.wrap = function (bin) { 140 | this._wrapRequire() 141 | this._wrapExit() 142 | return this 143 | } 144 | 145 | NYC.prototype.writeCoverageFile = function () { 146 | var coverage = global.__coverage__ 147 | if (typeof __coverage__ === 'object') coverage = __coverage__ 148 | if (!coverage) return 149 | 150 | fs.writeFileSync( 151 | path.resolve(this.tmpDirectory(), './', process.pid + '.json'), 152 | JSON.stringify(coverage), 153 | 'utf-8' 154 | ) 155 | } 156 | 157 | NYC.prototype.report = function (cb, _collector, _reporter) { 158 | cb = cb || function () {} 159 | 160 | var collector = _collector || new this.istanbul.Collector() 161 | var reporter = _reporter || new this.istanbul.Reporter() 162 | 163 | this._loadReports().forEach(function (report) { 164 | collector.add(report) 165 | }) 166 | 167 | this.reporter.forEach(function (_reporter) { 168 | reporter.add(_reporter) 169 | }) 170 | 171 | reporter.write(collector, true, cb) 172 | } 173 | 174 | NYC.prototype._loadReports = function () { 175 | var _this = this 176 | var files = fs.readdirSync(this.tmpDirectory()) 177 | 178 | return _.map(files, function (f) { 179 | try { 180 | return JSON.parse(fs.readFileSync( 181 | path.resolve(_this.tmpDirectory(), './', f), 182 | 'utf-8' 183 | )) 184 | } catch (e) { // handle corrupt JSON output. 185 | return {} 186 | } 187 | }) 188 | } 189 | 190 | NYC.prototype.tmpDirectory = function () { 191 | return path.resolve(this.cwd, './', this.tempDirectory) 192 | } 193 | 194 | NYC.prototype.mungeArgs = function (yargv) { 195 | var argv = process.argv.slice(1) 196 | argv = argv.slice(argv.indexOf(yargv._[0])) 197 | if (!/^(node|iojs)$/.test(argv[0]) && 198 | process.platform === 'win32' && 199 | (/\.js$/.test(argv[0]) || 200 | (!/\.(cmd|exe)$/.test(argv[0]) && 201 | !fs.existsSync(argv[0] + '.cmd') && 202 | !fs.existsSync(argv[0] + '.exe')))) { 203 | argv.unshift(process.execPath) 204 | } 205 | 206 | return argv 207 | } 208 | 209 | module.exports = NYC 210 | -------------------------------------------------------------------------------- /test/nyc-test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | var _ = require('lodash') 4 | var fs = require('fs') 5 | var NYC = require('../') 6 | var path = require('path') 7 | var rimraf = require('rimraf') 8 | var sinon = require('sinon') 9 | var spawn = require('child_process').spawn 10 | 11 | require('chai').should() 12 | require('tap').mochaGlobals() 13 | 14 | describe('nyc', function () { 15 | var fixtures = path.resolve(__dirname, './fixtures') 16 | 17 | describe('cwd', function () { 18 | function afterEach () { 19 | delete process.env.NYC_CWD 20 | rimraf.sync(path.resolve(fixtures, './nyc_output')) 21 | } 22 | 23 | it('sets cwd to process.cwd() if no environment variable is set', function () { 24 | var nyc = new NYC() 25 | 26 | nyc.cwd.should.eql(process.cwd()) 27 | afterEach() 28 | }) 29 | 30 | it('uses NYC_CWD environment variable for cwd if it is set', function () { 31 | process.env.NYC_CWD = path.resolve(__dirname, './fixtures') 32 | 33 | var nyc = new NYC() 34 | 35 | nyc.cwd.should.equal(path.resolve(__dirname, './fixtures')) 36 | afterEach() 37 | }) 38 | }) 39 | 40 | describe('config', function () { 41 | it("loads 'exclude' patterns from package.json", function () { 42 | var nyc = new NYC({ 43 | cwd: path.resolve(__dirname, './fixtures') 44 | }) 45 | 46 | nyc.exclude.length.should.eql(3) 47 | }) 48 | }) 49 | 50 | describe('wrap', function () { 51 | var nyc 52 | 53 | it('wraps modules with coverage counters when they are required', function () { 54 | nyc = (new NYC({ 55 | cwd: process.cwd() 56 | })).wrap() 57 | 58 | // clear the module cache so that 59 | // we pull index.js in again and wrap it. 60 | var name = require.resolve('../') 61 | delete require.cache[name] 62 | 63 | // when we require index.js it should be wrapped. 64 | var index = require('../') 65 | index.should.match(/__cov_/) 66 | }) 67 | 68 | function testSignal (signal, done) { 69 | var proc = spawn(process.execPath, ['./test/fixtures/' + signal + '.js'], { 70 | cwd: process.cwd(), 71 | env: process.env, 72 | stdio: 'inherit' 73 | }) 74 | 75 | proc.on('close', function () { 76 | var reports = _.filter(nyc._loadReports(), function (report) { 77 | return report['./test/fixtures/' + signal + '.js'] 78 | }) 79 | reports.length.should.equal(1) 80 | return done() 81 | }) 82 | } 83 | 84 | it('writes coverage report when process is killed with SIGTERM', function (done) { 85 | testSignal('sigterm', done) 86 | }) 87 | 88 | it('writes coverage report when process is killed with SIGINT', function (done) { 89 | testSignal('sigint', done) 90 | }) 91 | 92 | it('does not output coverage for files that have not been included, by default', function (done) { 93 | var reports = _.filter(nyc._loadReports(), function (report) { 94 | return report['./test/fixtures/not-loaded.js'] 95 | }) 96 | reports.length.should.equal(0) 97 | return done() 98 | }) 99 | }) 100 | 101 | describe('report', function () { 102 | it('runs reports for all JSON in output directory', function (done) { 103 | var nyc = new NYC({ 104 | cwd: process.cwd() 105 | }) 106 | var proc = spawn(process.execPath, ['./test/fixtures/sigint.js'], { 107 | cwd: process.cwd(), 108 | env: process.env, 109 | stdio: 'inherit' 110 | }) 111 | var start = fs.readdirSync(nyc.tmpDirectory()).length 112 | 113 | proc.on('close', function () { 114 | nyc.report( 115 | null, 116 | { 117 | add: function (report) { 118 | // the subprocess we ran should output reports 119 | // for files in the fixtures directory. 120 | Object.keys(report).should.match(/.\/test\/fixtures\//) 121 | } 122 | }, 123 | { 124 | add: function (reporter) { 125 | // reporter defaults to 'text'/ 126 | reporter.should.equal('text') 127 | }, 128 | write: function () { 129 | // we should have output a report for the new subprocess. 130 | var stop = fs.readdirSync(nyc.tmpDirectory()).length 131 | stop.should.be.gt(start) 132 | return done() 133 | } 134 | } 135 | ) 136 | }) 137 | }) 138 | 139 | it('handles corrupt JSON files', function (done) { 140 | var nyc = new NYC({ 141 | cwd: process.cwd() 142 | }) 143 | var proc = spawn(process.execPath, ['./test/fixtures/sigint.js'], { 144 | cwd: process.cwd(), 145 | env: process.env, 146 | stdio: 'inherit' 147 | }) 148 | 149 | fs.writeFileSync('./.nyc_output/bad.json', '}', 'utf-8') 150 | 151 | proc.on('close', function () { 152 | nyc.report( 153 | null, 154 | { 155 | add: function (report) {} 156 | }, 157 | { 158 | add: function (reporter) {}, 159 | write: function () { 160 | // we should get here without exception. 161 | fs.unlinkSync('./.nyc_output/bad.json') 162 | return done() 163 | } 164 | } 165 | ) 166 | }) 167 | }) 168 | 169 | it('handles multiple reporters', function (done) { 170 | var reporters = ['text-summary', 'text-lcov'] 171 | var incr = 0 172 | var nyc = new NYC({ 173 | cwd: process.cwd(), 174 | reporter: reporters 175 | }) 176 | var proc = spawn(process.execPath, ['./test/fixtures/sigint.js'], { 177 | cwd: process.cwd(), 178 | env: process.env, 179 | stdio: 'inherit' 180 | }) 181 | 182 | proc.on('close', function () { 183 | nyc.report( 184 | null, 185 | { 186 | add: function (report) {} 187 | }, 188 | { 189 | add: function (reporter) { 190 | incr += !!~reporters.indexOf(reporter) 191 | }, 192 | write: function () { 193 | incr.should.eql(reporters.length) 194 | return done() 195 | } 196 | } 197 | ) 198 | }) 199 | }) 200 | }) 201 | 202 | describe('.istanbul.yml configuration', function () { 203 | var istanbul = require('istanbul') 204 | var configSpy = sinon.spy(istanbul.config, 'loadFile') 205 | var instrumenterSpy = sinon.spy(istanbul, 'Instrumenter') 206 | 207 | function writeConfig () { 208 | fs.writeFileSync('./.istanbul.yml', 'instrumentation:\n\tpreserve-comments: true', 'utf-8') 209 | } 210 | 211 | function afterEach () { 212 | configSpy.reset() 213 | instrumenterSpy.reset() 214 | rimraf.sync('./.istanbul.yml') 215 | } 216 | 217 | it('it handles having no .istanbul.yml in the root directory', function (done) { 218 | afterEach() 219 | var nyc = new NYC() 220 | nyc.wrap() 221 | return done() 222 | }) 223 | 224 | it('uses the values in .istanbul.yml to instantiate the instrumenter', function (done) { 225 | writeConfig() 226 | 227 | var nyc = new NYC({ 228 | istanbul: istanbul 229 | }) 230 | nyc.wrap() 231 | 232 | istanbul.config.loadFile.calledWithMatch('.istanbul.yml').should.equal(true) 233 | istanbul.Instrumenter.calledWith({ 234 | coverageVariable: '__coverage__', 235 | embedSource: false, 236 | noCompact: false, 237 | preserveComments: false 238 | }).should.equal(true) 239 | 240 | afterEach() 241 | return done() 242 | }) 243 | 244 | it('loads the .istanbul.yml configuration from NYC_CWD', function (done) { 245 | var nyc = new NYC({ 246 | istanbul: istanbul, 247 | cwd: './test/fixtures' 248 | }) 249 | nyc.wrap() 250 | 251 | istanbul.config.loadFile.calledWithMatch(path.join('test', 'fixtures', '.istanbul.yml')).should.equal(true) 252 | istanbul.Instrumenter.calledWith({ 253 | coverageVariable: '__coverage__', 254 | embedSource: false, 255 | noCompact: false, 256 | preserveComments: true 257 | }).should.equal(true) 258 | 259 | afterEach() 260 | return done() 261 | }) 262 | }) 263 | 264 | describe('mungeArgs', function () { 265 | it('removes dashed options that proceed bin', function () { 266 | process.argv = ['/Users/benjamincoe/bin/iojs', 267 | '/Users/benjamincoe/bin/nyc.js', 268 | '--reporter', 269 | 'lcov', 270 | 'node', 271 | 'test/nyc-test.js' 272 | ] 273 | 274 | var yargv = require('yargs').argv 275 | 276 | var munged = (new NYC()).mungeArgs(yargv) 277 | 278 | munged.should.eql(['node', 'test/nyc-test.js']) 279 | }) 280 | }) 281 | 282 | describe('addAllFiles', function () { 283 | it('outputs an empty coverage report for all files that are not excluded', function (done) { 284 | var nyc = (new NYC()) 285 | nyc.addAllFiles() 286 | 287 | var reports = _.filter(nyc._loadReports(), function (report) { 288 | return report['./test/fixtures/not-loaded.js'] 289 | }) 290 | var report = reports[0]['./test/fixtures/not-loaded.js'] 291 | 292 | reports.length.should.equal(1) 293 | report.s['1'].should.equal(0) 294 | report.s['2'].should.equal(0) 295 | return done() 296 | }) 297 | 298 | it('tracks coverage appropriately once the file is required', function (done) { 299 | var nyc = (new NYC({ 300 | cwd: process.cwd() 301 | })).wrap() 302 | require('./fixtures/not-loaded') 303 | nyc.writeCoverageFile() 304 | 305 | var reports = _.filter(nyc._loadReports(), function (report) { 306 | return report['./test/fixtures/not-loaded.js'] 307 | }) 308 | var report = reports[0]['./test/fixtures/not-loaded.js'] 309 | 310 | reports.length.should.equal(1) 311 | report.s['1'].should.equal(1) 312 | report.s['2'].should.equal(1) 313 | 314 | return done() 315 | }) 316 | }) 317 | }) 318 | --------------------------------------------------------------------------------