├── .editorconfig ├── .github ├── ISSUE_TEMPLATE.md ├── stale.yml └── workflows │ ├── ci.yaml │ └── release-please.yml ├── .gitignore ├── .release-please-manifest.json ├── .taprc ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── bin ├── nyc.js └── wrap.js ├── build-self-coverage.js ├── docs ├── instrument.md ├── profiling.md ├── setup-codecov.md └── setup-coveralls.md ├── index.js ├── lib ├── commands │ ├── check-coverage.js │ ├── helpers.js │ ├── instrument.js │ ├── merge.js │ └── report.js ├── config-util.js ├── fs-promises.js ├── hash.js ├── instrumenters │ ├── istanbul.js │ └── noop.js ├── process-args.js ├── register-env.js ├── source-maps.js └── wrap.js ├── npm-run-clean.js ├── nyc.config.js ├── package-lock.json ├── package.json ├── release-please-config.json ├── screen.png ├── screen2.png ├── self-coverage-helper.js ├── tap-snapshots └── test │ ├── config-override.js.test.cjs │ ├── eager.js.test.cjs │ ├── instrument.js.test.cjs │ ├── nyc-integration.js.test.cjs │ └── tsc.js.test.cjs └── test ├── add-all-files.js ├── cache.js ├── config-override.js ├── config.js ├── cwd.js ├── eager.js ├── fixtures ├── all-type-module │ ├── extra.mjs │ ├── index.js │ ├── package.json │ └── script.cjs ├── cache-collision-runner.js ├── cache-collision-target.js ├── cache-collision-worker.js ├── check-instrumented.js ├── child-1.js ├── child-2.js ├── cli │ ├── .instrument-nycrc │ ├── args.js │ ├── by-arg2.js │ ├── classes.js │ ├── conf-override-module.js │ ├── conf-override-root.js │ ├── empty.js │ ├── env.js │ ├── es6.js │ ├── external-instrumenter.js │ ├── fakebin │ │ ├── .gitignore │ │ └── npm-template.js │ ├── gc.js │ ├── half-covered-failing.js │ ├── half-covered.js │ ├── instrument-inplace │ │ ├── file1.js │ │ ├── file2.js │ │ └── package.json │ ├── merge-input │ │ ├── a.json │ │ └── b.json │ ├── no-transform │ │ └── half-covered.xjs │ ├── not-strict.js │ ├── nyc-config-js │ │ ├── ignore.js │ │ ├── index.js │ │ ├── nyc.config.js │ │ ├── nycrc-config.js │ │ └── package.json │ ├── nycrc │ │ ├── .nycrc │ │ ├── .nycrc-config.json │ │ ├── .nycrc.yaml │ │ ├── .nycrc.yml │ │ ├── ignore.js │ │ ├── index.js │ │ └── package.json │ ├── package.json │ ├── run-npm-test-recursive │ │ ├── half-covered.js │ │ └── package.json │ ├── run-npm-test │ │ ├── half-covered.js │ │ └── package.json │ ├── selfspawn-fibonacci.js │ ├── skip-full.js │ ├── subdir │ │ ├── .gitignore │ │ └── input-dir │ │ │ ├── bad.js │ │ │ ├── exclude-me │ │ │ └── index.js │ │ │ ├── include-me │ │ │ ├── exclude-me.js │ │ │ └── include-me.js │ │ │ ├── index.js │ │ │ └── node_modules │ │ │ └── index.js │ └── test.js ├── conf-empty │ └── package.json ├── conf-multiple-extensions │ ├── check-instrumented.es6 │ ├── check-instrumented.foo.bar │ ├── check-instrumented.js │ ├── not-loaded.es6 │ ├── not-loaded.js │ ├── package.json │ └── run.js ├── eager.js ├── exclude-node-modules │ ├── .gitignore │ ├── bin │ │ └── do-nothing.js │ ├── node_modules │ │ └── @istanbuljs │ │ │ ├── fake-module-1 │ │ │ └── index.js │ │ │ └── fake-module-2 │ │ │ └── index.js │ └── package.json ├── hooks │ ├── index.js │ ├── lib │ │ ├── ipsum.js │ │ └── lorem.js │ ├── package.json │ └── run-in-context.js ├── identical-file-runner.js ├── identical-file1.js ├── identical-file2.js ├── not-loaded.js ├── nyc.config.js ├── package.json ├── parser-plugins │ ├── no-plugins.json │ ├── package.json │ └── v8.js ├── recursive-run │ └── package.json ├── sigint.js ├── sigterm.js ├── source-maps │ ├── instrumented │ │ ├── s1.min.js │ │ ├── s1.min.js.map │ │ └── s2.min.js │ ├── original │ │ ├── s1.js │ │ └── s2.js │ └── package.json ├── spawn.js ├── stack-trace.js ├── transpile-hook.js └── tsc │ ├── .npmrc │ ├── mapping.js │ ├── mapping.js.map │ ├── mapping.ts │ ├── package.json │ └── tsconfig.json ├── helpers ├── env-check-config.js ├── index.js ├── parse-argv.js ├── paths.js ├── reset-state.js ├── run-nyc.js ├── source-map-support.js ├── spawn.js ├── temp-dir-setup.js ├── test-failure.js └── test-success.js ├── instrument.js ├── issue-190.js ├── nyc-integration.js ├── parser-plugins.js ├── process-args.js ├── processinfo.js ├── report.js ├── should-instrument.js ├── source-map-support.js ├── temp-dir.js ├── tsc.js └── wrap.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = space 11 | indent_size = 2 12 | 13 | # We recommend you to keep these unchanged 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | ## Link to bug demonstration repository 3 | 11 | 12 | ## Expected Behavior 13 | 14 | ## Observed Behavior 15 | 16 | ### Troubleshooting steps 17 | - [ ] still occurring when I put `cache: false` in my nyc config 18 | 19 | ## Environment Information 20 | 24 | ``` 25 | # paste the output here 26 | 27 | ``` 28 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | daysUntilStale: 365 2 | 3 | exemptLabels: 4 | - "Great First Contribution" 5 | - pinned 6 | - security 7 | 8 | staleLabel: stale 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | pull_request: 6 | types: [assigned, opened, synchronize, reopened, labeled] 7 | workflow_dispatch: 8 | name: ci 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | node: [18, 20] 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: ${{ matrix.node }} 21 | cache: npm 22 | - run: npm config set update-notifier false 23 | - run: npm ci --engine-strict 24 | - run: npm test 25 | windows: 26 | runs-on: windows-latest 27 | steps: 28 | - uses: actions/checkout@v4 29 | - uses: actions/setup-node@v4 30 | with: 31 | node-version: lts/* 32 | cache: npm 33 | - run: npm config set update-notifier false 34 | - run: npm ci 35 | - run: npm test 36 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | name: release-please 6 | jobs: 7 | release-please: 8 | runs-on: ubuntu-latest 9 | if: github.repository == 'istanbuljs/nyc' 10 | steps: 11 | - uses: google-github-actions/release-please-action@v4 12 | id: release 13 | with: 14 | token: ${{ secrets.GITHUB_TOKEN }} 15 | config-file: release-please-config.json 16 | manifest-file: .release-please-manifest.json 17 | # The logic below handles the npm publication: 18 | - uses: actions/checkout@v3 19 | # these if statements ensure that a publication only occurs when 20 | # a new release is created: 21 | if: ${{ steps.release.outputs.release_created }} 22 | - uses: actions/setup-node@v3 23 | with: 24 | node-version: 16 25 | cache: npm 26 | if: ${{ steps.release.outputs.release_created }} 27 | - run: npm publish 28 | env: 29 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 30 | if: ${{ steps.release.outputs.release_created }} 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output 2 | coverage 3 | node_modules 4 | test/build/ 5 | .self_coverage 6 | self-coverage/ 7 | .tap 8 | *.swp 9 | test/fixtures/cli/coverage.json 10 | needs-transpile.js 11 | -------------------------------------------------------------------------------- /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | ".": "17.1.0" 3 | } 4 | -------------------------------------------------------------------------------- /.taprc: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": ["^test/(helpers|src|fixtures)/", "**/fixtures/**"], 3 | "disable-coverage": true, 4 | "jobs": 1, 5 | "timeout": 360, 6 | "bail": false, 7 | "node-arg": [ 8 | "--require", 9 | "./self-coverage-helper.js" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | os: 3 | - linux 4 | - osx 5 | node_js: 6 | - "node" 7 | - 10 8 | - 8 9 | git: 10 | depth: 11 | 1 12 | 13 | after_script: 14 | - "cat ./coverage/lcov.info | ./node_modules/.bin/coveralls" 15 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2015, Contributors 4 | 5 | Permission to use, copy, modify, and/or distribute this software 6 | for any purpose with or without fee is hereby granted, provided 7 | that the above copyright notice and this permission notice 8 | appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES 12 | OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE 13 | LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES 14 | OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 15 | WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 16 | ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nyc 2 | 3 | [![ci](https://github.com/istanbuljs/nyc/actions/workflows/ci.yaml/badge.svg)](https://github.com/bcoe/c8/actions/workflows/ci.yaml) 4 | [![NPM version](https://img.shields.io/npm/v/nyc.svg)](https://www.npmjs.com/package/nyc) 5 | [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org) 6 | 7 | _Having problems? want to contribute? join our [community slack](https://devtoolscommunity.herokuapp.com)_. 8 | 9 | Istanbul's state of the art command line interface, with support for: 10 | 11 | * applications that spawn subprocesses. 12 | * source mapped coverage of Babel and TypeScript projects 13 | 14 | ## How Istanbul works 15 | 16 | Istanbul instruments your ES5 and ES2015+ JavaScript code with line counters, so that you can track how well your unit-tests exercise your codebase. 17 | 18 | The `nyc` command-line-client for Istanbul works well with most JavaScript testing frameworks: tap, mocha, AVA, etc. 19 | 20 | ## Installation & Usage 21 | 22 | Use your package manager to add it as a dev dependency: `npm i -D nyc` or `yarn add -D nyc`. 23 | You can use nyc to call npm scripts (assuming they don't already have nyc executed in them), like so (replace `mocha` with your test runner everywhere you see it): 24 | 25 | ```json 26 | { 27 | "scripts": { 28 | "test": "mocha", 29 | "coverage": "nyc npm run test" 30 | } 31 | } 32 | ``` 33 | 34 | You can use also `npx` instead of installing nyc as a dependency, but you might get updates you are not ready for; to get around this, pin to a specific major version by specifying, e.g. `nyc@14`. 35 | 36 | ```json 37 | { 38 | "scripts": { 39 | "test": "npx nyc@latest mocha" 40 | } 41 | } 42 | ``` 43 | 44 | This is a good way of testing upcoming releases of nyc, usually on the `next` tag. 45 | 46 | **Note**: If you use [`jest`](https://npm.im/jest) or [`tap`](https://www.node-tap.org/), you do not need to install `nyc`. 47 | Those runners already have the IstanbulJS libraries to provide coverage for you. 48 | Follow their documentation to enable and configure coverage reporting. 49 | 50 | ## Configuring `nyc` 51 | 52 | nyc accepts a wide variety of configuration arguments, run `npx nyc --help` for thorough documentation. 53 | 54 | Configuration arguments on the command-line should be provided prior to the program that nyc is executing. 55 | As an example, the following command executes `ava`, and indicates to nyc that it should output both an `lcov` (`lcov.info` + html report) and a `text-summary` coverage report. 56 | 57 | ```shell 58 | nyc --reporter=lcov --reporter=text-summary ava 59 | ``` 60 | 61 | ### Babel projects 62 | 63 | Please start with the pre-configured [`@istanbuljs/nyc-config-babel`] preset. 64 | You can add your custom configuration options as shown below. 65 | 66 | ### TypeScript projects 67 | 68 | Please start with the pre-configured [`@istanbuljs/nyc-config-typescript`](https://www.npmjs.com/package/@istanbuljs/nyc-config-typescript) preset. 69 | 70 | #### Adding your overrides 71 | 72 | nyc allows you to inherit other configurations using the key `extends` in the `package.json` stanza, `.nycrc`, or YAML files. 73 | You can then add the specific configuration options you want that aren't in that particular shared config, e.g. 74 | 75 | ```json 76 | { 77 | "extends": "@istanbuljs/nyc-config-typescript", 78 | "all": true, 79 | "check-coverage": true 80 | } 81 | ``` 82 | 83 | ### Configuration files 84 | 85 | Any configuration options that can be set via the command line can also be specified in the `nyc` stanza of your package.json, or within a separate configuration file - a variety of flavors are available: 86 | 87 | | File name | File Association | 88 | |-----------------|------------------| 89 | | `.nycrc` | JSON | 90 | | `.nycrc.json` | JSON | 91 | | `.nycrc.yaml` | YAML | 92 | | `.nycrc.yml` | YAML | 93 | | `nyc.config.js` | CommonJS export | 94 | 95 | ### Common Configuration Options 96 | 97 | See `nyc --help` for all options available. 98 | You can set these in any of the files listed above, or from the command line. 99 | This table is a quick TLDR for the rest of this readme and there are more advanced docs available. 100 | 101 | | Option name | Description | Type | Default | 102 | | ----------- | ----------- | ---- | ------- | 103 | | `all` | Whether or not to instrument all files (not just the ones touched by your test suite) | `Boolean` | `false` | 104 | | `check-coverage` | Check whether coverage is within thresholds, fail if not | `Boolean` | `false` | 105 | | `extension` | List of extensions that nyc should attempt to handle in addition to `.js` | `Array` | `['.js', '.cjs', '.mjs', '.ts', '.tsx', '.jsx']` | 106 | | `include` | See [selecting files for coverage] for more info | `Array` | `['**']`| 107 | | `exclude` | See [selecting files for coverage] for more info | `Array` | [list](https://github.com/istanbuljs/schema/blob/master/default-exclude.js) | 108 | | `reporter` | May be set to a [built-in coverage reporter](https://istanbul.js.org/docs/advanced/alternative-reporters/) or an npm package (dev)dependency | `Array` | `['text']` | 109 | | `report-dir` | Where to put the coverage report files | `String` | `./coverage` | 110 | | `skip-full` | Don't show files with 100% statement, branch, and function coverage | `Boolean` | `false` | 111 | | `temp-dir` | Directory to output raw coverage information to | `String` | `./.nyc_output` | 112 | 113 | Configuration can also be provided by `nyc.config.js` if programmed logic is required: 114 | 115 | ```js 116 | 'use strict'; 117 | 118 | const defaultExclude = require('@istanbuljs/schema/default-exclude'); 119 | const isWindows = require('is-windows'); 120 | 121 | let platformExclude = [ 122 | isWindows() ? 'lib/posix.js' : 'lib/win32.js' 123 | ]; 124 | 125 | module.exports = { 126 | exclude: platformExclude.concat(defaultExclude) 127 | }; 128 | ``` 129 | 130 | ### Publish and reuse your nyc configuration(s) 131 | 132 | To publish and reuse your own `nyc` configuration, simply create an npm module that exports your JSON config (via [`index.json`](https://github.com/istanbuljs/istanbuljs/blob/master/packages/nyc-config-typescript/) or a CJS [`index.js`](https://github.com/istanbuljs/istanbuljs/blob/master/packages/nyc-config-hook-run-in-this-context/)). 133 | 134 | A more advanced use case would be to combine multiple shared configs in a `nyc.config.js` file: 135 | 136 | ```js 137 | 'use strict'; 138 | 139 | const babelConfig = require('@istanbuljs/nyc-config-babel'); 140 | const hookRunInThisContextConfig = require('@istanbuljs/nyc-config-hook-run-in-this-context'); 141 | 142 | module.exports = { 143 | ...babelConfig, 144 | ...hookRunInThisContextConfig, 145 | all: true, 146 | 'check-coverage': true 147 | }; 148 | ``` 149 | 150 | ## Selecting files for coverage 151 | 152 | By default, nyc only collects coverage for source files that are visited during a test. 153 | It does this by watching for files that are `require()`'d during the test. 154 | When a file is `require()`'d, nyc creates and returns an instrumented version of the source, rather than the original. 155 | Only source files that are visited during a test will appear in the coverage report and contribute to coverage statistics. 156 | 157 | nyc will instrument all files if the `--all` flag is set or if running `nyc instrument`. 158 | In this case all files will appear in the coverage report and contribute to coverage statistics. 159 | 160 | nyc will only collect coverage for files that are located under `cwd`, and then only files with extensions listed in the `extension` array. 161 | 162 | You can reduce the set of instrumented files by adding `include` and `exclude` filter arrays to your config. 163 | These allow you to shape the set of instrumented files by specifying glob patterns that can filter files from the default instrumented set. 164 | The `exclude` array may also use exclude negated glob patterns, these are specified with a `!` prefix, and can restore sub-paths of excluded paths. 165 | 166 | Globs are matched using [minimatch](https://www.npmjs.com/package/minimatch). 167 | 168 | We use the following process to remove files from consideration: 169 | 170 | 1. Limit the set of instrumented files to those files in paths listed in the `include` array. 171 | 2. Remove any files that are found in the `exclude` array. 172 | 3. Restore any exclude negated files that have been excluded in step 2. 173 | 174 | ### Using include and exclude arrays 175 | 176 | If there are paths specified in the `include` array, then the set of instrumented files will be limited to eligible files found in those paths. 177 | If the `include` array is left undefined all eligible files will be included, equivalent to setting `include: ['**']`. 178 | Multiple `include` globs can be specified on the command line, each must follow a `--include`, `-n` switch. 179 | 180 | If there are paths specified in the `exclude` array, then the set of instrumented files will not feature eligible files found in those paths. 181 | You can also specify negated paths in the `exclude` array, by prefixing them with a `!`. 182 | Negated paths can restore paths that have been already been excluded in the `exclude` array. 183 | Multiple `exclude` globs can be specified on the command line, each must follow a `--exclude`, `-x` switch. 184 | 185 | The default `exclude` list is defined in the [@istanbuljs/schema module](https://github.com/istanbuljs/schema/blob/master/default-exclude.js). 186 | Specifying your own exclude property completely replaces these defaults. 187 | 188 | For example, the following `nyc` config will collect coverage for every file in the `src` directory regardless of whether it is `require()`'d in a test. 189 | It will also exclude any files with the extension `.spec.js`. 190 | 191 | ```json 192 | { 193 | "all": true, 194 | "include": [ 195 | "src/**/*.js" 196 | ], 197 | "exclude": [ 198 | "**/*.spec.js" 199 | ] 200 | } 201 | ``` 202 | 203 | **Note:** Be wary of automatic OS glob expansion when specifying include/exclude globs with the CLI. 204 | To prevent this, wrap each glob in single quotes. 205 | 206 | ### Including files within `node_modules` 207 | 208 | We always add `**/node_modules/**` to the exclude list, even if not specified in the config. 209 | You can override this by setting `--exclude-node-modules=false`. 210 | 211 | For example, `"excludeNodeModules: false"` in the following `nyc` config will prevent `node_modules` from being added to the exclude rules. 212 | The set of include rules then restrict nyc to only consider instrumenting files found under the `lib/` and `node_modules/@my-org/` directories. 213 | The exclude rules then prevent nyc instrumenting anything in a `test` folder and the file `node_modules/@my-org/something/unwanted.js`. 214 | 215 | ```json 216 | { 217 | "all": true, 218 | "include": [ 219 | "lib/**", 220 | "node_modules/@my-org/**" 221 | ], 222 | "exclude": [ 223 | "node_modules/@my-org/something/unwanted.js", 224 | "**/test/**" 225 | ], 226 | "excludeNodeModules": false 227 | } 228 | ``` 229 | 230 | ## Setting the project root directory 231 | 232 | nyc runs a lot of file system operations relative to the project root directory. 233 | During startup nyc will look for the *default* project root directory. 234 | The *default* project root directory is the first directory found that contains a `package.json` file when searching from the current working directory up. 235 | If nyc fails to find a directory containing a `package.json` file, it will use the current working directory as the *default* project root directory. 236 | You can change the project root directory with the `--cwd` option. 237 | 238 | nyc uses the project root directory when: 239 | 240 | * looking for source files to instrument 241 | * creating globs for include and exclude rules during file selection 242 | * loading custom require hooks from the `require` array 243 | 244 | nyc may create artifact directories within the project root, with these defaults: 245 | 246 | * the report directory, `/coverage` 247 | * the cache directory, `/node_modules/.cache/nyc` 248 | * the temp directory, `/.nyc_output` 249 | 250 | ## Require additional modules 251 | 252 | The `--require` flag can be provided to `nyc` to indicate that additional modules should be required in the subprocess collecting coverage: 253 | 254 | ```shell 255 | nyc --require esm mocha 256 | ``` 257 | 258 | ### Interaction with `--all` flag 259 | 260 | The `--require` flag also operates on the main nyc process for use by `--all`. 261 | For example, in situations with `nyc --all --instrument false` and [`babel-plugin-istanbul`] setup the `--all` option only works if `--require @babel/register` is passed to nyc. 262 | Passing it to mocha would cause the tests to be instrumented but unloaded sources would not be seen. 263 | The [`@istanbuljs/nyc-config-babel`] package handles this for you! 264 | 265 | ## Caching 266 | 267 | `nyc`'s default behavior is to cache instrumented files to disk to prevent instrumenting source files multiple times, and speed `nyc` execution times. 268 | You can disable this behavior by running `nyc` with the `--cache false` flag. 269 | You can also change the default cache directory from `./node_modules/.cache/nyc` by setting the `--cache-dir` flag. 270 | 271 | ## Coverage thresholds 272 | 273 | You can set custom coverage thresholds that will fail if `check-coverage` is set to `true` and your coverage drops below those thresholds. 274 | For example, in the following `nyc` configuration, dropping below 80% branch, line, functions, or statements coverage would fail the build (you can have any combination of these): 275 | 276 | ```json 277 | { 278 | "branches": 80, 279 | "lines": 80, 280 | "functions": 80, 281 | "statements": 80 282 | } 283 | ``` 284 | 285 | To do this check on a per-file basis (as opposed to in aggregate), set the `per-file` option to `true`. 286 | 287 | ### High and low watermarks 288 | 289 | Several of the coverage reporters supported by nyc display special information for high and low watermarks: 290 | 291 | * high-watermarks represent healthy test coverage (in many reports this is represented with green highlighting). 292 | * low-watermarks represent sub-optimal coverage levels (in many reports this is represented with red highlighting). 293 | 294 | You can specify custom high and low watermarks in nyc's configuration: 295 | 296 | ```json 297 | { 298 | "watermarks": { 299 | "lines": [80, 95], 300 | "functions": [80, 95], 301 | "branches": [80, 95], 302 | "statements": [80, 95] 303 | } 304 | } 305 | ``` 306 | 307 | ## Parsing Hints (Ignoring Lines) 308 | 309 | There may be some sections of your codebase that you wish to purposefully 310 | exclude from coverage tracking, to do so you can use the following parsing 311 | hints: 312 | 313 | * `/* istanbul ignore if */`: ignore the next if statement. 314 | * `/* istanbul ignore else */`: ignore the else portion of an if statement. 315 | * `/* istanbul ignore next */`: ignore the next _thing_ in the source-code ( 316 | functions, if statements, classes, you name it). 317 | * `/* istanbul ignore file */`: ignore an entire source-file (this should be 318 | placed at the top of the file). 319 | 320 | ## Ignoring Methods 321 | 322 | You can ignore every instance of a method simply by adding its name to the `ignore-class-method` array in your `nyc` config. 323 | 324 | ```json 325 | { 326 | "ignore-class-method": ["render"] 327 | } 328 | ``` 329 | 330 | ## Combining reports from multiple runs 331 | 332 | If for whatever reason you have different test runners in your project or a different series of test runs for different kinds of tests, nyc will automatically combine the coverage report for you if configured correctly with the `--no-clean` flag and the `report` command. 333 | Originally inspired by @janiukjf in #1001, here's an example, where the `test:*` scripts (not shown) invoke only your test runner(s) and not nyc: 334 | 335 | ```json 336 | { 337 | "scripts": { 338 | "cover": "npm run cover:unit && npm run cover:integration && npm run cover:report", 339 | "cover:unit": "nyc --silent npm run test:unit", 340 | "cover:integration": "nyc --silent --no-clean npm run test:integration", 341 | "cover:report": "nyc report --reporter=lcov --reporter=text" 342 | } 343 | } 344 | ``` 345 | 346 | ### What about `nyc merge`? 347 | 348 | The `nyc merge` command is for producing one _raw coverage output file_ that combines the results from many test runs. 349 | So if you had the above setup and needed to produce a single `coverage.json` for some external tool, you could do: 350 | 351 | ```json 352 | { 353 | "scripts": { 354 | "cover:merge": "npm run cover:unit && npm run cover:integration && nyc merge .nyc_output coverage.json" 355 | } 356 | } 357 | ``` 358 | 359 | ## Source-Map support for pre-instrumented codebases 360 | 361 | If you opt to pre-instrument your source-code (rather than using a just-in-time transpiler like [`@babel/register`]) nyc supports both inline source-maps and `.map` files. 362 | 363 | _Important: If you are using nyc with a project that pre-instruments its code, run nyc with the configuration option `--exclude-after-remap` set to `false`. 364 | Otherwise nyc's reports will exclude any files that source-maps remap to folders covered under exclude rules._ 365 | 366 | ## [Integrating with coveralls](./docs/setup-coveralls.md) 367 | 368 | ## [Integrating with codecov](./docs/setup-codecov.md) 369 | 370 | ## [Producing instrumented source](./docs/instrument.md) 371 | 372 | ## Integrating with TAP formatters 373 | 374 | Many testing frameworks (Mocha, Tape, Tap, etc.) can produce [TAP](https://en.wikipedia.org/wiki/Test_Anything_Protocol) output. [tap-nyc](https://github.com/MegaArman/tap-nyc) is a TAP formatter designed to look nice with nyc. 375 | 376 | ## Tutorials and Advanced Documentation 377 | 378 | See [more nyc tutorials](https://istanbul.js.org/docs/tutorials) and [advanced nyc documentation](https://istanbul.js.org/docs/advanced/). 379 | 380 | Please feel free to [contribute documentation](https://github.com/istanbuljs/istanbuljs.github.io/tree/development/content) to help us improve. 381 | 382 | ## `nyc` for enterprise 383 | 384 | Available as part of the Tidelift Subscription. 385 | 386 | The maintainers of `nyc` and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-nyc?utm_source=npm-nyc&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) 387 | 388 | [`@babel/register`]: https://www.npmjs.com/package/@babel/register 389 | [`babel-plugin-istanbul`]: https://github.com/istanbuljs/babel-plugin-istanbul 390 | [`@istanbuljs/nyc-config-babel`]: https://www.npmjs.com/package/@istanbuljs/nyc-config-babel 391 | [selecting files for coverage]: #selecting-files-for-coverage 392 | -------------------------------------------------------------------------------- /bin/nyc.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | const configUtil = require('../lib/config-util') 5 | const { cliWrapper, suppressEPIPE } = require('../lib/commands/helpers') 6 | const { foregroundChild } = require('foreground-child') 7 | const resolveFrom = require('resolve-from') 8 | const NYC = require('../index.js') 9 | const path = require('path') 10 | const fs = require('fs') 11 | 12 | // parse configuration and command-line arguments; 13 | // we keep these values in a few different forms, 14 | // used in the various execution contexts of nyc: 15 | // reporting, instrumenting subprocesses, etc. 16 | 17 | async function main () { 18 | const { argv, childArgs, yargs } = await configUtil() 19 | 20 | if (['check-coverage', 'report', 'instrument', 'merge'].includes(argv._[0])) { 21 | // look in lib/commands for logic. 22 | return 23 | } 24 | 25 | if (argv._.length === 0) { 26 | // I don't have a clue what you're doing. 27 | process.exitCode = 1 28 | yargs.showHelp() 29 | return 30 | } 31 | 32 | // if instrument is set to false, 33 | // enable a noop instrumenter. 34 | if (!argv.instrument) argv.instrumenter = './lib/instrumenters/noop' 35 | else argv.instrumenter = './lib/instrumenters/istanbul' 36 | 37 | var nyc = (new NYC(argv)) 38 | if (argv.clean) { 39 | await nyc.reset() 40 | } else { 41 | await nyc.createTempDirectory() 42 | } 43 | 44 | const env = { 45 | NYC_CONFIG: JSON.stringify(argv), 46 | NYC_CWD: process.cwd() 47 | } 48 | 49 | /* istanbul ignore else */ 50 | if (argv['babel-cache'] === false) { 51 | // babel's cache interferes with some configurations, so is 52 | // disabled by default. opt in by setting babel-cache=true. 53 | env.BABEL_DISABLE_CACHE = process.env.BABEL_DISABLE_CACHE = '1' 54 | } 55 | 56 | if (!argv.useSpawnWrap) { 57 | const requireModules = [ 58 | require.resolve('../lib/register-env.js'), 59 | ...nyc.require.map(mod => resolveFrom.silent(nyc.cwd, mod) || mod) 60 | ] 61 | const preloadList = require('node-preload') 62 | preloadList.push( 63 | ...requireModules, 64 | require.resolve('../lib/wrap.js') 65 | ) 66 | 67 | Object.assign(process.env, env) 68 | requireModules.forEach(mod => { 69 | require(mod) 70 | }) 71 | } 72 | 73 | if (argv.all) { 74 | await nyc.addAllFiles() 75 | } 76 | 77 | if (argv.useSpawnWrap) { 78 | const wrapper = require.resolve('./wrap.js') 79 | // Support running nyc as a user without HOME (e.g. linux 'nobody'), 80 | // https://github.com/istanbuljs/nyc/issues/951 81 | env.SPAWN_WRAP_SHIM_ROOT = process.env.SPAWN_WRAP_SHIM_ROOT || process.env.XDG_CACHE_HOME || require('os').homedir() 82 | const sw = require('spawn-wrap') 83 | 84 | sw([wrapper], env) 85 | } 86 | 87 | // Both running the test script invocation and the check-coverage run may 88 | // set process.exitCode. Keep track so that both children are run, but 89 | // a non-zero exit codes in either one leads to an overall non-zero exit code. 90 | process.exitCode = 0 91 | foregroundChild(childArgs, async (code, signal, processInfo) => { 92 | let exitCode = process.exitCode || code 93 | 94 | try { 95 | // clean up foreground-child watchdog process info 96 | const parentDir = path.resolve(nyc.tempDirectory()) 97 | const dir = path.resolve(nyc.tempDirectory(), 'processinfo') 98 | const files = await nyc.coverageFiles(dir) 99 | for (let i = 0; i < files.length; i++) { 100 | const data = await nyc.coverageFileLoad(files[i], dir) 101 | if (data.pid === processInfo.watchdogPid) { 102 | fs.unlinkSync(path.resolve(parentDir, files[i])) 103 | fs.unlinkSync(path.resolve(dir, files[i])) 104 | } 105 | } 106 | 107 | await nyc.writeProcessIndex() 108 | 109 | nyc.maybePurgeSourceMapCache() 110 | if (argv.checkCoverage) { 111 | await nyc.checkCoverage({ 112 | lines: argv.lines, 113 | functions: argv.functions, 114 | branches: argv.branches, 115 | statements: argv.statements 116 | }, argv['per-file']).catch(suppressEPIPE) 117 | exitCode = process.exitCode || exitCode 118 | } 119 | 120 | if (!argv.silent) { 121 | await nyc.report().catch(suppressEPIPE) 122 | } 123 | } catch (error) { 124 | /* istanbul ignore next */ 125 | exitCode = process.exitCode || exitCode || 1 126 | /* istanbul ignore next */ 127 | console.error(error.message) 128 | } 129 | 130 | return exitCode 131 | }) 132 | } 133 | 134 | cliWrapper(main)() 135 | -------------------------------------------------------------------------------- /bin/wrap.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('../lib/wrap') 4 | require('spawn-wrap').runMain() 5 | -------------------------------------------------------------------------------- /build-self-coverage.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const fs = require('fs') 5 | const istanbul = require('istanbul-lib-instrument') 6 | const makeDir = require('make-dir') 7 | const glob = require('glob') 8 | 9 | const instrumenter = istanbul.createInstrumenter({ 10 | coverageVariable: '___NYC_SELF_COVERAGE___', 11 | esModules: true 12 | }) 13 | 14 | function instrumentFile (name) { 15 | const indexPath = path.join(__dirname, name) 16 | const outputPath = path.join(__dirname, 'self-coverage', name) 17 | 18 | const source = fs.readFileSync(indexPath, 'utf8') 19 | const instrumentedSource = name === 'package.json' ? source : instrumenter.instrumentSync(source, indexPath) 20 | 21 | makeDir.sync(path.dirname(outputPath)) 22 | fs.writeFileSync(outputPath, instrumentedSource) 23 | } 24 | 25 | function instrumentGlob (pattern) { 26 | const result = glob.sync(pattern, { 27 | cwd: __dirname, 28 | nodir: true 29 | }) 30 | 31 | result.forEach(file => { 32 | instrumentFile(file) 33 | }) 34 | } 35 | 36 | function instrumentAll () { 37 | /* package.json is just being copied so the instrumented copy of lib/hash.js can find it. */ 38 | const globPatterns = ['package.json', 'index.js', 'bin/*.js', 'lib/**/*.js'] 39 | 40 | globPatterns.forEach(pattern => { 41 | instrumentGlob(pattern) 42 | }) 43 | } 44 | 45 | instrumentAll() 46 | -------------------------------------------------------------------------------- /docs/instrument.md: -------------------------------------------------------------------------------- 1 | # Producing instrumented source 2 | 3 | The `nyc instrument` command can produce instrumented source files. 4 | These files are suitable for client side deployment during end to end testing. 5 | You can either pre-instrument your source, or write instrumented source to a stream. 6 | 7 | Run `nyc instrument --help` to display a full list of available command options. 8 | 9 | ## Pre-instrumented source 10 | 11 | You can create pre-instrumented source code by running: 12 | 13 | ```bash 14 | nyc instrument [output] 15 | ``` 16 | 17 | `` can be any file or directory within the project root directory. 18 | The `[output]` directory is optional and can be located anywhere, if not set the instrumented code will be sent to `stdout`. 19 | For example, `nyc instrument . ./output` will produce instrumented versions of any source files it finds in `.` and store them in `./output`. 20 | 21 | The `--delete` option will remove the existing output directory before instrumenting. 22 | 23 | The `--in-place` option will allow you to run the instrument command. 24 | 25 | The `--complete-copy` option will copy all remaining files from the `input` directory to the `output` directory. 26 | When using `--complete-copy` nyc will not copy the contents of a `.git` folder to the output directory. 27 | 28 | **Note:** `--complete-copy` will dereference any symlinks during the copy process, this may stop scripts running properly from the output directory. 29 | 30 | ## Streaming instrumentation 31 | 32 | `nyc instrument ` will stream instrumented source directly to `stdout` and that output can then be piped to another process. 33 | You can use this behaviour to create a server that dynamically instruments files on request. 34 | The following example shows streaming instrumentation middleware capable of instrumenting files on request. 35 | 36 | ```javascript 37 | app.use((req, res, next) => { 38 | const myOptions = "" 39 | const filename = myHelper.getFilename(req) 40 | const nyc = cp.spawn(`nyc instrument ${myOptions} ${filename}`) 41 | nyc.stdout.pipe(res) 42 | }) 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/profiling.md: -------------------------------------------------------------------------------- 1 | # Profiling: 2 | 3 | The self-coverage tool can provide a [fairly interesting information on how nyc performs](https://github.com/bcoe/nyc/pull/101#issuecomment-165337057) in real world test suites. Doing this is a bit involved, detailed steps follow. 4 | 5 | *Note:* This assumes your locally cloned development version of `nyc` is in `/user/dev/nyc`, and that you are profiling against the AVA test suite in `/user/dev/ava`. Adapt as required. 6 | 7 | ## Initial Setup (NYC) 8 | 9 | We must use `npm link`, and ensure we have fresh "self-coverage" scripts generated. 10 | 11 | ```sh 12 | # Go to the local clone of nyc 13 | cd /user/dev/nyc 14 | 15 | # Link the local clone globally 16 | npm link 17 | 18 | # Create the self-coverage instrumented files 19 | node ./build-self-coverage 20 | ``` 21 | 22 | ## Initial Setup (Test Project) 23 | 24 | ```sh 25 | # CD to the real world test suite you want to profile against 26 | cd /user/dev/ava 27 | 28 | # Link the globally linked nyc into your local node_modules 29 | npm link nyc 30 | ``` 31 | 32 | This will likely not work with `tap --coverage`, since tap will try to use it's own older copy of nyc instead of your globally linked one. Modify the test script in your test project so it uses the `nyc` binary directly, and disable `tap`s version with the `--no-cov` flag: 33 | 34 | `package.json`: 35 | 36 | ```json 37 | { 38 | "scripts" : { 39 | "test": "nyc tap --no-cov test/*.js" 40 | } 41 | } 42 | ``` 43 | 44 | ## Each Run 45 | 46 | ```sh 47 | # Clear existing self coverage (`trash` ~== `rm -rf`) 48 | cd /user/dev/nyc 49 | trash ./.self_coverage 50 | 51 | # Clear the `.nyc_cache` folder in your test project 52 | cd /user/dev/ava 53 | trash ./.nyc_cache 54 | 55 | # Run your test suite 56 | npm test 57 | 58 | # Go back to the `nyc` folder and create a self-coverage report 59 | cd /user/dev/nyc 60 | npm run report 61 | ``` 62 | 63 | A detailed profile of your test run now exists in `/user/dev/nyc/coverage/lcov-report` 64 | 65 | *Note:* `trash` is a safer version of `rm -rf`. Install via `npm i -g trash-cli`. [More info](https://github.com/sindresorhus/guides/blob/master/how-not-to-rm-yourself.md). 66 | 67 | ## WARNING: Self coverage can cause some confusing problems. 68 | 69 | If `index.covered.js` exists, it will be used instead of the normal file. This means your changes to `index.js` will not have an effect until you recreate or delete the self coverage files. Unless you are trying to do some profiling, you should probably delete them so the regular files are used. 70 | 71 | You can delete the self coverage scripts and use the regular ones as follows: 72 | 73 | ```sh 74 | # Go to nyc directory and remove the self coverage scripts 75 | cd /user/dev/nyc 76 | npm run clean 77 | ``` 78 | 79 | You can rerun `node ./build-self-coverage` scripts as desired to re-enable self-coverage. 80 | -------------------------------------------------------------------------------- /docs/setup-codecov.md: -------------------------------------------------------------------------------- 1 | # Integrating with codecov.io 2 | 3 | [codecov](https://codecov.io/) is a great tool for adding coverage reports to your GitHub project, even viewing them inline on GitHub with a [browser extension](https://docs.codecov.io/docs/browser-extension). 4 | 5 | ## Quick start 6 | 7 | Assuming your `npm test` does not run `nyc` and you have the `npx` executable (npm v5.2+), have your CI runner execute the following: 8 | 9 | ```shell 10 | npx nyc --reporter=lcov npm test && npx codecov 11 | ``` 12 | 13 | ## Without `npx` - Travis CI example using npm scripts 14 | 15 | 1. add the codecov and nyc dependencies: 16 | 17 | ```shell 18 | npm install codecov nyc --save-dev 19 | ``` 20 | 21 | 2. update the scripts in your package.json to include these lines (replace `mocha` with your test runner): 22 | 23 | ```json 24 | { 25 | "scripts": { 26 | "test": "nyc --reporter=lcov mocha", 27 | "coverage": "codecov" 28 | } 29 | } 30 | ``` 31 | 32 | 3. For private repos, add the environment variable `CODECOV_TOKEN` to Travis CI. 33 | 34 | 4. add the following to your `.travis.yml`: 35 | 36 | ```yaml 37 | after_success: npm run coverage 38 | ``` 39 | -------------------------------------------------------------------------------- /docs/setup-coveralls.md: -------------------------------------------------------------------------------- 1 | # Integrating with coveralls.io 2 | 3 | [coveralls.io](https://coveralls.io) is a great tool for adding 4 | coverage reports to your GitHub project. Here's how to get nyc 5 | integrated with coveralls and travis-ci.org: 6 | 7 | 1. add the coveralls and nyc dependencies to your module: 8 | 9 | ```shell 10 | npm install coveralls nyc --save-dev 11 | ``` 12 | 13 | 2. update the scripts in your package.json to include these bins: 14 | 15 | ```json 16 | { 17 | "scripts": { 18 | "test": "nyc mocha", 19 | "coverage": "nyc report --reporter=text-lcov | coveralls" 20 | } 21 | } 22 | ``` 23 | 24 | 3. For private repos, add the environment variable `COVERALLS_REPO_TOKEN` to Travis CI. 25 | 26 | 4. add the following to your `.travis.yml`: 27 | 28 | ```yaml 29 | after_success: npm run coverage 30 | ``` 31 | 32 | That's all there is to it! 33 | 34 | > Note: by default coveralls.io adds comments to pull-requests on GitHub, this can feel intrusive. To disable this, click on your repo on coveralls.io and uncheck `LEAVE COMMENTS?`. 35 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* global __coverage__ */ 4 | 5 | const cachingTransform = require('caching-transform') 6 | const findCacheDir = require('find-cache-dir') 7 | const fs = require('./lib/fs-promises') 8 | const os = require('os') 9 | const { debuglog, promisify } = require('util') 10 | const glob = promisify(require('glob')) 11 | const Hash = require('./lib/hash') 12 | const libCoverage = require('istanbul-lib-coverage') 13 | const libHook = require('istanbul-lib-hook') 14 | const { ProcessInfo, ProcessDB } = require('istanbul-lib-processinfo') 15 | const mkdirp = require('make-dir') 16 | const Module = require('module') 17 | const onExit = require('signal-exit') 18 | const path = require('path') 19 | const rimraf = promisify(require('rimraf')) 20 | const SourceMaps = require('./lib/source-maps') 21 | const TestExclude = require('test-exclude') 22 | const pMap = require('p-map') 23 | const getPackageType = require('get-package-type') 24 | 25 | const debugLog = debuglog('nyc') 26 | 27 | const nycSelfCoverageHelper = Symbol.for('nyc self-test coverage helper') 28 | /* istanbul ignore next */ 29 | const selfCoverageHelper = global[nycSelfCoverageHelper] || { 30 | // Avoid additional conditional code 31 | onExit () {} 32 | } 33 | 34 | function coverageFinder () { 35 | var coverage = global.__coverage__ 36 | if (typeof __coverage__ === 'object') coverage = __coverage__ 37 | if (!coverage) coverage = global.__coverage__ = {} 38 | return coverage 39 | } 40 | 41 | class NYC { 42 | constructor (config) { 43 | this.config = { ...config } 44 | 45 | this.subprocessBin = config.subprocessBin || path.resolve(__dirname, './bin/nyc.js') 46 | this._tempDirectory = config.tempDirectory || config.tempDir || './.nyc_output' 47 | this._instrumenterLib = require(config.instrumenter || './lib/instrumenters/istanbul') 48 | this._reportDir = config.reportDir || 'coverage' 49 | this._sourceMap = typeof config.sourceMap === 'boolean' ? config.sourceMap : true 50 | this._showProcessTree = config.showProcessTree || false 51 | this._eagerInstantiation = config.eager || false 52 | this.cwd = config.cwd || process.cwd() 53 | this.reporter = [].concat(config.reporter || 'text') 54 | 55 | this.cacheDirectory = (config.cacheDir && path.resolve(config.cacheDir)) || findCacheDir({ name: 'nyc', cwd: this.cwd }) 56 | this.cache = Boolean(this.cacheDirectory && config.cache) 57 | 58 | this.extensions = [].concat(config.extension || []) 59 | .concat('.js') 60 | .map(ext => ext.toLowerCase()) 61 | .filter((item, pos, arr) => arr.indexOf(item) === pos) 62 | 63 | this.exclude = new TestExclude({ 64 | cwd: this.cwd, 65 | include: config.include, 66 | exclude: config.exclude, 67 | excludeNodeModules: config.excludeNodeModules !== false, 68 | extension: this.extensions 69 | }) 70 | 71 | this.sourceMaps = new SourceMaps({ 72 | cache: this.cache, 73 | cacheDirectory: this.cacheDirectory 74 | }) 75 | 76 | // require extensions can be provided as config in package.json. 77 | this.require = [].concat(config.require || []) 78 | 79 | this.transforms = this.extensions.reduce((transforms, ext) => { 80 | transforms[ext] = this._createTransform(ext) 81 | return transforms 82 | }, {}) 83 | 84 | this.hookRequire = config.hookRequire 85 | this.hookRunInContext = config.hookRunInContext 86 | this.hookRunInThisContext = config.hookRunInThisContext 87 | this.fakeRequire = null 88 | 89 | this.processInfo = new ProcessInfo(Object.assign({}, config._processInfo, { 90 | directory: path.resolve(this.tempDirectory(), 'processinfo') 91 | })) 92 | 93 | this.hashCache = {} 94 | } 95 | 96 | _createTransform (ext) { 97 | const opts = { 98 | salt: Hash.salt(this.config), 99 | hashData: (input, metadata) => [metadata.filename], 100 | filenamePrefix: metadata => path.parse(metadata.filename).name + '-', 101 | onHash: (input, metadata, hash) => { 102 | this.hashCache[metadata.filename] = hash 103 | }, 104 | cacheDir: this.cacheDirectory, 105 | // when running --all we should not load source-file from 106 | // cache, we want to instead return the fake source. 107 | disableCache: this._disableCachingTransform(), 108 | ext: ext 109 | } 110 | if (this._eagerInstantiation) { 111 | opts.transform = this._transformFactory(this.cacheDirectory) 112 | } else { 113 | opts.factory = this._transformFactory.bind(this) 114 | } 115 | return cachingTransform(opts) 116 | } 117 | 118 | _disableCachingTransform () { 119 | return !(this.cache && this.config.isChildProcess) 120 | } 121 | 122 | _loadAdditionalModules () { 123 | if (!this.config.useSpawnWrap || this.require.length === 0) { 124 | return 125 | } 126 | 127 | const resolveFrom = require('resolve-from') 128 | this.require.forEach(requireModule => { 129 | // Attempt to require the module relative to the directory being instrumented. 130 | // Then try other locations, e.g. the nyc node_modules folder. 131 | require(resolveFrom.silent(this.cwd, requireModule) || requireModule) 132 | }) 133 | } 134 | 135 | instrumenter () { 136 | return this._instrumenter || (this._instrumenter = this._createInstrumenter()) 137 | } 138 | 139 | _createInstrumenter () { 140 | return this._instrumenterLib({ 141 | ignoreClassMethods: [].concat(this.config.ignoreClassMethod).filter(a => a), 142 | produceSourceMap: this.config.produceSourceMap, 143 | compact: this.config.compact, 144 | preserveComments: this.config.preserveComments, 145 | esModules: this.config.esModules, 146 | parserPlugins: this.config.parserPlugins 147 | }) 148 | } 149 | 150 | addFile (filename) { 151 | const source = this._readTranspiledSource(filename) 152 | this._maybeInstrumentSource(source, filename) 153 | } 154 | 155 | _readTranspiledSource (filePath) { 156 | var source = null 157 | var ext = path.extname(filePath) 158 | if (typeof Module._extensions[ext] === 'undefined') { 159 | ext = '.js' 160 | } 161 | Module._extensions[ext]({ 162 | _compile: function (content, filename) { 163 | source = content 164 | } 165 | }, filePath) 166 | return source 167 | } 168 | 169 | _getSourceMap (code, filename, hash) { 170 | const sourceMap = {} 171 | if (this._sourceMap) { 172 | sourceMap.sourceMap = this.sourceMaps.extract(code, filename) 173 | sourceMap.registerMap = () => this.sourceMaps.registerMap(filename, hash, sourceMap.sourceMap) 174 | } else { 175 | sourceMap.registerMap = () => {} 176 | } 177 | 178 | return sourceMap 179 | } 180 | 181 | async addAllFiles () { 182 | this._loadAdditionalModules() 183 | 184 | this.fakeRequire = true 185 | const files = await this.exclude.glob(this.cwd) 186 | for (const relFile of files) { 187 | const filename = path.resolve(this.cwd, relFile) 188 | const ext = path.extname(filename) 189 | if (ext === '.mjs' || (ext === '.js' && await getPackageType(filename) === 'module')) { 190 | const source = await fs.readFile(filename, 'utf8') 191 | this.instrumenter().instrumentSync( 192 | source, 193 | filename, 194 | this._getSourceMap(source, filename) 195 | ) 196 | } else { 197 | this.addFile(filename) 198 | } 199 | const coverage = coverageFinder() 200 | const lastCoverage = this.instrumenter().lastFileCoverage() 201 | if (lastCoverage) { 202 | coverage[lastCoverage.path] = { 203 | ...lastCoverage, 204 | // Only use this data if we don't have it without `all: true` 205 | all: true 206 | } 207 | } 208 | } 209 | this.fakeRequire = false 210 | 211 | this.writeCoverageFile() 212 | } 213 | 214 | async instrumentAllFiles (input, output) { 215 | let inputDir = '.' + path.sep 216 | const visitor = async relFile => { 217 | const inFile = path.resolve(inputDir, relFile) 218 | const inCode = await fs.readFile(inFile, 'utf-8') 219 | const outCode = this._transform(inCode, inFile) || inCode 220 | 221 | if (output) { 222 | const { mode } = await fs.stat(inFile) 223 | const outFile = path.resolve(output, relFile) 224 | 225 | await mkdirp(path.dirname(outFile)) 226 | await fs.writeFile(outFile, outCode) 227 | await fs.chmod(outFile, mode) 228 | } else { 229 | console.log(outCode) 230 | } 231 | } 232 | 233 | this._loadAdditionalModules() 234 | 235 | const stats = await fs.lstat(input) 236 | if (stats.isDirectory()) { 237 | inputDir = input 238 | 239 | const filesToInstrument = await this.exclude.glob(input) 240 | 241 | const concurrency = output ? os.cpus().length : 1 242 | if (this.config.completeCopy && output) { 243 | const files = await glob(path.resolve(input, '**'), { 244 | dot: true, 245 | nodir: true, 246 | ignore: ['**/.git', '**/.git/**', path.join(output, '**')] 247 | }) 248 | const destDirs = new Set( 249 | files.map(src => path.dirname(path.join(output, path.relative(input, src)))) 250 | ) 251 | 252 | await pMap( 253 | destDirs, 254 | dir => mkdirp(dir), 255 | { concurrency } 256 | ) 257 | await pMap( 258 | files, 259 | src => fs.copyFile(src, path.join(output, path.relative(input, src))), 260 | { concurrency } 261 | ) 262 | } 263 | 264 | await pMap(filesToInstrument, visitor, { concurrency }) 265 | } else { 266 | await visitor(input) 267 | } 268 | } 269 | 270 | _transform (code, filename) { 271 | const extname = path.extname(filename).toLowerCase() 272 | const transform = this.transforms[extname] || (() => null) 273 | 274 | return transform(code, { filename }) 275 | } 276 | 277 | _maybeInstrumentSource (code, filename) { 278 | if (!this.exclude.shouldInstrument(filename)) { 279 | return null 280 | } 281 | 282 | return this._transform(code, filename) 283 | } 284 | 285 | maybePurgeSourceMapCache () { 286 | if (!this.cache) { 287 | this.sourceMaps.purgeCache() 288 | } 289 | } 290 | 291 | _transformFactory (cacheDir) { 292 | const instrumenter = this.instrumenter() 293 | let instrumented 294 | 295 | return (code, metadata, hash) => { 296 | const filename = metadata.filename 297 | const sourceMap = this._getSourceMap(code, filename, hash) 298 | 299 | try { 300 | instrumented = instrumenter.instrumentSync(code, filename, sourceMap) 301 | } catch (e) { 302 | debugLog('failed to instrument ' + filename + ' with error: ' + e.stack) 303 | if (this.config.exitOnError) { 304 | console.error('Failed to instrument ' + filename) 305 | process.exit(1) 306 | } else { 307 | instrumented = code 308 | } 309 | } 310 | 311 | if (this.fakeRequire) { 312 | return 'function x () {}' 313 | } else { 314 | return instrumented 315 | } 316 | } 317 | } 318 | 319 | _handleJs (code, options) { 320 | // ensure the path has correct casing (see istanbuljs/nyc#269 and nodejs/node#6624) 321 | const filename = path.resolve(this.cwd, options.filename) 322 | return this._maybeInstrumentSource(code, filename) || code 323 | } 324 | 325 | _addHook (type) { 326 | const handleJs = this._handleJs.bind(this) 327 | const dummyMatcher = () => true // we do all processing in transformer 328 | libHook['hook' + type](dummyMatcher, handleJs, { extensions: this.extensions }) 329 | } 330 | 331 | _addRequireHooks () { 332 | if (this.hookRequire) { 333 | this._addHook('Require') 334 | } 335 | if (this.hookRunInContext) { 336 | this._addHook('RunInContext') 337 | } 338 | if (this.hookRunInThisContext) { 339 | this._addHook('RunInThisContext') 340 | } 341 | } 342 | 343 | async createTempDirectory () { 344 | await mkdirp(this.tempDirectory()) 345 | if (this.cache) { 346 | await mkdirp(this.cacheDirectory) 347 | } 348 | 349 | await mkdirp(this.processInfo.directory) 350 | } 351 | 352 | async reset () { 353 | if (!process.env.NYC_CWD) { 354 | await rimraf(this.tempDirectory()) 355 | } 356 | 357 | await this.createTempDirectory() 358 | } 359 | 360 | _wrapExit () { 361 | selfCoverageHelper.registered = true 362 | 363 | // we always want to write coverage 364 | // regardless of how the process exits. 365 | onExit( 366 | () => { 367 | this.writeCoverageFile() 368 | selfCoverageHelper.onExit() 369 | }, 370 | { alwaysLast: true } 371 | ) 372 | } 373 | 374 | wrap (bin) { 375 | process.env.NYC_PROCESS_ID = this.processInfo.uuid 376 | // This is a bug with the spawn-wrap method where 377 | // we cannot force propagation of NYC_PROCESS_ID. 378 | if (!this.config.useSpawnWrap) { 379 | const updateVariable = require('./lib/register-env.js') 380 | updateVariable('NYC_PROCESS_ID') 381 | } 382 | this._addRequireHooks() 383 | this._wrapExit() 384 | this._loadAdditionalModules() 385 | return this 386 | } 387 | 388 | writeCoverageFile () { 389 | var coverage = coverageFinder() 390 | 391 | // Remove any files that should be excluded but snuck into the coverage 392 | Object.keys(coverage).forEach(function (absFile) { 393 | if (!this.exclude.shouldInstrument(absFile)) { 394 | delete coverage[absFile] 395 | } 396 | }, this) 397 | 398 | if (this.cache) { 399 | Object.keys(coverage).forEach(function (absFile) { 400 | if (this.hashCache[absFile] && coverage[absFile]) { 401 | coverage[absFile].contentHash = this.hashCache[absFile] 402 | } 403 | }, this) 404 | } 405 | 406 | var id = this.processInfo.uuid 407 | var coverageFilename = path.resolve(this.tempDirectory(), id + '.json') 408 | 409 | fs.writeFileSync( 410 | coverageFilename, 411 | JSON.stringify(coverage), 412 | 'utf-8' 413 | ) 414 | 415 | this.processInfo.coverageFilename = coverageFilename 416 | this.processInfo.files = Object.keys(coverage) 417 | this.processInfo.saveSync() 418 | } 419 | 420 | async getCoverageMapFromAllCoverageFiles (baseDirectory) { 421 | const map = libCoverage.createCoverageMap({}) 422 | const files = await this.coverageFiles(baseDirectory) 423 | 424 | await pMap( 425 | files, 426 | async f => { 427 | const report = await this.coverageFileLoad(f, baseDirectory) 428 | map.merge(report) 429 | }, 430 | { concurrency: os.cpus().length } 431 | ) 432 | 433 | map.data = await this.sourceMaps.remapCoverage(map.data) 434 | 435 | // depending on whether source-code is pre-instrumented 436 | // or instrumented using a JIT plugin like @babel/require 437 | // you may opt to exclude files after applying 438 | // source-map remapping logic. 439 | if (this.config.excludeAfterRemap) { 440 | map.filter(filename => this.exclude.shouldInstrument(filename)) 441 | } 442 | 443 | return map 444 | } 445 | 446 | async report () { 447 | const libReport = require('istanbul-lib-report') 448 | const reports = require('istanbul-reports') 449 | 450 | const context = libReport.createContext({ 451 | dir: this.reportDirectory(), 452 | watermarks: this.config.watermarks, 453 | coverageMap: await this.getCoverageMapFromAllCoverageFiles() 454 | }) 455 | 456 | this.reporter.forEach((_reporter) => { 457 | reports.create(_reporter, { 458 | skipEmpty: this.config.skipEmpty, 459 | skipFull: this.config.skipFull, 460 | projectRoot: this.cwd, 461 | maxCols: process.stdout.columns || 100 462 | }).execute(context) 463 | }) 464 | 465 | if (this._showProcessTree) { 466 | await this.showProcessTree() 467 | } 468 | } 469 | 470 | async writeProcessIndex () { 471 | const db = new ProcessDB(this.processInfo.directory) 472 | await db.writeIndex() 473 | } 474 | 475 | async showProcessTree () { 476 | const db = new ProcessDB(this.processInfo.directory) 477 | console.log(await db.renderTree(this)) 478 | } 479 | 480 | async checkCoverage (thresholds, perFile) { 481 | const map = await this.getCoverageMapFromAllCoverageFiles() 482 | 483 | if (perFile) { 484 | map.files().forEach(file => { 485 | // ERROR: Coverage for lines (90.12%) does not meet threshold (120%) for index.js 486 | this._checkCoverage(map.fileCoverageFor(file).toSummary(), thresholds, file) 487 | }) 488 | } else { 489 | // ERROR: Coverage for lines (90.12%) does not meet global threshold (120%) 490 | this._checkCoverage(map.getCoverageSummary(), thresholds) 491 | } 492 | } 493 | 494 | _checkCoverage (summary, thresholds, file) { 495 | Object.keys(thresholds).forEach(function (key) { 496 | var coverage = summary[key].pct 497 | if (coverage < thresholds[key]) { 498 | process.exitCode = 1 499 | if (file) { 500 | console.error('ERROR: Coverage for ' + key + ' (' + coverage + '%) does not meet threshold (' + thresholds[key] + '%) for ' + file) 501 | } else { 502 | console.error('ERROR: Coverage for ' + key + ' (' + coverage + '%) does not meet global threshold (' + thresholds[key] + '%)') 503 | } 504 | } 505 | }) 506 | } 507 | 508 | coverageFiles (baseDirectory = this.tempDirectory()) { 509 | return fs.readdir(baseDirectory) 510 | } 511 | 512 | async coverageFileLoad (filename, baseDirectory = this.tempDirectory()) { 513 | try { 514 | const report = JSON.parse(await fs.readFile(path.resolve(baseDirectory, filename)), 'utf8') 515 | await this.sourceMaps.reloadCachedSourceMaps(report) 516 | return report 517 | } catch (error) { 518 | return {} 519 | } 520 | } 521 | 522 | // TODO: Remove from nyc v16 523 | async coverageData (baseDirectory) { 524 | const files = await this.coverageFiles(baseDirectory) 525 | return pMap( 526 | files, 527 | f => this.coverageFileLoad(f, baseDirectory), 528 | { concurrency: os.cpus().length } 529 | ) 530 | } 531 | 532 | tempDirectory () { 533 | return path.resolve(this.cwd, this._tempDirectory) 534 | } 535 | 536 | reportDirectory () { 537 | return path.resolve(this.cwd, this._reportDir) 538 | } 539 | } 540 | 541 | module.exports = NYC 542 | -------------------------------------------------------------------------------- /lib/commands/check-coverage.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const NYC = require('../../index.js') 4 | const { cliWrapper, suppressEPIPE, setupOptions } = require('./helpers.js') 5 | 6 | exports.command = 'check-coverage' 7 | 8 | exports.describe = 'check whether coverage is within thresholds provided' 9 | 10 | exports.builder = function (yargs) { 11 | yargs 12 | .demandCommand(0, 0) 13 | .example('$0 check-coverage --lines 95', "check whether the JSON in nyc's output folder meets the thresholds provided") 14 | 15 | setupOptions(yargs, 'check-coverage') 16 | } 17 | 18 | exports.handler = cliWrapper(async argv => { 19 | process.env.NYC_CWD = process.cwd() 20 | 21 | const nyc = new NYC(argv) 22 | await nyc.checkCoverage({ 23 | lines: argv.lines, 24 | functions: argv.functions, 25 | branches: argv.branches, 26 | statements: argv.statements 27 | }, argv['per-file']).catch(suppressEPIPE) 28 | }) 29 | -------------------------------------------------------------------------------- /lib/commands/helpers.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const decamelize = require('decamelize') 4 | const schema = require('@istanbuljs/schema') 5 | 6 | /* These options still need to be connected to the instrumenter 7 | * Disabling them for now also avoids the issue with OSX cutting 8 | * off the error help screen at 8192 characters. 9 | */ 10 | const blockOptions = [ 11 | 'coverageVariable', 12 | 'coverageGlobalScope', 13 | 'coverageGlobalScopeFunc' 14 | ] 15 | 16 | module.exports = { 17 | setupOptions (yargs, command, cwd) { 18 | Object.entries(schema.nyc.properties).forEach(([name, setup]) => { 19 | if (blockOptions.includes(name)) { 20 | return 21 | } 22 | 23 | const option = { 24 | description: setup.description, 25 | default: setup.default, 26 | type: setup.type 27 | } 28 | 29 | if (name === 'cwd') { 30 | if (command !== null) { 31 | return 32 | } 33 | 34 | option.default = cwd 35 | option.global = true 36 | } 37 | 38 | if (option.type === 'array') { 39 | option.type = 'string' 40 | } 41 | 42 | if ('nycAlias' in setup) { 43 | option.alias = setup.nycAlias 44 | } 45 | 46 | const optionName = decamelize(name, '-') 47 | yargs.option(optionName, option) 48 | if (!setup.nycCommands.includes(command)) { 49 | yargs.hide(optionName) 50 | } 51 | }) 52 | }, 53 | /* istanbul ignore next: unsure how to test this */ 54 | suppressEPIPE (error) { 55 | /* Prevent dumping error when `nyc npm t|head` causes stdout to 56 | * be closed when reporting runs. */ 57 | if (error.code !== 'EPIPE') { 58 | throw error 59 | } 60 | }, 61 | cliWrapper (execute) { 62 | return argv => { 63 | execute(argv).catch(error => { 64 | try { 65 | console.error(error.message) 66 | } catch (_) { 67 | /* We need to run process.exit(1) even if stderr is destroyed */ 68 | } 69 | 70 | process.exit(1) 71 | }) 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/commands/instrument.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const NYC = require('../../index.js') 4 | const path = require('path') 5 | const { promisify } = require('util') 6 | const resolveFrom = require('resolve-from') 7 | const rimraf = promisify(require('rimraf')) 8 | const { cliWrapper, setupOptions } = require('./helpers.js') 9 | 10 | exports.command = 'instrument [output]' 11 | 12 | exports.describe = 'instruments a file or a directory tree and writes the instrumented code to the desired output location' 13 | 14 | exports.builder = function (yargs) { 15 | yargs 16 | .demandCommand(0, 0) 17 | .example('$0 instrument ./lib ./output', 'instrument all .js files in ./lib with coverage and output in ./output') 18 | 19 | setupOptions(yargs, 'instrument') 20 | } 21 | 22 | exports.handler = cliWrapper(async argv => { 23 | if (argv.output && !argv.inPlace && (path.resolve(argv.cwd, argv.input) === path.resolve(argv.cwd, argv.output))) { 24 | throw new Error('cannot instrument files in place, must differ from . Set \'--in-place\' to force') 25 | } 26 | 27 | if (path.relative(argv.cwd, path.resolve(argv.cwd, argv.input)).startsWith('..')) { 28 | throw new Error('cannot instrument files outside project root directory') 29 | } 30 | 31 | if (argv.delete && argv.inPlace) { 32 | throw new Error('cannot use \'--delete\' when instrumenting files in place') 33 | } 34 | 35 | if (argv.delete && argv.output && argv.output.length !== 0) { 36 | const relPath = path.relative(process.cwd(), path.resolve(argv.output)) 37 | if (relPath !== '' && !relPath.startsWith('..')) { 38 | await rimraf(argv.output) 39 | } else { 40 | throw new Error(`attempt to delete '${process.cwd()}' or containing directory.`) 41 | } 42 | } 43 | 44 | // If instrument is set to false enable a noop instrumenter. 45 | argv.instrumenter = (argv.instrument) 46 | ? './lib/instrumenters/istanbul' 47 | : './lib/instrumenters/noop' 48 | 49 | if (argv.inPlace) { 50 | argv.output = argv.input 51 | argv.completeCopy = false 52 | } 53 | 54 | const nyc = new NYC(argv) 55 | if (!argv.useSpawnWrap) { 56 | nyc.require.forEach(requireModule => { 57 | const mod = resolveFrom.silent(nyc.cwd, requireModule) || requireModule 58 | require(mod) 59 | }) 60 | } 61 | 62 | await nyc.instrumentAllFiles(argv.input, argv.output) 63 | }) 64 | -------------------------------------------------------------------------------- /lib/commands/merge.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const makeDir = require('make-dir') 4 | const fs = require('../fs-promises') 5 | const { cliWrapper, setupOptions } = require('./helpers.js') 6 | 7 | const NYC = require('../../index.js') 8 | 9 | exports.command = 'merge [output-file]' 10 | 11 | exports.describe = 'merge istanbul format coverage output in a given folder' 12 | 13 | exports.builder = function (yargs) { 14 | yargs 15 | .demandCommand(0, 0) 16 | .example('$0 merge ./out coverage.json', 'merge together reports in ./out and output as coverage.json') 17 | .positional('input-directory', { 18 | describe: 'directory containing multiple istanbul coverage files', 19 | type: 'text', 20 | default: './.nyc_output' 21 | }) 22 | .positional('output-file', { 23 | describe: 'file to output combined istanbul format coverage to', 24 | type: 'text', 25 | default: 'coverage.json' 26 | }) 27 | 28 | setupOptions(yargs, 'merge') 29 | yargs.default('exclude-after-remap', false) 30 | } 31 | 32 | exports.handler = cliWrapper(async argv => { 33 | process.env.NYC_CWD = process.cwd() 34 | const nyc = new NYC(argv) 35 | const inputStat = await fs.stat(argv.inputDirectory).catch(error => { 36 | throw new Error(`failed access input directory ${argv.inputDirectory} with error:\n\n${error.message}`) 37 | }) 38 | 39 | if (!inputStat.isDirectory()) { 40 | throw new Error(`${argv.inputDirectory} was not a directory`) 41 | } 42 | await makeDir(path.dirname(argv.outputFile)) 43 | const map = await nyc.getCoverageMapFromAllCoverageFiles(argv.inputDirectory) 44 | await fs.writeFile(argv.outputFile, JSON.stringify(map), 'utf8') 45 | console.info(`coverage files in ${argv.inputDirectory} merged into ${argv.outputFile}`) 46 | }) 47 | -------------------------------------------------------------------------------- /lib/commands/report.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const NYC = require('../../index.js') 4 | const { cliWrapper, suppressEPIPE, setupOptions } = require('./helpers.js') 5 | 6 | exports.command = 'report' 7 | 8 | exports.describe = 'run coverage report for .nyc_output' 9 | 10 | exports.builder = function (yargs) { 11 | yargs 12 | .demandCommand(0, 0) 13 | .example('$0 report --reporter=lcov', 'output an HTML lcov report to ./coverage') 14 | 15 | setupOptions(yargs, 'report') 16 | } 17 | 18 | exports.handler = cliWrapper(async argv => { 19 | process.env.NYC_CWD = process.cwd() 20 | var nyc = new NYC(argv) 21 | await nyc.report().catch(suppressEPIPE) 22 | if (argv.checkCoverage) { 23 | await nyc.checkCoverage({ 24 | lines: argv.lines, 25 | functions: argv.functions, 26 | branches: argv.branches, 27 | statements: argv.statements 28 | }, argv['per-file']).catch(suppressEPIPE) 29 | } 30 | }) 31 | -------------------------------------------------------------------------------- /lib/config-util.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const findUp = require('find-up') 5 | const Yargs = require('yargs/yargs') 6 | 7 | const { setupOptions } = require('./commands/helpers') 8 | const processArgs = require('./process-args') 9 | const { loadNycConfig } = require('@istanbuljs/load-nyc-config') 10 | 11 | async function guessCWD (cwd) { 12 | cwd = cwd || process.env.NYC_CWD || process.cwd() 13 | const pkgPath = await findUp('package.json', { cwd }) 14 | if (pkgPath) { 15 | cwd = path.dirname(pkgPath) 16 | } 17 | 18 | return cwd 19 | } 20 | 21 | async function processConfig (cwd) { 22 | cwd = await guessCWD(cwd) 23 | const yargs = Yargs([]) 24 | .usage('$0 [command] [options]') 25 | .usage('$0 [options] [bin-to-instrument]') 26 | .showHidden(false) 27 | 28 | setupOptions(yargs, null, cwd) 29 | 30 | yargs 31 | .example('$0 npm test', 'instrument your tests with coverage') 32 | .example('$0 --require @babel/register npm test', 'instrument your tests with coverage and transpile with Babel') 33 | .example('$0 report --reporter=text-lcov', 'output lcov report after running your tests') 34 | .epilog('visit https://git.io/vHysA for list of available reporters') 35 | .boolean('h') 36 | .boolean('version') 37 | .help(false) 38 | .version(false) 39 | 40 | const instrumenterArgs = processArgs.hideInstrumenteeArgs() 41 | 42 | // This yargs.parse must come before any options that exit post-hoc 43 | const childArgs = processArgs.hideInstrumenterArgs(yargs.parse(process.argv.slice(2))) 44 | const config = await loadNycConfig(yargs.parse(instrumenterArgs)) 45 | 46 | yargs 47 | .config(config) 48 | .help('h') 49 | .alias('h', 'help') 50 | .version() 51 | .command(require('./commands/check-coverage')) 52 | .command(require('./commands/instrument')) 53 | .command(require('./commands/report')) 54 | .command(require('./commands/merge')) 55 | 56 | return { 57 | get argv () { 58 | return yargs.parse(instrumenterArgs) 59 | }, 60 | childArgs, 61 | yargs 62 | } 63 | } 64 | 65 | module.exports = processConfig 66 | -------------------------------------------------------------------------------- /lib/fs-promises.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | 5 | const { promisify } = require('util') 6 | 7 | module.exports = { ...fs } 8 | 9 | // Promisify all functions for consistency 10 | const fns = [ 11 | 'access', 12 | 'appendFile', 13 | 'chmod', 14 | 'chown', 15 | 'close', 16 | 'copyFile', 17 | 'fchmod', 18 | 'fchown', 19 | 'fdatasync', 20 | 'fstat', 21 | 'fsync', 22 | 'ftruncate', 23 | 'futimes', 24 | 'lchmod', 25 | 'lchown', 26 | 'link', 27 | 'lstat', 28 | 'mkdir', 29 | 'mkdtemp', 30 | 'open', 31 | 'read', 32 | 'readdir', 33 | 'readFile', 34 | 'readlink', 35 | 'realpath', 36 | 'rename', 37 | 'rmdir', 38 | 'stat', 39 | 'symlink', 40 | 'truncate', 41 | 'unlink', 42 | 'utimes', 43 | 'write', 44 | 'writeFile' 45 | ] 46 | fns.forEach(fn => { 47 | /* istanbul ignore else: all functions exist on OSX */ 48 | if (fs[fn]) { 49 | module.exports[fn] = promisify(fs[fn]) 50 | } 51 | }) 52 | -------------------------------------------------------------------------------- /lib/hash.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | function getInvalidatingOptions (config) { 4 | return [ 5 | 'compact', 6 | 'esModules', 7 | 'ignoreClassMethods', 8 | 'instrument', 9 | 'instrumenter', 10 | 'parserPlugins', 11 | 'preserveComments', 12 | 'produceSourceMap', 13 | 'sourceMap' 14 | ].reduce((acc, optName) => { 15 | acc[optName] = config[optName] 16 | return acc 17 | }, {}) 18 | } 19 | 20 | module.exports = { 21 | salt (config) { 22 | return JSON.stringify({ 23 | modules: { 24 | 'istanbul-lib-instrument': require('istanbul-lib-instrument/package.json').version, 25 | nyc: require('../package.json').version 26 | }, 27 | nycrc: getInvalidatingOptions(config) 28 | }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/instrumenters/istanbul.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | function InstrumenterIstanbul (options) { 4 | const { createInstrumenter } = require('istanbul-lib-instrument') 5 | const convertSourceMap = require('convert-source-map') 6 | 7 | const instrumenter = createInstrumenter({ 8 | autoWrap: true, 9 | coverageVariable: '__coverage__', 10 | embedSource: true, 11 | compact: options.compact, 12 | preserveComments: options.preserveComments, 13 | produceSourceMap: options.produceSourceMap, 14 | ignoreClassMethods: options.ignoreClassMethods, 15 | esModules: options.esModules, 16 | parserPlugins: options.parserPlugins 17 | }) 18 | 19 | return { 20 | instrumentSync (code, filename, { sourceMap, registerMap }) { 21 | var instrumented = instrumenter.instrumentSync(code, filename, sourceMap) 22 | if (instrumented !== code) { 23 | registerMap() 24 | } 25 | 26 | // the instrumenter can optionally produce source maps, 27 | // this is useful for features like remapping stack-traces. 28 | if (options.produceSourceMap) { 29 | var lastSourceMap = instrumenter.lastSourceMap() 30 | /* istanbul ignore else */ 31 | if (lastSourceMap) { 32 | instrumented += '\n' + convertSourceMap.fromObject(lastSourceMap).toComment() 33 | } 34 | } 35 | 36 | return instrumented 37 | }, 38 | lastFileCoverage () { 39 | return instrumenter.lastFileCoverage() 40 | } 41 | } 42 | } 43 | 44 | module.exports = InstrumenterIstanbul 45 | -------------------------------------------------------------------------------- /lib/instrumenters/noop.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | function NOOP () { 4 | const { readInitialCoverage } = require('istanbul-lib-instrument') 5 | 6 | return { 7 | instrumentSync (code, filename) { 8 | const extracted = readInitialCoverage(code) 9 | if (extracted) { 10 | this.fileCoverage = extracted.coverageData 11 | } else { 12 | this.fileCoverage = null 13 | } 14 | return code 15 | }, 16 | lastFileCoverage () { 17 | return this.fileCoverage 18 | } 19 | } 20 | } 21 | 22 | module.exports = NOOP 23 | -------------------------------------------------------------------------------- /lib/process-args.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { Parser } = require('yargs/yargs') 4 | const commands = [ 5 | 'report', 6 | 'check-coverage', 7 | 'instrument', 8 | 'merge' 9 | ] 10 | 11 | module.exports = { 12 | // don't pass arguments that are meant 13 | // for nyc to the bin being instrumented. 14 | hideInstrumenterArgs: function (yargv) { 15 | var argv = process.argv.slice(1) 16 | argv = argv.slice(argv.indexOf(yargv._[0])) 17 | if (argv[0][0] === '-') { 18 | argv.unshift(process.execPath) 19 | } 20 | return argv 21 | }, 22 | // don't pass arguments for the bin being 23 | // instrumented to nyc. 24 | hideInstrumenteeArgs: function () { 25 | var argv = process.argv.slice(2) 26 | var yargv = Parser(argv) 27 | if (!yargv._.length) return argv 28 | for (var i = 0, command; (command = yargv._[i]) !== undefined; i++) { 29 | if (~commands.indexOf(command)) return argv 30 | } 31 | 32 | // drop all the arguments after the bin being 33 | // instrumented by nyc. 34 | argv = argv.slice(0, argv.indexOf(yargv._[0])) 35 | argv.push(yargv._[0]) 36 | 37 | return argv 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/register-env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const processOnSpawn = require('process-on-spawn') 4 | 5 | const envToCopy = {} 6 | 7 | processOnSpawn.addListener(({ env }) => { 8 | Object.assign(env, envToCopy) 9 | }) 10 | 11 | const copyAtLoad = [ 12 | 'NYC_CONFIG', 13 | 'NYC_CWD', 14 | 'NYC_PROCESS_ID', 15 | 'BABEL_DISABLE_CACHE', 16 | 'NYC_PROCESS_ID' 17 | ] 18 | 19 | for (const env of copyAtLoad) { 20 | if (env in process.env) { 21 | envToCopy[env] = process.env[env] 22 | } 23 | } 24 | 25 | module.exports = function updateVariable (envName) { 26 | envToCopy[envName] = process.env[envName] 27 | } 28 | -------------------------------------------------------------------------------- /lib/source-maps.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const convertSourceMap = require('convert-source-map') 4 | const libCoverage = require('istanbul-lib-coverage') 5 | const libSourceMaps = require('istanbul-lib-source-maps') 6 | const fs = require('./fs-promises') 7 | const os = require('os') 8 | const path = require('path') 9 | const pMap = require('p-map') 10 | 11 | class SourceMaps { 12 | constructor (opts) { 13 | this.cache = opts.cache 14 | this.cacheDirectory = opts.cacheDirectory 15 | this.loadedMaps = {} 16 | this._sourceMapCache = libSourceMaps.createSourceMapStore() 17 | } 18 | 19 | cachedPath (source, hash) { 20 | return path.join( 21 | this.cacheDirectory, 22 | `${path.parse(source).name}-${hash}.map` 23 | ) 24 | } 25 | 26 | purgeCache () { 27 | this._sourceMapCache = libSourceMaps.createSourceMapStore() 28 | this.loadedMaps = {} 29 | } 30 | 31 | extract (code, filename) { 32 | const sourceMap = convertSourceMap.fromSource(code) || convertSourceMap.fromMapFileSource(code, path.dirname(filename)) 33 | return sourceMap ? sourceMap.toObject() : undefined 34 | } 35 | 36 | registerMap (filename, hash, sourceMap) { 37 | if (!sourceMap) { 38 | return 39 | } 40 | 41 | if (this.cache && hash) { 42 | const mapPath = this.cachedPath(filename, hash) 43 | fs.writeFileSync(mapPath, JSON.stringify(sourceMap)) 44 | } else { 45 | this._sourceMapCache.registerMap(filename, sourceMap) 46 | } 47 | } 48 | 49 | async remapCoverage (obj) { 50 | const transformed = await this._sourceMapCache.transformCoverage( 51 | libCoverage.createCoverageMap(obj) 52 | ) 53 | return transformed.data 54 | } 55 | 56 | async reloadCachedSourceMaps (report) { 57 | await pMap( 58 | Object.entries(report), 59 | async ([absFile, fileReport]) => { 60 | if (!fileReport || !fileReport.contentHash) { 61 | return 62 | } 63 | 64 | const hash = fileReport.contentHash 65 | if (!(hash in this.loadedMaps)) { 66 | try { 67 | const mapPath = this.cachedPath(absFile, hash) 68 | this.loadedMaps[hash] = JSON.parse(await fs.readFile(mapPath, 'utf8')) 69 | } catch (e) { 70 | // set to false to avoid repeatedly trying to load the map 71 | this.loadedMaps[hash] = false 72 | } 73 | } 74 | 75 | if (this.loadedMaps[hash]) { 76 | this._sourceMapCache.registerMap(absFile, this.loadedMaps[hash]) 77 | } 78 | }, 79 | { concurrency: os.cpus().length } 80 | ) 81 | } 82 | } 83 | 84 | module.exports = SourceMaps 85 | -------------------------------------------------------------------------------- /lib/wrap.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const NYC = require('../index.js') 4 | 5 | const config = JSON.parse( 6 | process.env.NYC_CONFIG || 7 | /* istanbul ignore next */ '{}' 8 | ) 9 | 10 | config.isChildProcess = true 11 | 12 | config._processInfo = { 13 | pid: process.pid, 14 | ppid: process.ppid, 15 | parent: process.env.NYC_PROCESS_ID || null 16 | } 17 | 18 | if (process.env.NYC_PROCESSINFO_EXTERNAL_ID) { 19 | config._processInfo.externalId = process.env.NYC_PROCESSINFO_EXTERNAL_ID 20 | delete process.env.NYC_PROCESSINFO_EXTERNAL_ID 21 | } 22 | 23 | if (process.env.NYC_CONFIG_OVERRIDE) { 24 | Object.assign(config, JSON.parse(process.env.NYC_CONFIG_OVERRIDE)) 25 | process.env.NYC_CONFIG = JSON.stringify(config) 26 | } 27 | 28 | ;(new NYC(config)).wrap() 29 | -------------------------------------------------------------------------------- /npm-run-clean.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | const { promisify } = require('util') 5 | const rimraf = promisify(require('rimraf')) 6 | 7 | Promise.all([ 8 | '**/.nyc_output', 9 | 'node_modules/.cache', 10 | '.self_coverage', 11 | 'test/**/.cache', 12 | 'test/fixtures/cli/coverage', 13 | 'test/fixtures/cli/fakebin/node', 14 | 'test/fixtures/cli/fakebin/npm', 15 | 'test/fixtures/cli/foo-cache', 16 | 'test/fixtures/cli/nyc-config-js/node_modules', 17 | 'test/temp-dir-*', 18 | 'self-coverage' 19 | ].map(f => rimraf(f, { cwd: __dirname }))) 20 | -------------------------------------------------------------------------------- /nyc.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const isWindows = require('is-windows')() 4 | 5 | module.exports = { 6 | exclude: [ 7 | 'coverage', 8 | 'self-coverage', 9 | 'test/fixtures/coverage.js', 10 | 'test/build/*', 11 | 'test/src/*', 12 | 'test/nyc.js', 13 | 'test/process-args.js', 14 | 'test/fixtures/_generateCoverage.js' 15 | ], 16 | /* Unknown why we don't get 100% coverage on Windows. */ 17 | 'check-coverage': !isWindows, 18 | branches: 100, 19 | functions: 100, 20 | lines: 100, 21 | statements: 100 22 | } 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nyc", 3 | "version": "17.1.0", 4 | "description": "the Istanbul command line interface", 5 | "main": "index.js", 6 | "scripts": { 7 | "lint": "standard", 8 | "pretest": "npm run lint && npm run clean && npm run instrument", 9 | "test": "tap", 10 | "snap": "npm test -- --snapshot", 11 | "fix": "standard --fix", 12 | "posttest": "npm run report", 13 | "clean": "node ./npm-run-clean.js", 14 | "instrument": "node ./build-self-coverage.js", 15 | "report": "node ./bin/nyc report --temp-dir ./.self_coverage/ -r text -r lcov", 16 | "release": "standard-version" 17 | }, 18 | "bin": { 19 | "nyc": "./bin/nyc.js" 20 | }, 21 | "files": [ 22 | "index.js", 23 | "bin/*.js", 24 | "lib/**/*.js" 25 | ], 26 | "standard": { 27 | "ignore": [ 28 | "/tap-snapshots/", 29 | "**/fixtures/**", 30 | "**/test/build/*" 31 | ] 32 | }, 33 | "keywords": [ 34 | "coverage", 35 | "reporter", 36 | "subprocess", 37 | "testing" 38 | ], 39 | "contributors": [ 40 | { 41 | "name": "Isaac Schlueter", 42 | "website": "https://github.com/isaacs" 43 | }, 44 | { 45 | "name": "Mark Wubben", 46 | "website": "https://novemberborn.net" 47 | }, 48 | { 49 | "name": "James Talmage", 50 | "website": "https://twitter.com/jamestalmage" 51 | }, 52 | { 53 | "name": "Krishnan Anantheswaran", 54 | "website": "https://github.com/gotwarlost" 55 | } 56 | ], 57 | "author": "Ben Coe ", 58 | "license": "ISC", 59 | "dependencies": { 60 | "@istanbuljs/load-nyc-config": "^1.0.0", 61 | "@istanbuljs/schema": "^0.1.2", 62 | "caching-transform": "^4.0.0", 63 | "convert-source-map": "^1.7.0", 64 | "decamelize": "^1.2.0", 65 | "find-cache-dir": "^3.2.0", 66 | "find-up": "^4.1.0", 67 | "foreground-child": "^3.3.0", 68 | "get-package-type": "^0.1.0", 69 | "glob": "^7.1.6", 70 | "istanbul-lib-coverage": "^3.0.0", 71 | "istanbul-lib-hook": "^3.0.0", 72 | "istanbul-lib-instrument": "^6.0.2", 73 | "istanbul-lib-processinfo": "^2.0.2", 74 | "istanbul-lib-report": "^3.0.0", 75 | "istanbul-lib-source-maps": "^4.0.0", 76 | "istanbul-reports": "^3.0.2", 77 | "make-dir": "^3.0.0", 78 | "node-preload": "^0.2.1", 79 | "p-map": "^3.0.0", 80 | "process-on-spawn": "^1.0.0", 81 | "resolve-from": "^5.0.0", 82 | "rimraf": "^3.0.0", 83 | "signal-exit": "^3.0.2", 84 | "spawn-wrap": "^2.0.0", 85 | "test-exclude": "^6.0.0", 86 | "yargs": "^15.0.2" 87 | }, 88 | "devDependencies": { 89 | "any-path": "^1.3.0", 90 | "is-windows": "^1.0.2", 91 | "requirejs": "^2.3.6", 92 | "source-map-support": "^0.5.16", 93 | "standard": "^14.3.1", 94 | "standard-version": "^9.0.0", 95 | "tap": "^18.7.2", 96 | "uuid": "^3.4.0", 97 | "which": "^2.0.2" 98 | }, 99 | "engines": { 100 | "node": ">=18" 101 | }, 102 | "homepage": "https://istanbul.js.org/", 103 | "bugs": "https://github.com/istanbuljs/nyc/issues", 104 | "repository": { 105 | "type": "git", 106 | "url": "git@github.com:istanbuljs/nyc.git" 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", 3 | "release-type": "node", 4 | "bootstrap-sha": "ab7c53b2f340b458789a746dff2abd3e2e4790c3", 5 | "packages": { 6 | ".": {} 7 | } 8 | } 9 | 10 | -------------------------------------------------------------------------------- /screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/istanbuljs/nyc/41f4476f9f1010367d62c9e7841e14cfe4a2801a/screen.png -------------------------------------------------------------------------------- /screen2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/istanbuljs/nyc/41f4476f9f1010367d62c9e7841e14cfe4a2801a/screen2.png -------------------------------------------------------------------------------- /self-coverage-helper.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const fs = require('fs') 5 | const uuid = require('uuid/v4') 6 | const mkdirp = require('make-dir') 7 | const onExit = require('signal-exit') 8 | const nodePreload = require('node-preload') 9 | 10 | if (!nodePreload.includes(__filename)) { 11 | nodePreload.unshift(__filename) 12 | } 13 | 14 | const nycSelfCoverageHelper = Symbol.for('nyc self-test coverage helper') 15 | 16 | global[nycSelfCoverageHelper] = { 17 | registered: false, 18 | onExit () { 19 | const coverage = global.___NYC_SELF_COVERAGE___ || {} 20 | 21 | const selfCoverageDir = path.join(__dirname, '.self_coverage') 22 | mkdirp.sync(selfCoverageDir) 23 | fs.writeFileSync( 24 | path.join(selfCoverageDir, uuid() + '.json'), 25 | JSON.stringify(coverage), 26 | 'utf-8' 27 | ) 28 | } 29 | } 30 | 31 | onExit(() => { 32 | if (global[nycSelfCoverageHelper].registered) { 33 | return 34 | } 35 | 36 | global[nycSelfCoverageHelper].onExit() 37 | }) 38 | -------------------------------------------------------------------------------- /tap-snapshots/test/config-override.js.test.cjs: -------------------------------------------------------------------------------- 1 | /* IMPORTANT 2 | * This snapshot file is auto-generated, but designed for humans. 3 | * It should be checked into source control and tracked carefully. 4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests. 5 | * Make sure to inspect the output below. Do not ignore changes! 6 | */ 7 | 'use strict' 8 | exports[`test/config-override.js > TAP > spawn that does config overriding > stdout 1`] = ` 9 | in parent { include: 'conf-override-root.js' } 10 | in child { include: 'conf-override-module.js' } 11 | in module { include: 'conf-override-module.js' } 12 | -------------------------|---------|----------|---------|---------|------------------- 13 | File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 14 | -------------------------|---------|----------|---------|---------|------------------- 15 | All files | 77.77 | 50 | 100 | 77.77 | 16 | conf-override-module.js | 100 | 100 | 100 | 100 | 17 | conf-override-root.js | 71.42 | 50 | 100 | 71.42 | 22-23 18 | -------------------------|---------|----------|---------|---------|------------------- 19 | 20 | ` 21 | -------------------------------------------------------------------------------- /tap-snapshots/test/eager.js.test.cjs: -------------------------------------------------------------------------------- 1 | /* IMPORTANT 2 | * This snapshot file is auto-generated, but designed for humans. 3 | * It should be checked into source control and tracked carefully. 4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests. 5 | * Make sure to inspect the output below. Do not ignore changes! 6 | */ 7 | 'use strict' 8 | exports[`test/eager.js > TAP > eager disabled by default > stdout 1`] = ` 9 | 0 10 | 11 | ` 12 | 13 | exports[`test/eager.js > TAP > eager enabled > stdout 1`] = ` 14 | 1 15 | 16 | ` 17 | -------------------------------------------------------------------------------- /tap-snapshots/test/instrument.js.test.cjs: -------------------------------------------------------------------------------- 1 | /* IMPORTANT 2 | * This snapshot file is auto-generated, but designed for humans. 3 | * It should be checked into source control and tracked carefully. 4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests. 5 | * Make sure to inspect the output below. Do not ignore changes! 6 | */ 7 | 'use strict' 8 | exports[`test/instrument.js > TAP > can write files in place with --in-place switch > stdout 1`] = ` 9 | ----------|---------|----------|---------|---------|------------------- 10 | File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 11 | ----------|---------|----------|---------|---------|------------------- 12 | All files | 0 | 100 | 0 | 0 | 13 | file1.js | 0 | 100 | 0 | 0 | 2-5 14 | file2.js | 0 | 100 | 0 | 0 | 2-5 15 | ----------|---------|----------|---------|---------|------------------- 16 | 17 | ` 18 | -------------------------------------------------------------------------------- /tap-snapshots/test/tsc.js.test.cjs: -------------------------------------------------------------------------------- 1 | /* IMPORTANT 2 | * This snapshot file is auto-generated, but designed for humans. 3 | * It should be checked into source control and tracked carefully. 4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests. 5 | * Make sure to inspect the output below. Do not ignore changes! 6 | */ 7 | 'use strict' 8 | exports[`test/tsc.js > TAP > ignore source-map > stdout 1`] = ` 9 | ------------|---------|----------|---------|---------|------------------- 10 | File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 11 | ------------|---------|----------|---------|---------|------------------- 12 | All files | 38.46 | 57.14 | 66.66 | 50 | 13 | mapping.js | 38.46 | 57.14 | 66.66 | 50 | 3-8 14 | ------------|---------|----------|---------|---------|------------------- 15 | 16 | ` 17 | 18 | exports[`test/tsc.js > TAP > reads source-map > stdout 1`] = ` 19 | ------------|---------|----------|---------|---------|------------------- 20 | File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 21 | ------------|---------|----------|---------|---------|------------------- 22 | All files | 100 | 100 | 100 | 100 | 23 | mapping.ts | 100 | 100 | 100 | 100 | 24 | ------------|---------|----------|---------|---------|------------------- 25 | 26 | ` 27 | -------------------------------------------------------------------------------- /test/add-all-files.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('../lib/fs-promises') 4 | const path = require('path') 5 | 6 | const t = require('tap') 7 | const ap = require('any-path') 8 | 9 | const NYC = require('../self-coverage') 10 | 11 | const { parseArgv, resetState } = require('./helpers') 12 | 13 | const fixtures = path.resolve(__dirname, 'fixtures') 14 | const transpileHook = path.resolve(__dirname, 'fixtures/transpile-hook') 15 | 16 | t.beforeEach(resetState) 17 | 18 | t.test('outputs an empty coverage report for all files that are not excluded', async t => { 19 | const nyc = new NYC(await parseArgv(fixtures)) 20 | await nyc.reset() 21 | await nyc.addAllFiles() 22 | 23 | const notLoadedPath = path.join(fixtures, './not-loaded.js') 24 | const reports = (await nyc.coverageData()).filter(report => ap(report)[notLoadedPath]) 25 | const report = reports[0][notLoadedPath] 26 | 27 | t.equal(reports.length, 1) 28 | t.equal(report.s['0'], 0) 29 | t.equal(report.s['1'], 0) 30 | }) 31 | 32 | t.test('outputs an empty coverage report for multiple configured extensions', async t => { 33 | const cwd = path.resolve(fixtures, './conf-multiple-extensions') 34 | const nyc = new NYC(await parseArgv(cwd)) 35 | await nyc.reset() 36 | await nyc.addAllFiles() 37 | 38 | const notLoadedPath1 = path.join(cwd, './not-loaded.es6') 39 | const notLoadedPath2 = path.join(cwd, './not-loaded.js') 40 | const reports = (await nyc.coverageData()).filter(report => { 41 | const apr = ap(report) 42 | return apr[notLoadedPath1] || apr[notLoadedPath2] 43 | }) 44 | 45 | t.equal(reports.length, 1) 46 | 47 | const report1 = reports[0][notLoadedPath1] 48 | t.equal(report1.s['0'], 0) 49 | t.equal(report1.s['1'], 0) 50 | 51 | const report2 = reports[0][notLoadedPath2] 52 | t.equal(report2.s['0'], 0) 53 | t.equal(report2.s['1'], 0) 54 | }) 55 | 56 | t.test('transpiles .js files added via addAllFiles', async t => { 57 | await fs.writeFile( 58 | './test/fixtures/needs-transpile.js', 59 | '--> pork chop sandwiches <--\nconst a = 99', 60 | 'utf-8' 61 | ) 62 | 63 | const nyc = new NYC(await parseArgv(fixtures, [ 64 | '--use-spawn-wrap=true', 65 | '--require', 66 | transpileHook 67 | ])) 68 | await nyc.reset() 69 | await nyc.addAllFiles() 70 | 71 | const needsTranspilePath = path.join(fixtures, './needs-transpile.js') 72 | const reports = (await nyc.coverageData()).filter(report => ap(report)[needsTranspilePath]) 73 | const report = reports[0][needsTranspilePath] 74 | 75 | t.equal(reports.length, 1) 76 | t.equal(report.s['0'], 0) 77 | 78 | await fs.unlink(needsTranspilePath) 79 | }) 80 | 81 | t.test('does not attempt to transpile files when they are excluded', async t => { 82 | const notNeedTranspilePath = path.join(fixtures, './do-not-need-transpile.do-not-transpile') 83 | await fs.writeFile( 84 | notNeedTranspilePath, 85 | '--> pork chop sandwiches <--\nconst a = 99', 86 | 'utf-8' 87 | ) 88 | 89 | const nyc = new NYC(await parseArgv(fixtures, [ 90 | '--use-spawn-wrap=true', 91 | `--require=${transpileHook}`, 92 | '--extension=.do-not-transpile', 93 | '--include=needs-transpile.do-not-transpile' 94 | ])) 95 | 96 | await nyc.reset() 97 | 98 | // If this ran against *.do-not-transpile it would throw 99 | await nyc.addAllFiles() 100 | await fs.unlink(notNeedTranspilePath) 101 | }) 102 | 103 | t.test('transpiles non-.js files added via addAllFiles', async t => { 104 | await fs.writeFile( 105 | './test/fixtures/needs-transpile.whatever', 106 | '--> pork chop sandwiches <--\nconst a = 99', 107 | 'utf-8' 108 | ) 109 | 110 | const nyc = new NYC(await parseArgv(fixtures, [ 111 | '--use-spawn-wrap=true', 112 | `--require=${transpileHook}`, 113 | '--extension=.whatever' 114 | ])) 115 | 116 | await nyc.reset() 117 | await nyc.addAllFiles() 118 | 119 | const needsTranspilePath = path.join(fixtures, './needs-transpile.whatever') 120 | const reports = (await nyc.coverageData()).filter(report => ap(report)[needsTranspilePath]) 121 | const report = reports[0][needsTranspilePath] 122 | 123 | t.equal(reports.length, 1) 124 | t.equal(report.s['0'], 0) 125 | 126 | await fs.unlink(needsTranspilePath) 127 | }) 128 | -------------------------------------------------------------------------------- /test/cache.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const { promisify } = require('util') 5 | 6 | const t = require('tap') 7 | const rimraf = promisify(require('rimraf')) 8 | 9 | const NYC = require('../self-coverage') 10 | 11 | const { parseArgv, resetState, runNYC } = require('./helpers') 12 | 13 | const fixtures = path.resolve(__dirname, './fixtures') 14 | 15 | t.beforeEach(resetState) 16 | 17 | async function cacheTest (t, script) { 18 | const nyc = new NYC(await parseArgv(fixtures)) 19 | await rimraf(nyc.cacheDirectory) 20 | 21 | const { status } = await runNYC({ 22 | args: [ 23 | process.execPath, 24 | script 25 | ], 26 | cwd: fixtures, 27 | env: {} 28 | }) 29 | 30 | t.equal(status, 0) 31 | } 32 | 33 | t.test('cache handles collisions', t => cacheTest(t, './cache-collision-runner.js')) 34 | 35 | t.test('cache handles identical files', t => cacheTest(t, './identical-file-runner.js')) 36 | -------------------------------------------------------------------------------- /test/config-override.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const t = require('tap') 4 | 5 | const { tempDirSetup, testSuccess } = require('./helpers') 6 | 7 | tempDirSetup(t, __filename) 8 | 9 | t.test('spawn that does config overriding', t => testSuccess(t, { 10 | args: [ 11 | '--exclude-after-remap=false', 12 | '--include=conf-override-root.js', 13 | process.execPath, 'conf-override-root.js' 14 | ] 15 | })) 16 | -------------------------------------------------------------------------------- /test/config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | 5 | const { test } = require('tap') 6 | 7 | const NYC = require('../self-coverage') 8 | 9 | const { parseArgv } = require('./helpers') 10 | 11 | test("loads 'exclude' patterns from package.json#nyc", async t => { 12 | const nyc = new NYC(await parseArgv(path.resolve(__dirname, './fixtures'))) 13 | 14 | t.equal(nyc.exclude.exclude.length, 8) 15 | }) 16 | 17 | test("loads 'extension' patterns from package.json#nyc", async t => { 18 | const nyc = new NYC(await parseArgv(path.resolve(__dirname, './fixtures/conf-multiple-extensions'))) 19 | 20 | t.equal(nyc.extensions.length, 3) 21 | }) 22 | 23 | test("ignores 'include' option if it's falsy or []", async t => { 24 | const nyc1 = new NYC(await parseArgv(path.resolve(__dirname, './fixtures/conf-empty'))) 25 | 26 | t.equal(nyc1.exclude.include, false) 27 | 28 | const nyc2 = new NYC({ 29 | include: [] 30 | }) 31 | 32 | t.equal(nyc2.exclude.include, false) 33 | }) 34 | 35 | test("ignores 'exclude' option if it's falsy", async t => { 36 | const nyc = new NYC(await parseArgv(path.resolve(__dirname, './fixtures/conf-empty'))) 37 | 38 | t.equal(nyc.exclude.exclude.length, 27) 39 | }) 40 | 41 | test("allows for empty 'exclude'", async t => { 42 | const nyc = new NYC({ exclude: [] }) 43 | 44 | // an empty exclude still has **/node_modules/**, node_modules/** and added. 45 | t.equal(nyc.exclude.exclude.length, 2) 46 | }) 47 | 48 | test("allows for completely empty 'exclude' with exclude-node-modules", async t => { 49 | const nyc = new NYC({ exclude: [], excludeNodeModules: false }) 50 | 51 | t.equal(nyc.exclude.exclude.length, 0) 52 | }) 53 | 54 | test('should resolve default cache folder to absolute path', async t => { 55 | const nyc = new NYC({ 56 | cache: true 57 | }) 58 | 59 | t.equal(path.isAbsolute(nyc.cacheDirectory), true) 60 | }) 61 | 62 | test('should resolve custom cache folder to absolute path', async t => { 63 | const nyc = new NYC({ 64 | cacheDir: '.nyc_cache', 65 | cache: true 66 | }) 67 | 68 | t.equal(path.isAbsolute(nyc.cacheDirectory), true) 69 | }) 70 | 71 | test('if cache is false _disableCachingTransform is true', async t => { 72 | const nycParent = new NYC({ cache: false, isChildProcess: false }) 73 | const nycChild = new NYC({ cache: false, isChildProcess: true }) 74 | 75 | t.equal(nycParent._disableCachingTransform(), true) 76 | t.equal(nycChild._disableCachingTransform(), true) 77 | }) 78 | 79 | test('if cache is true _disableCachingTransform is equal to !isChildProcess', async t => { 80 | const nycParent = new NYC({ cache: true, isChildProcess: false }) 81 | const nycChild = new NYC({ cache: true, isChildProcess: true }) 82 | 83 | t.equal(nycParent._disableCachingTransform(), true) 84 | t.equal(nycChild._disableCachingTransform(), false) 85 | }) 86 | -------------------------------------------------------------------------------- /test/cwd.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | 5 | const t = require('tap') 6 | 7 | const NYC = require('../self-coverage') 8 | 9 | const { parseArgv } = require('./helpers') 10 | 11 | t.beforeEach(async () => { 12 | delete process.env.NYC_CWD 13 | }) 14 | 15 | t.test('sets cwd to process.cwd() if no environment variable is set', async t => { 16 | const nyc = new NYC(await parseArgv()) 17 | 18 | t.equal(nyc.cwd, process.cwd()) 19 | }) 20 | 21 | t.test('uses NYC_CWD environment variable for cwd if it is set', async t => { 22 | const fixtures = path.resolve(__dirname, './fixtures') 23 | process.env.NYC_CWD = fixtures 24 | const nyc = new NYC(await parseArgv()) 25 | 26 | t.equal(nyc.cwd, fixtures) 27 | }) 28 | 29 | t.test('will look upwards for package.json from cwd', async t => { 30 | const nyc = new NYC(await parseArgv(__dirname)) 31 | 32 | t.equal(nyc.cwd, path.join(__dirname, '..')) 33 | }) 34 | 35 | t.test('uses --cwd for cwd if it is set (highest priority and does not look upwards for package.json) ', async t => { 36 | const nyc = new NYC(await parseArgv(__dirname, ['--cwd', __dirname])) 37 | 38 | t.equal(nyc.cwd, __dirname) 39 | }) 40 | -------------------------------------------------------------------------------- /test/eager.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | 5 | const t = require('tap') 6 | 7 | const { testSuccess } = require('./helpers') 8 | 9 | const cwd = path.resolve(__dirname, 'fixtures') 10 | 11 | t.test('eager disabled by default', t => testSuccess(t, { 12 | args: [ 13 | '--silent=true', 14 | '--exclude=eager.js', 15 | process.execPath, 16 | './eager.js' 17 | ], 18 | cwd 19 | })) 20 | 21 | t.test('eager enabled', t => testSuccess(t, { 22 | args: [ 23 | '--silent=true', 24 | '--eager=true', 25 | '--exclude=eager.js', 26 | process.execPath, 27 | './eager.js' 28 | ], 29 | cwd 30 | })) 31 | -------------------------------------------------------------------------------- /test/fixtures/all-type-module/extra.mjs: -------------------------------------------------------------------------------- 1 | export default function () { 2 | return 'es module'; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/all-type-module/index.js: -------------------------------------------------------------------------------- 1 | export default function () { 2 | return 'es module'; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/all-type-module/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "all-type-module", 3 | "version": "0.1.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "nyc": { 8 | "all": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/all-type-module/script.cjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | // Not doing anything, we just want something to run with `nyc --all` 5 | process.exit(0); 6 | -------------------------------------------------------------------------------- /test/fixtures/cache-collision-runner.js: -------------------------------------------------------------------------------- 1 | 2 | var path = require('path') 3 | 4 | var assert = require('assert') 5 | 6 | var spawn = require('child_process').spawn 7 | 8 | var time = process.hrtime() 9 | 10 | var workerPath = path.join(__dirname, './cache-collision-worker.js') 11 | 12 | function doFork (message) { 13 | spawn(process.execPath, [workerPath, String(time[0]), String(time[1]), message]) 14 | .on('close', function (code) { 15 | assert.equal(code, 0, 'received non-zero exit code ' + code) 16 | }) 17 | } 18 | 19 | doFork('foo') 20 | doFork('bar') 21 | doFork('baz') 22 | doFork('quz') 23 | doFork('nada') 24 | -------------------------------------------------------------------------------- /test/fixtures/cache-collision-target.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (foo) { 3 | if (foo === 'foo') { 4 | return 'this is a foo' 5 | } 6 | if (foo === 'bar') { 7 | return 'this is a bar' 8 | } 9 | if (foo === 'baz') { 10 | return 'this is a baz' 11 | } 12 | if (foo === 'quz') { 13 | return 'this is a quz' 14 | } 15 | } -------------------------------------------------------------------------------- /test/fixtures/cache-collision-worker.js: -------------------------------------------------------------------------------- 1 | 2 | var assert = require('assert') 3 | 4 | var start = [ 5 | parseInt(process.argv[2], 10), 6 | parseInt(process.argv[3], 10) 7 | ] 8 | 9 | var message = process.argv[4] 10 | 11 | var diff = process.hrtime(start) 12 | 13 | while (diff[0] * 1e9 + diff[1] < 3e9) { 14 | diff = process.hrtime(start) 15 | } 16 | 17 | 18 | assert.equal(require('./cache-collision-target')(message), message === 'nada' ? undefined : 'this is a ' + message) 19 | 20 | //assert.equal(process.env.NYC_CWD, __dirname) 21 | -------------------------------------------------------------------------------- /test/fixtures/check-instrumented.js: -------------------------------------------------------------------------------- 1 | function probe () {} 2 | 3 | // When instrumented there will be references to variables like 4 | // __cov_pwkoI2PYHp3LJXkn_erl1Q in the probe() source. 5 | module.exports = function () { 6 | return /\bcov_\B/.test(probe + '') 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/child-1.js: -------------------------------------------------------------------------------- 1 | // invoked by spawn. 2 | -------------------------------------------------------------------------------- /test/fixtures/child-2.js: -------------------------------------------------------------------------------- 1 | // invoked by spawn. 2 | -------------------------------------------------------------------------------- /test/fixtures/cli/.instrument-nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": [ 3 | "**/exclude-me/**" 4 | ], 5 | "complete-copy": true 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/cli/args.js: -------------------------------------------------------------------------------- 1 | console.log(JSON.stringify(process.argv)) 2 | -------------------------------------------------------------------------------- /test/fixtures/cli/by-arg2.js: -------------------------------------------------------------------------------- 1 | const arg = process.argv[2]; 2 | if (arg === '1') { 3 | console.log('1') 4 | } else if (arg === '2') { 5 | console.log('2') 6 | } else { 7 | console.log('unexpected') 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/cli/classes.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class Funclass { 4 | hit() { 5 | const miss = () => { 6 | console.log('This is intentionally uncovered'); 7 | } 8 | } 9 | 10 | skip() { 11 | console.log('this will be skipped'); 12 | } 13 | } 14 | 15 | new Funclass().hit(); 16 | -------------------------------------------------------------------------------- /test/fixtures/cli/conf-override-module.js: -------------------------------------------------------------------------------- 1 | const config = JSON.parse(process.env.NYC_CONFIG) 2 | console.log('in module', {include: config.include}) 3 | -------------------------------------------------------------------------------- /test/fixtures/cli/conf-override-root.js: -------------------------------------------------------------------------------- 1 | const config = JSON.parse(process.env.NYC_CONFIG) 2 | const { include } = config 3 | 4 | if (process.argv[2] !== 'child') { 5 | console.log('in parent', { include }) 6 | require('child_process').spawn(process.execPath, [__filename, 'child'], { 7 | cwd: __dirname, 8 | env: Object.assign( 9 | {}, 10 | process.env, 11 | { 12 | NYC_CONFIG_OVERRIDE: JSON.stringify({ 13 | include: 'conf-override-module.js' 14 | }) 15 | } 16 | ), 17 | stdio: 'inherit', 18 | }) 19 | } else { 20 | // this should run, but not be covered, even though the shebang says to 21 | // the child run ONLY covers the child file, not the dump-root.js 22 | console.log('in child', { include }) 23 | require('./conf-override-module.js') 24 | } 25 | -------------------------------------------------------------------------------- /test/fixtures/cli/empty.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/istanbuljs/nyc/41f4476f9f1010367d62c9e7841e14cfe4a2801a/test/fixtures/cli/empty.js -------------------------------------------------------------------------------- /test/fixtures/cli/env.js: -------------------------------------------------------------------------------- 1 | console.log(JSON.stringify(process.env)) 2 | -------------------------------------------------------------------------------- /test/fixtures/cli/es6.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class Yarsay { 4 | constructor() { 5 | console.log('sup') 6 | } 7 | 8 | hit() { 9 | console.log('do not hit') 10 | let miss = () => { 11 | console.log('do not hit') 12 | } 13 | } 14 | 15 | miss() { 16 | let miss = () => { 17 | console.log('do not hit') 18 | } 19 | } 20 | } 21 | 22 | let y = new Yarsay() 23 | y.hit() 24 | -------------------------------------------------------------------------------- /test/fixtures/cli/external-instrumenter.js: -------------------------------------------------------------------------------- 1 | 'use strict';var cov_4mo0jj89z=function(){var path="./external-instrumenter.js";var hash="258f33c73dcdfdb232fbc4b001a43ae5dc7da182";var global=new Function("return this")();var gcv="__coverage__";var coverageData={path:"./external-instrumenter.js",statementMap:{"0":{start:{line:5,column:4},end:{line:5,column:22}},"1":{start:{line:9,column:4},end:{line:9,column:29}},"2":{start:{line:10,column:15},end:{line:12,column:5}},"3":{start:{line:11,column:6},end:{line:11,column:31}},"4":{start:{line:16,column:15},end:{line:18,column:5}},"5":{start:{line:17,column:6},end:{line:17,column:31}},"6":{start:{line:22,column:8},end:{line:22,column:20}},"7":{start:{line:23,column:0},end:{line:23,column:7}}},fnMap:{"0":{name:"(anonymous_0)",decl:{start:{line:4,column:2},end:{line:4,column:3}},loc:{start:{line:4,column:16},end:{line:6,column:3}},line:4},"1":{name:"(anonymous_1)",decl:{start:{line:8,column:2},end:{line:8,column:3}},loc:{start:{line:8,column:8},end:{line:13,column:3}},line:8},"2":{name:"(anonymous_2)",decl:{start:{line:10,column:15},end:{line:10,column:16}},loc:{start:{line:10,column:21},end:{line:12,column:5}},line:10},"3":{name:"(anonymous_3)",decl:{start:{line:15,column:2},end:{line:15,column:3}},loc:{start:{line:15,column:9},end:{line:19,column:3}},line:15},"4":{name:"(anonymous_4)",decl:{start:{line:16,column:15},end:{line:16,column:16}},loc:{start:{line:16,column:21},end:{line:18,column:5}},line:16}},branchMap:{},s:{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0},f:{"0":0,"1":0,"2":0,"3":0,"4":0},b:{},_coverageSchema:"1a1c01bbd47fc00a2c39e90264f33305004495a9",hash:"258f33c73dcdfdb232fbc4b001a43ae5dc7da182"};var coverage=global[gcv]||(global[gcv]={});if(coverage[path]&&coverage[path].hash===hash){return coverage[path];}return coverage[path]=coverageData;}();class Yarsay{constructor(){cov_4mo0jj89z.f[0]++;cov_4mo0jj89z.s[0]++;console.log('sup');}hit(){cov_4mo0jj89z.f[1]++;cov_4mo0jj89z.s[1]++;console.log('do not hit');cov_4mo0jj89z.s[2]++;let miss=()=>{cov_4mo0jj89z.f[2]++;cov_4mo0jj89z.s[3]++;console.log('do not hit');};}miss(){cov_4mo0jj89z.f[3]++;cov_4mo0jj89z.s[4]++;let miss=()=>{cov_4mo0jj89z.f[4]++;cov_4mo0jj89z.s[5]++;console.log('do not hit');};}}let y=(cov_4mo0jj89z.s[6]++,new Yarsay());cov_4mo0jj89z.s[7]++;y.hit(); 2 | -------------------------------------------------------------------------------- /test/fixtures/cli/fakebin/.gitignore: -------------------------------------------------------------------------------- 1 | node 2 | npm 3 | -------------------------------------------------------------------------------- /test/fixtures/cli/fakebin/npm-template.js: -------------------------------------------------------------------------------- 1 | // shebang gets added before this line 2 | var which = require('which') 3 | var assert = require('assert') 4 | var { foregroundChild } = require('foreground-child') 5 | 6 | // strip first PATH folder 7 | process.env.PATH = process.env.PATH.replace(/^.+?[:;]/, '') 8 | 9 | foregroundChild('npm', process.argv.slice(2)) 10 | -------------------------------------------------------------------------------- /test/fixtures/cli/gc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | gc(); 3 | console.log('I’m still running'); 4 | -------------------------------------------------------------------------------- /test/fixtures/cli/half-covered-failing.js: -------------------------------------------------------------------------------- 1 | var a = 0 2 | 3 | process.exit(1) 4 | 5 | if (a === 0) { 6 | a++; 7 | a--; 8 | a++; 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/cli/half-covered.js: -------------------------------------------------------------------------------- 1 | var a = 0 2 | 3 | a++ 4 | 5 | if (a === 0) { 6 | a++; 7 | a--; 8 | a++; 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/cli/instrument-inplace/file1.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | return 'file1' 3 | } 4 | 5 | module.exports = test 6 | -------------------------------------------------------------------------------- /test/fixtures/cli/instrument-inplace/file2.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | return 'file2' 3 | } 4 | 5 | module.exports = test 6 | -------------------------------------------------------------------------------- /test/fixtures/cli/instrument-inplace/package.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /test/fixtures/cli/merge-input/a.json: -------------------------------------------------------------------------------- 1 | { 2 | "/private/tmp/contrived/library.js": { 3 | "path": "/private/tmp/contrived/library.js", 4 | "statementMap": { 5 | "0": { 6 | "start": { 7 | "line": 1, 8 | "column": 0 9 | }, 10 | "end": { 11 | "line": 15, 12 | "column": 1 13 | } 14 | }, 15 | "1": { 16 | "start": { 17 | "line": 3, 18 | "column": 4 19 | }, 20 | "end": { 21 | "line": 3, 22 | "column": 16 23 | } 24 | }, 25 | "2": { 26 | "start": { 27 | "line": 6, 28 | "column": 4 29 | }, 30 | "end": { 31 | "line": 6, 32 | "column": 16 33 | } 34 | }, 35 | "3": { 36 | "start": { 37 | "line": 9, 38 | "column": 4 39 | }, 40 | "end": { 41 | "line": 13, 42 | "column": 5 43 | } 44 | }, 45 | "4": { 46 | "start": { 47 | "line": 10, 48 | "column": 6 49 | }, 50 | "end": { 51 | "line": 10, 52 | "column": 14 53 | } 54 | }, 55 | "5": { 56 | "start": { 57 | "line": 12, 58 | "column": 6 59 | }, 60 | "end": { 61 | "line": 12, 62 | "column": 14 63 | } 64 | } 65 | }, 66 | "fnMap": { 67 | "0": { 68 | "name": "(anonymous_0)", 69 | "decl": { 70 | "start": { 71 | "line": 2, 72 | "column": 11 73 | }, 74 | "end": { 75 | "line": 2, 76 | "column": 12 77 | } 78 | }, 79 | "loc": { 80 | "start": { 81 | "line": 2, 82 | "column": 21 83 | }, 84 | "end": { 85 | "line": 4, 86 | "column": 3 87 | } 88 | }, 89 | "line": 2 90 | }, 91 | "1": { 92 | "name": "(anonymous_1)", 93 | "decl": { 94 | "start": { 95 | "line": 5, 96 | "column": 11 97 | }, 98 | "end": { 99 | "line": 5, 100 | "column": 12 101 | } 102 | }, 103 | "loc": { 104 | "start": { 105 | "line": 5, 106 | "column": 21 107 | }, 108 | "end": { 109 | "line": 7, 110 | "column": 3 111 | } 112 | }, 113 | "line": 5 114 | }, 115 | "2": { 116 | "name": "(anonymous_2)", 117 | "decl": { 118 | "start": { 119 | "line": 8, 120 | "column": 11 121 | }, 122 | "end": { 123 | "line": 8, 124 | "column": 12 125 | } 126 | }, 127 | "loc": { 128 | "start": { 129 | "line": 8, 130 | "column": 18 131 | }, 132 | "end": { 133 | "line": 14, 134 | "column": 3 135 | } 136 | }, 137 | "line": 8 138 | } 139 | }, 140 | "branchMap": { 141 | "0": { 142 | "loc": { 143 | "start": { 144 | "line": 9, 145 | "column": 4 146 | }, 147 | "end": { 148 | "line": 13, 149 | "column": 5 150 | } 151 | }, 152 | "type": "if", 153 | "locations": [ 154 | { 155 | "start": { 156 | "line": 9, 157 | "column": 4 158 | }, 159 | "end": { 160 | "line": 13, 161 | "column": 5 162 | } 163 | }, 164 | { 165 | "start": { 166 | "line": 9, 167 | "column": 4 168 | }, 169 | "end": { 170 | "line": 13, 171 | "column": 5 172 | } 173 | } 174 | ], 175 | "line": 9 176 | } 177 | }, 178 | "s": { 179 | "0": 1, 180 | "1": 1, 181 | "2": 0, 182 | "3": 1, 183 | "4": 0, 184 | "5": 1 185 | }, 186 | "f": { 187 | "0": 1, 188 | "1": 0, 189 | "2": 1 190 | }, 191 | "b": { 192 | "0": [ 193 | 0, 194 | 1 195 | ] 196 | }, 197 | "_coverageSchema": "332fd63041d2c1bcb487cc26dd0d5f7d97098a6c", 198 | "hash": "e86c0c0fa7c4fadac81e2479bfba3c0d59b657aa" 199 | } 200 | } -------------------------------------------------------------------------------- /test/fixtures/cli/merge-input/b.json: -------------------------------------------------------------------------------- 1 | { 2 | "/private/tmp/contrived/library.js": { 3 | "path": "/private/tmp/contrived/library.js", 4 | "statementMap": { 5 | "0": { 6 | "start": { 7 | "line": 1, 8 | "column": 0 9 | }, 10 | "end": { 11 | "line": 15, 12 | "column": 1 13 | } 14 | }, 15 | "1": { 16 | "start": { 17 | "line": 3, 18 | "column": 4 19 | }, 20 | "end": { 21 | "line": 3, 22 | "column": 16 23 | } 24 | }, 25 | "2": { 26 | "start": { 27 | "line": 6, 28 | "column": 4 29 | }, 30 | "end": { 31 | "line": 6, 32 | "column": 16 33 | } 34 | }, 35 | "3": { 36 | "start": { 37 | "line": 9, 38 | "column": 4 39 | }, 40 | "end": { 41 | "line": 13, 42 | "column": 5 43 | } 44 | }, 45 | "4": { 46 | "start": { 47 | "line": 10, 48 | "column": 6 49 | }, 50 | "end": { 51 | "line": 10, 52 | "column": 14 53 | } 54 | }, 55 | "5": { 56 | "start": { 57 | "line": 12, 58 | "column": 6 59 | }, 60 | "end": { 61 | "line": 12, 62 | "column": 14 63 | } 64 | } 65 | }, 66 | "fnMap": { 67 | "0": { 68 | "name": "(anonymous_0)", 69 | "decl": { 70 | "start": { 71 | "line": 2, 72 | "column": 11 73 | }, 74 | "end": { 75 | "line": 2, 76 | "column": 12 77 | } 78 | }, 79 | "loc": { 80 | "start": { 81 | "line": 2, 82 | "column": 21 83 | }, 84 | "end": { 85 | "line": 4, 86 | "column": 3 87 | } 88 | }, 89 | "line": 2 90 | }, 91 | "1": { 92 | "name": "(anonymous_1)", 93 | "decl": { 94 | "start": { 95 | "line": 5, 96 | "column": 11 97 | }, 98 | "end": { 99 | "line": 5, 100 | "column": 12 101 | } 102 | }, 103 | "loc": { 104 | "start": { 105 | "line": 5, 106 | "column": 21 107 | }, 108 | "end": { 109 | "line": 7, 110 | "column": 3 111 | } 112 | }, 113 | "line": 5 114 | }, 115 | "2": { 116 | "name": "(anonymous_2)", 117 | "decl": { 118 | "start": { 119 | "line": 8, 120 | "column": 11 121 | }, 122 | "end": { 123 | "line": 8, 124 | "column": 12 125 | } 126 | }, 127 | "loc": { 128 | "start": { 129 | "line": 8, 130 | "column": 18 131 | }, 132 | "end": { 133 | "line": 14, 134 | "column": 3 135 | } 136 | }, 137 | "line": 8 138 | } 139 | }, 140 | "branchMap": { 141 | "0": { 142 | "loc": { 143 | "start": { 144 | "line": 9, 145 | "column": 4 146 | }, 147 | "end": { 148 | "line": 13, 149 | "column": 5 150 | } 151 | }, 152 | "type": "if", 153 | "locations": [ 154 | { 155 | "start": { 156 | "line": 9, 157 | "column": 4 158 | }, 159 | "end": { 160 | "line": 13, 161 | "column": 5 162 | } 163 | }, 164 | { 165 | "start": { 166 | "line": 9, 167 | "column": 4 168 | }, 169 | "end": { 170 | "line": 13, 171 | "column": 5 172 | } 173 | } 174 | ], 175 | "line": 9 176 | } 177 | }, 178 | "s": { 179 | "0": 1, 180 | "1": 0, 181 | "2": 1, 182 | "3": 1, 183 | "4": 1, 184 | "5": 0 185 | }, 186 | "f": { 187 | "0": 0, 188 | "1": 1, 189 | "2": 1 190 | }, 191 | "b": { 192 | "0": [ 193 | 1, 194 | 0 195 | ] 196 | }, 197 | "_coverageSchema": "332fd63041d2c1bcb487cc26dd0d5f7d97098a6c", 198 | "hash": "e86c0c0fa7c4fadac81e2479bfba3c0d59b657aa" 199 | } 200 | } -------------------------------------------------------------------------------- /test/fixtures/cli/no-transform/half-covered.xjs: -------------------------------------------------------------------------------- 1 | var a = 0 2 | 3 | a++ 4 | 5 | if (a === 0) { 6 | a++; 7 | a--; 8 | a++; 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/cli/not-strict.js: -------------------------------------------------------------------------------- 1 | function package() { 2 | return 1; 3 | } 4 | 5 | package(); 6 | -------------------------------------------------------------------------------- /test/fixtures/cli/nyc-config-js/ignore.js: -------------------------------------------------------------------------------- 1 | var i = 2 2 | -------------------------------------------------------------------------------- /test/fixtures/cli/nyc-config-js/index.js: -------------------------------------------------------------------------------- 1 | require('./ignore') 2 | 3 | var a = 0 4 | 5 | a++ 6 | 7 | if (a === 0) { 8 | a++; 9 | a--; 10 | a++; 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/cli/nyc-config-js/nyc.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | exclude: ['nyc.config.js', 'nycrc-config.js', 'ignore.js'] 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/cli/nyc-config-js/nycrc-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'check-coverage': true, 3 | 'per-file': true, 4 | lines: 100, 5 | statements: 100, 6 | functions: 100, 7 | branches: 100, 8 | exclude: [] 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/cli/nyc-config-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "nyc": { 3 | "reporter": ["text-lcov"] 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/cli/nycrc/.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": ["ignore.js"] 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/cli/nycrc/.nycrc-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "check-coverage": true, 3 | "per-file": true, 4 | "lines": 100, 5 | "statements": 100, 6 | "functions": 100, 7 | "branches": 100, 8 | "exclude": [] 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/cli/nycrc/.nycrc.yaml: -------------------------------------------------------------------------------- 1 | exclude: 2 | - ignore.js 3 | -------------------------------------------------------------------------------- /test/fixtures/cli/nycrc/.nycrc.yml: -------------------------------------------------------------------------------- 1 | exclude: 2 | - ignore.js 3 | -------------------------------------------------------------------------------- /test/fixtures/cli/nycrc/ignore.js: -------------------------------------------------------------------------------- 1 | var i = 2 2 | -------------------------------------------------------------------------------- /test/fixtures/cli/nycrc/index.js: -------------------------------------------------------------------------------- 1 | require('./ignore') 2 | 3 | var a = 0 4 | 5 | a++ 6 | 7 | if (a === 0) { 8 | a++; 9 | a--; 10 | a++; 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/cli/nycrc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "nyc": { 3 | "reporter": ["text-lcov"] 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "nyc": { 4 | "reporter": ["text"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/cli/run-npm-test-recursive/half-covered.js: -------------------------------------------------------------------------------- 1 | var a = 0 2 | 3 | a++ 4 | 5 | if (a === 0) { 6 | a++; 7 | a--; 8 | a++; 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/cli/run-npm-test-recursive/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "test": "npm run test:deeper", 5 | "test:deeper": "npm run test:even-deeper", 6 | "test:even-deeper": "node ./half-covered.js" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/cli/run-npm-test/half-covered.js: -------------------------------------------------------------------------------- 1 | var a = 0 2 | 3 | a++ 4 | 5 | if (a === 0) { 6 | a++; 7 | a--; 8 | a++; 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/cli/run-npm-test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "test": "node ./half-covered.js" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/cli/selfspawn-fibonacci.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var cp = require('child_process'); 3 | 4 | process.env = {}; 5 | 6 | var index = +process.argv[2] || 0 7 | if (index <= 1) { 8 | console.log(0) 9 | return 10 | } 11 | if (index == 2) { 12 | console.log(1) 13 | return 14 | } 15 | 16 | function getFromChild(n, cb) { 17 | var proc = cp.spawn(process.execPath, [__filename, n]) 18 | var stdout = '' 19 | proc.stdout.on('data', function (data) { stdout += data }) 20 | proc.on('close', function () { 21 | cb(null, +stdout) 22 | }) 23 | proc.on('error', cb) 24 | } 25 | 26 | getFromChild(index - 1, function(err, result1) { 27 | if (err) throw err 28 | getFromChild(index - 2, function(err, result2) { 29 | if (err) throw err 30 | console.log(result1 + result2) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /test/fixtures/cli/skip-full.js: -------------------------------------------------------------------------------- 1 | require('./empty') 2 | require('./half-covered') -------------------------------------------------------------------------------- /test/fixtures/cli/subdir/.gitignore: -------------------------------------------------------------------------------- 1 | output-dir 2 | !node_modules 3 | -------------------------------------------------------------------------------- /test/fixtures/cli/subdir/input-dir/bad.js: -------------------------------------------------------------------------------- 1 | generate async futurelet console.log('Hello, World!') // this isn't real JS. 2 | -------------------------------------------------------------------------------- /test/fixtures/cli/subdir/input-dir/exclude-me/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | console.log('Hello, World!') 3 | -------------------------------------------------------------------------------- /test/fixtures/cli/subdir/input-dir/include-me/exclude-me.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | console.log('Hello, World!') 3 | -------------------------------------------------------------------------------- /test/fixtures/cli/subdir/input-dir/include-me/include-me.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | console.log('Hello, World!') 3 | -------------------------------------------------------------------------------- /test/fixtures/cli/subdir/input-dir/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | console.log('Hello, World!') 3 | -------------------------------------------------------------------------------- /test/fixtures/cli/subdir/input-dir/node_modules/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | console.log('Hello, World!') 3 | -------------------------------------------------------------------------------- /test/fixtures/cli/test.js: -------------------------------------------------------------------------------- 1 | // should be excluded by default. 2 | -------------------------------------------------------------------------------- /test/fixtures/conf-empty/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nyc", 3 | "version": "1.1.1", 4 | "description": "forking code-coverage using istanbul.", 5 | "main": "index.js" 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/conf-multiple-extensions/check-instrumented.es6: -------------------------------------------------------------------------------- 1 | function probe () {} 2 | 3 | // When instrumented there will be references to variables like 4 | // __cov_pwkoI2PYHp3LJXkn_erl1Q in the probe() source. 5 | module.exports = function () { 6 | return /\bcov_\B/.test(probe + '') 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/conf-multiple-extensions/check-instrumented.foo.bar: -------------------------------------------------------------------------------- 1 | function probe () {} 2 | 3 | // When instrumented there will be references to variables like 4 | // __cov_pwkoI2PYHp3LJXkn_erl1Q in the probe() source. 5 | module.exports = function () { 6 | return /\bcov_\B/.test(probe + '') 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/conf-multiple-extensions/check-instrumented.js: -------------------------------------------------------------------------------- 1 | function probe () {} 2 | 3 | // When instrumented there will be references to variables like 4 | // __cov_pwkoI2PYHp3LJXkn_erl1Q in the probe() source. 5 | module.exports = function () { 6 | return /\b__cov_\B/.test(probe + '') 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/conf-multiple-extensions/not-loaded.es6: -------------------------------------------------------------------------------- 1 | var i = 3 + 5 2 | i++ 3 | -------------------------------------------------------------------------------- /test/fixtures/conf-multiple-extensions/not-loaded.js: -------------------------------------------------------------------------------- 1 | var i = 3 + 5 2 | i++ 3 | -------------------------------------------------------------------------------- /test/fixtures/conf-multiple-extensions/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 | "nyc": { 11 | "exclude": [ 12 | "**/blarg", 13 | "**/blerg" 14 | ], 15 | "extension": [".es6", ".foo.BAR"] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/fixtures/conf-multiple-extensions/run.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | console.log('run'); 5 | -------------------------------------------------------------------------------- /test/fixtures/eager.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | const lib = require.resolve('istanbul-lib-instrument') 5 | console.log(Object.keys(require.cache).filter(s => s === lib).length) 6 | -------------------------------------------------------------------------------- /test/fixtures/exclude-node-modules/.gitignore: -------------------------------------------------------------------------------- 1 | !node_modules 2 | node_modules/.cache/ 3 | -------------------------------------------------------------------------------- /test/fixtures/exclude-node-modules/bin/do-nothing.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/istanbuljs/nyc/41f4476f9f1010367d62c9e7841e14cfe4a2801a/test/fixtures/exclude-node-modules/bin/do-nothing.js -------------------------------------------------------------------------------- /test/fixtures/exclude-node-modules/node_modules/@istanbuljs/fake-module-1/index.js: -------------------------------------------------------------------------------- 1 | console.log('hello world'); 2 | -------------------------------------------------------------------------------- /test/fixtures/exclude-node-modules/node_modules/@istanbuljs/fake-module-2/index.js: -------------------------------------------------------------------------------- 1 | console.log('hello world'); 2 | -------------------------------------------------------------------------------- /test/fixtures/exclude-node-modules/package.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /test/fixtures/hooks/index.js: -------------------------------------------------------------------------------- 1 | // RequireJS uses `vm.runInThisContext` 2 | // make sure we add hooks for it as well 3 | 4 | var rjs = require('requirejs'), 5 | assert = require('assert'); 6 | 7 | rjs.config({ 8 | baseUrl : __dirname, 9 | nodeRequire : require 10 | }); 11 | 12 | rjs(['./lib/lorem'], function(lorem){ 13 | var result = lorem(1, 2, 3); 14 | assert.equal(9, result); 15 | }); 16 | 17 | -------------------------------------------------------------------------------- /test/fixtures/hooks/lib/ipsum.js: -------------------------------------------------------------------------------- 1 | define(function(){ 2 | 3 | function sum(a, b) { 4 | return a + b; 5 | } 6 | 7 | return { 8 | sum : sum 9 | }; 10 | 11 | }); 12 | -------------------------------------------------------------------------------- /test/fixtures/hooks/lib/lorem.js: -------------------------------------------------------------------------------- 1 | define(['./ipsum'], function (ipsum) { 2 | 3 | return function exec(a, b, c){ 4 | return ipsum.sum(a, b) * c; 5 | }; 6 | 7 | }); 8 | -------------------------------------------------------------------------------- /test/fixtures/hooks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hooktest", 3 | "version": "1.1.1", 4 | "description": "AMD/ requirejs test project", 5 | "main": "index.js", 6 | "dependencies": { 7 | "requirejs": "^2.2.0" 8 | }, 9 | "private": true 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/hooks/run-in-context.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const vm = require('vm') 4 | 5 | vm.runInContext( 6 | '(() => 10)();', 7 | vm.createContext({}), 8 | path.resolve(__dirname, 'in-context.js') 9 | ) 10 | -------------------------------------------------------------------------------- /test/fixtures/identical-file-runner.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const assert = require('assert') 3 | const file1 = require('./identical-file1.js') 4 | const file2 = require('./identical-file2.js') 5 | 6 | assert.equal(file1(), file2()) 7 | 8 | const cov = (new Function('return this.__coverage__'))() 9 | 10 | assert.deepEqual(Object.keys(cov).sort(), [ 11 | __filename, 12 | path.resolve('identical-file1.js'), 13 | path.resolve('identical-file2.js') 14 | ]) 15 | -------------------------------------------------------------------------------- /test/fixtures/identical-file1.js: -------------------------------------------------------------------------------- 1 | function identical() { 2 | return 'identical' 3 | } 4 | 5 | module.exports = identical 6 | -------------------------------------------------------------------------------- /test/fixtures/identical-file2.js: -------------------------------------------------------------------------------- 1 | function identical() { 2 | return 'identical' 3 | } 4 | 5 | module.exports = identical 6 | -------------------------------------------------------------------------------- /test/fixtures/not-loaded.js: -------------------------------------------------------------------------------- 1 | var i = 3 + 5 2 | i++ 3 | -------------------------------------------------------------------------------- /test/fixtures/nyc.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // This just blocks fixtures from seeing the root nyc.config.js 4 | module.exports = {} 5 | -------------------------------------------------------------------------------- /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 | "nyc": { 11 | "exclude": [ 12 | "**/blarg.js", 13 | "**/blerg.js" 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/fixtures/parser-plugins/no-plugins.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser-plugins": [] 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/parser-plugins/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "nyc": { 3 | "parser-plugins": [ 4 | "v8intrinsic" 5 | ] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/parser-plugins/v8.js: -------------------------------------------------------------------------------- 1 | function fn() {} 2 | %GetOptimizationStatus(fn) 3 | -------------------------------------------------------------------------------- /test/fixtures/recursive-run/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "nyc": { 3 | "reporter": [] 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/fixtures/source-maps/instrumented/s1.min.js: -------------------------------------------------------------------------------- 1 | var apple=99;var banana=200;function add(item1,item2){return item1+item2}function multiply(item1,item2){return item1*item2}add(apple,banana); 2 | //# sourceMappingURL=s1.min.js.map 3 | -------------------------------------------------------------------------------- /test/fixtures/source-maps/instrumented/s1.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3, "sourceRoot": "../", "sources":["original/s1.js"],"names":["apple","banana","add","item1","item2","multiply"],"mappings":"AAAA,IAAIA,MAAQ,GACZ,IAAIC,OAAS,IACb,SAASC,IAAKC,MAAOC,OACnB,OAAOD,MAAQC,MAEjB,SAASC,SAAUF,MAAOC,OACxB,OAAOD,MAAQC,MAEjBF,IAAIF,MAAOC"} 2 | -------------------------------------------------------------------------------- /test/fixtures/source-maps/instrumented/s2.min.js: -------------------------------------------------------------------------------- 1 | var strawberry=99;var pineapple=200;function add(item1,item2){return item1+item2}add(strawberry,pineapple); 2 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm9yaWdpbmFsL3MyLmpzIl0sIm5hbWVzIjpbInN0cmF3YmVycnkiLCJwaW5lYXBwbGUiLCJhZGQiLCJpdGVtMSIsIml0ZW0yIl0sIm1hcHBpbmdzIjoiQUFBQSxJQUFJQSxXQUFhLEdBQ2pCLElBQUlDLFVBQVksSUFDaEIsU0FBU0MsSUFBS0MsTUFBT0MsT0FDbkIsT0FBT0QsTUFBUUMsTUFFakJGLElBQUlGLFdBQVlDIiwic291cmNlUm9vdCI6Ii4uLyJ9 -------------------------------------------------------------------------------- /test/fixtures/source-maps/original/s1.js: -------------------------------------------------------------------------------- 1 | var apple = 99 2 | var banana = 200 3 | function add (item1, item2) { 4 | return item1 + item2 5 | } 6 | function multiply (item1, item2) { 7 | return item1 * item2 8 | } 9 | add(apple, banana) 10 | -------------------------------------------------------------------------------- /test/fixtures/source-maps/original/s2.js: -------------------------------------------------------------------------------- 1 | var strawberry = 99 2 | var pineapple = 200 3 | function add (item1, item2) { 4 | return item1 + item2 5 | } 6 | add(strawberry, pineapple) 7 | -------------------------------------------------------------------------------- /test/fixtures/source-maps/package.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /test/fixtures/spawn.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var spawn = require('child_process').spawn 4 | spawn(process.execPath, ['child-1.js'], { cwd: __dirname }) 5 | spawn(process.execPath, ['child-2.js'], { cwd: __dirname }) 6 | -------------------------------------------------------------------------------- /test/fixtures/stack-trace.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function blah() { 4 | throw new Error('Blarrh') 5 | } 6 | 7 | var stack; 8 | try { 9 | blah(); 10 | } catch(err) { 11 | stack = err.stack; 12 | } 13 | 14 | module.exports = function() { 15 | return stack; 16 | } 17 | -------------------------------------------------------------------------------- /test/fixtures/transpile-hook.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | 3 | require.extensions['.js'] = function (module, filename) { 4 | var content = fs.readFileSync(filename, 'utf8'); 5 | module._compile(content.replace('--> pork chop sandwiches <--', ''), filename); 6 | } 7 | 8 | require.extensions['.whatever'] = function (module, filename) { 9 | var content = fs.readFileSync(filename, 'utf8'); 10 | module._compile(content.replace('--> pork chop sandwiches <--', ''), filename); 11 | } 12 | 13 | require.extensions['.do-not-transpile'] = function (module, filename) { 14 | throw new Error(`Should not transpile ${filename}`) 15 | } -------------------------------------------------------------------------------- /test/fixtures/tsc/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /test/fixtures/tsc/mapping.js: -------------------------------------------------------------------------------- 1 | var __assign = (this && this.__assign) || function () { 2 | __assign = Object.assign || function(t) { 3 | for (var s, i = 1, n = arguments.length; i < n; i++) { 4 | s = arguments[i]; 5 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) 6 | t[p] = s[p]; 7 | } 8 | return t; 9 | }; 10 | return __assign.apply(this, arguments); 11 | }; 12 | function munge1(obj) { 13 | return __assign({ name: 'munge1' }, obj); 14 | } 15 | munge1({}); 16 | //# sourceMappingURL=mapping.js.map -------------------------------------------------------------------------------- /test/fixtures/tsc/mapping.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"mapping.js","sourceRoot":"","sources":["mapping.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,SAAS,MAAM,CAAE,GAAW;IAC1B,kBACE,IAAI,EAAE,QAAQ,IACX,GAAG,EACN;AACJ,CAAC;AAED,MAAM,CAAC,EAAE,CAAC,CAAA"} -------------------------------------------------------------------------------- /test/fixtures/tsc/mapping.ts: -------------------------------------------------------------------------------- 1 | function munge1 (obj: Object) { 2 | return { 3 | name: 'munge1', 4 | ...obj 5 | }; 6 | } 7 | 8 | munge1({}) 9 | -------------------------------------------------------------------------------- /test/fixtures/tsc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "postinstall": "tsc" 5 | }, 6 | "devDependencies": { 7 | "typescript": "^3.6.3" 8 | }, 9 | "nyc": { 10 | "reporter": [ 11 | "text", 12 | "html" 13 | ] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/fixtures/tsc/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true 4 | }, 5 | "files": [ 6 | "mapping.ts" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /test/helpers/env-check-config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const runNYC = require('./run-nyc') 4 | 5 | async function envCheckConfig (t, { configArgs, checkOptions }) { 6 | const { stdout, stderr, status } = await runNYC({ 7 | tempDir: t.tempDir, 8 | leavePathSep: true, 9 | args: [ 10 | ...configArgs, 11 | process.execPath, 12 | './env.js' 13 | ] 14 | }) 15 | 16 | const config = JSON.parse(JSON.parse(stdout).NYC_CONFIG) 17 | 18 | t.equal(status, 0) 19 | t.equal(stderr, '') 20 | t.matchSnapshot( 21 | JSON.stringify( 22 | checkOptions.sort().map(option => [option, config[option]]), 23 | null, 24 | 2 25 | ) 26 | ) 27 | } 28 | 29 | module.exports = envCheckConfig 30 | -------------------------------------------------------------------------------- /test/helpers/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { fixturesCLI, nycBin } = require('./paths') 4 | 5 | module.exports = { 6 | fixturesCLI, 7 | nycBin, 8 | resetState: require('./reset-state'), 9 | spawn: require('./spawn'), 10 | testSuccess: require('./test-success'), 11 | testFailure: require('./test-failure'), 12 | runNYC: require('./run-nyc'), 13 | tempDirSetup: require('./temp-dir-setup'), 14 | envCheckConfig: require('./env-check-config'), 15 | parseArgv: require('./parse-argv') 16 | } 17 | -------------------------------------------------------------------------------- /test/helpers/parse-argv.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const configUtil = require('../../self-coverage/lib/config-util') 4 | 5 | async function parseArgv (cwd, argv) { 6 | const { yargs } = await configUtil(cwd) 7 | 8 | return yargs.parse(argv) 9 | } 10 | 11 | module.exports = parseArgv 12 | -------------------------------------------------------------------------------- /test/helpers/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | 5 | module.exports = { 6 | nycBin: require.resolve('../../self-coverage/bin/nyc'), 7 | fixturesCLI: path.resolve(__dirname, '../fixtures/cli') 8 | } 9 | -------------------------------------------------------------------------------- /test/helpers/reset-state.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { promisify } = require('util') 4 | 5 | // reset global state modified by nyc in non-integration tests. 6 | const extensions = Object.assign({}, require.extensions) // eslint-disable-line 7 | 8 | const glob = promisify(require('glob')) 9 | const rimraf = promisify(require('rimraf')) 10 | 11 | module.exports = async function () { 12 | // nuke any temporary files created during test runs. 13 | const files = await glob('test/**/*/{.nyc_output,.cache}') 14 | await Promise.all(files.map(f => rimraf(f))) 15 | 16 | // reset Node's require cache. 17 | Object.keys(require.cache).forEach((key) => { 18 | if (key.indexOf('node_modules') === -1) delete require.cache[key] 19 | }) 20 | 21 | // reset any custom loaders for extensions, disabling the stack maintained 22 | // by append-transform. 23 | Object.keys(require.extensions).forEach((key) => { // eslint-disable-line 24 | delete require.extensions[key] // eslint-disable-line 25 | if (extensions[key]) { 26 | require.extensions[key] = extensions[key] // eslint-disable-line 27 | } 28 | }) 29 | 30 | // reset any NYC-specific environment variables that might have been set. 31 | delete process.env.NYC_CWD 32 | } 33 | -------------------------------------------------------------------------------- /test/helpers/run-nyc.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { nycBin, fixturesCLI } = require('./paths') 4 | const spawn = require('./spawn') 5 | 6 | const envPath = { 7 | PATH: process.env.PATH 8 | } 9 | 10 | // Work around a Windows issue with `APPDATA`, 11 | // https://github.com/istanbuljs/nyc/issues/1248 12 | if ('APPDATA' in process.env) { 13 | envPath.APPDATA = process.env.APPDATA 14 | } 15 | 16 | function sanitizeString (str, cwd, leavePathSep) { 17 | /* 18 | * File paths are different on different systems: 19 | * - make everything relative to cwd 20 | * - replace full node path with 'node' 21 | * - replace all Windows path separators ('\\') with POSIX path separators 22 | */ 23 | str = str 24 | .split(cwd).join('.') 25 | .split(process.execPath).join('node') 26 | 27 | if (!leavePathSep) { 28 | str = str.replace(/\\/g, '/') 29 | } 30 | 31 | return str 32 | } 33 | 34 | async function runNYC ({ args, tempDir, leavePathSep, cwd = fixturesCLI, env = {} }) { 35 | const runArgs = [nycBin].concat(tempDir ? ['--temp-dir', tempDir] : [], args) 36 | const { status, stderr, stdout } = await spawn(process.execPath, runArgs, { 37 | cwd: cwd, 38 | env: { 39 | ...envPath, 40 | ...env 41 | } 42 | }) 43 | 44 | return { 45 | status, 46 | originalText: { 47 | stderr, 48 | stdout 49 | }, 50 | stderr: sanitizeString(stderr.toString('utf8'), cwd, leavePathSep), 51 | stdout: sanitizeString(stdout.toString('utf8'), cwd, leavePathSep) 52 | } 53 | } 54 | 55 | module.exports = runNYC 56 | -------------------------------------------------------------------------------- /test/helpers/source-map-support.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('source-map-support').install({ hookRequire: true }) 4 | -------------------------------------------------------------------------------- /test/helpers/spawn.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const cp = require('child_process') 4 | 5 | function spawn (exe, args, opts) { 6 | return new Promise((resolve, reject) => { 7 | const proc = cp.spawn(exe, args, opts) 8 | const stdout = [] 9 | const stderr = [] 10 | 11 | proc.stdout.on('data', buf => stdout.push(buf)) 12 | proc.stderr.on('data', buf => stderr.push(buf)) 13 | 14 | proc.on('error', reject) 15 | proc.on('close', status => { 16 | resolve({ 17 | status, 18 | stdout: Buffer.concat(stdout).toString(), 19 | stderr: Buffer.concat(stderr).toString() 20 | }) 21 | }) 22 | }) 23 | } 24 | 25 | module.exports = spawn 26 | -------------------------------------------------------------------------------- /test/helpers/temp-dir-setup.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const fs = require('fs') 5 | const makeDir = require('make-dir') 6 | const _rimraf = require('rimraf') 7 | const { promisify } = require('util') 8 | 9 | const rimraf = promisify(_rimraf) 10 | const mkdtemp = promisify(fs.mkdtemp) 11 | 12 | function tempDirSetup (t, testFile) { 13 | const { dir, name } = path.parse(testFile) 14 | const tempDirBase = path.resolve(dir, 'temp-dir-' + name) 15 | 16 | makeDir.sync(tempDirBase) 17 | 18 | // Do not use arrow function for beforeEach 19 | // or afterEach, they need this from tap. 20 | t.beforeEach(async function (t) { 21 | t.tempDir = await mkdtemp(tempDirBase + '/') 22 | }) 23 | 24 | t.afterEach(function (t) { 25 | return rimraf(t.tempDir) 26 | }) 27 | 28 | t.teardown(() => rimraf(tempDirBase)) 29 | } 30 | 31 | module.exports = tempDirSetup 32 | -------------------------------------------------------------------------------- /test/helpers/test-failure.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const runNYC = require('./run-nyc') 4 | 5 | async function testFailure (t, opts) { 6 | const { status, stderr, stdout } = await runNYC({ 7 | tempDir: t.tempDir, 8 | ...opts 9 | }) 10 | 11 | t.equal(status, 1) 12 | t.matchSnapshot(stderr, 'stderr') 13 | t.matchSnapshot(stdout, 'stdout') 14 | } 15 | 16 | module.exports = testFailure 17 | -------------------------------------------------------------------------------- /test/helpers/test-success.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const runNYC = require('./run-nyc') 4 | 5 | async function testSuccess (t, opts) { 6 | const { status, stderr, stdout } = await runNYC({ 7 | tempDir: t.tempDir, 8 | ...opts 9 | }) 10 | 11 | t.equal(status, 0) 12 | t.equal(stderr, '') 13 | t.matchSnapshot(stdout, 'stdout') 14 | } 15 | 16 | module.exports = testSuccess 17 | -------------------------------------------------------------------------------- /test/instrument.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('../lib/fs-promises') 4 | const path = require('path') 5 | const { promisify } = require('util') 6 | 7 | const t = require('tap') 8 | const makeDir = require('make-dir') 9 | const isWindows = require('is-windows')() 10 | const rimraf = promisify(require('rimraf')) 11 | 12 | const { runNYC, testSuccess, fixturesCLI } = require('./helpers') 13 | 14 | const subdir = path.resolve(fixturesCLI, 'subdir') 15 | const outputDir = path.resolve(subdir, './output-dir') 16 | const removedByClean = path.resolve(outputDir, 'removed-by-clean') 17 | 18 | function cleanup () { 19 | return Promise.all([ 20 | rimraf(path.resolve(subdir, 'output-dir')), 21 | rimraf(path.resolve(fixturesCLI, 'output-dir')) 22 | ]) 23 | } 24 | 25 | t.test('clean before', cleanup) 26 | t.afterEach(cleanup) 27 | 28 | t.test('works in directories without a package.json', async t => { 29 | const { status } = await runNYC({ 30 | args: ['instrument', './input-dir', './output-dir'], 31 | cwd: subdir 32 | }) 33 | 34 | t.equal(status, 0) 35 | const target = path.resolve(subdir, 'output-dir', 'index.js') 36 | t.match(await fs.readFile(target, 'utf8'), /console.log\('Hello, World!'\)/) 37 | }) 38 | 39 | t.test('can be configured to exit on error', async t => { 40 | const { status } = await runNYC({ 41 | args: ['instrument', '--exit-on-error', './input-dir', './output-dir'], 42 | cwd: subdir 43 | }) 44 | 45 | t.equal(status, 1) 46 | }) 47 | 48 | t.test('allows a single file to be instrumented', async t => { 49 | const inputPath = path.resolve(fixturesCLI, './half-covered.js') 50 | const inputMode = (await fs.stat(inputPath)).mode & 0o7777 51 | const newMode = 0o775 52 | if (!isWindows) { 53 | await fs.chmod(inputPath, newMode) 54 | } 55 | 56 | const { status } = await runNYC({ 57 | args: ['instrument', './half-covered.js', outputDir] 58 | }) 59 | 60 | t.equal(status, 0) 61 | 62 | const files = await fs.readdir(outputDir) 63 | t.strictSame(files, ['half-covered.js']) 64 | 65 | if (!isWindows) { 66 | const outputPath = path.resolve(outputDir, 'half-covered.js') 67 | const outputMode = (await fs.stat(outputPath)).mode & 0o7777 68 | t.equal(outputMode, newMode) 69 | 70 | await fs.chmod(inputPath, inputMode) 71 | } 72 | }) 73 | 74 | t.test('allows a directory of files to be instrumented', async t => { 75 | const { status } = await runNYC({ 76 | args: ['instrument', './nyc-config-js', outputDir] 77 | }) 78 | 79 | t.equal(status, 0) 80 | 81 | const files = fs.readdirSync(outputDir) 82 | t.equal(files.includes('index.js'), true) 83 | t.equal(files.includes('ignore.js'), true) 84 | t.equal(files.includes('package.json'), false) 85 | t.equal(files.includes('node_modules'), false) 86 | 87 | const includeTarget = path.resolve(outputDir, 'ignore.js') 88 | t.match(await fs.readFile(includeTarget, 'utf8'), /function cov_/) 89 | }) 90 | 91 | t.test('copies all files from to as well as those that have been instrumented', async t => { 92 | // force node_modules to exist so we can verify that it is copied. 93 | const nmDir = path.resolve(fixturesCLI, 'nyc-config-js', 'node_modules') 94 | await makeDir(nmDir) 95 | await fs.writeFile(path.join(nmDir, 'test-file'), '') 96 | 97 | const { status } = await runNYC({ 98 | args: ['instrument', '--complete-copy', './nyc-config-js', outputDir] 99 | }) 100 | 101 | t.equal(status, 0) 102 | 103 | const files = await fs.readdir(outputDir) 104 | t.equal(files.includes('index.js'), true) 105 | t.equal(files.includes('ignore.js'), true) 106 | t.equal(files.includes('package.json'), true) 107 | t.equal(files.includes('node_modules'), true) 108 | 109 | const includeTarget = path.resolve(outputDir, 'ignore.js') 110 | t.match(await fs.readFile(includeTarget, 'utf8'), /function cov_/) 111 | }) 112 | 113 | t.test('can instrument the project directory', async t => { 114 | const { status } = await runNYC({ 115 | args: ['instrument', '.', outputDir] 116 | }) 117 | 118 | t.equal(status, 0) 119 | 120 | const files = await fs.readdir(outputDir) 121 | t.equal(files.includes('args.js'), true) 122 | t.equal(files.includes('subdir'), true) 123 | }) 124 | 125 | t.test('allows a sub-directory of files to be instrumented', async t => { 126 | const { status } = await runNYC({ 127 | args: ['instrument', './subdir/input-dir', outputDir] 128 | }) 129 | 130 | t.equal(status, 0) 131 | 132 | const files = await fs.readdir(outputDir) 133 | t.equal(files.includes('index.js'), true) 134 | }) 135 | 136 | t.test('allows a subdirectory to be excluded via .nycrc file', async t => { 137 | const { status } = await runNYC({ 138 | args: [ 139 | 'instrument', 140 | '--nycrc-path', 141 | './.instrument-nycrc', 142 | './subdir/input-dir', 143 | outputDir 144 | ] 145 | }) 146 | 147 | t.equal(status, 0) 148 | 149 | const files = fs.readdirSync(outputDir) 150 | t.equal(files.includes('exclude-me'), true) 151 | t.equal(files.includes('node_modules'), true) 152 | t.equal(files.includes('index.js'), true) 153 | t.equal(files.includes('bad.js'), true) 154 | 155 | const includeTarget = path.resolve(outputDir, 'index.js') 156 | t.match(await fs.readFile(includeTarget, 'utf8'), /function cov_/) 157 | 158 | const excludeTarget = path.resolve(outputDir, 'exclude-me', 'index.js') 159 | t.notMatch(await fs.readFile(excludeTarget, 'utf8'), /function cov_/) 160 | }) 161 | 162 | t.test('allows a file to be excluded', async t => { 163 | const { status } = await runNYC({ 164 | args: [ 165 | 'instrument', 166 | '--complete-copy', 167 | '--exclude', 168 | 'exclude-me/index.js', 169 | './subdir/input-dir', 170 | outputDir 171 | ] 172 | }) 173 | 174 | t.equal(status, 0) 175 | 176 | const files = await fs.readdir(outputDir) 177 | t.equal(files.includes('exclude-me'), true) 178 | 179 | const excludeTarget = path.resolve(outputDir, 'exclude-me', 'index.js') 180 | t.notMatch(await fs.readFile(excludeTarget, 'utf8'), /function cov_/) 181 | }) 182 | 183 | t.test('allows specifying a single sub-directory to be included', async t => { 184 | const { status } = await runNYC({ 185 | args: [ 186 | 'instrument', 187 | '--include', 188 | '**/include-me/**', 189 | './subdir/input-dir', 190 | outputDir 191 | ] 192 | }) 193 | 194 | t.equal(status, 0) 195 | 196 | const files = await fs.readdir(outputDir) 197 | t.equal(files.includes('include-me'), true) 198 | const instrumented = path.resolve(outputDir, 'include-me', 'include-me.js') 199 | t.match(await fs.readFile(instrumented, 'utf8'), /function cov_/) 200 | }) 201 | 202 | t.test('allows a file to be excluded from an included directory', async t => { 203 | const { status } = await runNYC({ 204 | args: [ 205 | 'instrument', 206 | '--complete-copy', 207 | '--exclude', 208 | '**/exclude-me.js', 209 | '--include', 210 | '**/include-me/**', 211 | './subdir/input-dir', 212 | outputDir 213 | ] 214 | }) 215 | 216 | t.equal(status, 0) 217 | 218 | const files = await fs.readdir(outputDir) 219 | t.equal(files.includes('include-me'), true) 220 | 221 | const includeMeFiles = await fs.readdir(path.resolve(outputDir, 'include-me')) 222 | t.equal(includeMeFiles.includes('include-me.js'), true) 223 | t.equal(includeMeFiles.includes('exclude-me.js'), true) 224 | 225 | const includeTarget = path.resolve(outputDir, 'include-me', 'include-me.js') 226 | t.match(await fs.readFile(includeTarget, 'utf8'), /function cov_/) 227 | 228 | const excludeTarget = path.resolve(outputDir, 'exclude-me', 'index.js') 229 | t.notMatch(await fs.readFile(excludeTarget, 'utf8'), /function cov_/) 230 | }) 231 | 232 | t.test('aborts if trying to write files in place', async t => { 233 | const { status, stderr } = await runNYC({ 234 | args: ['instrument', './', './'] 235 | }) 236 | 237 | t.equal(status, 1) 238 | t.match(stderr, /cannot instrument files in place/) 239 | }) 240 | 241 | t.test('can write files in place with --in-place switch', async t => { 242 | const sourceDir = path.resolve(fixturesCLI, 'instrument-inplace') 243 | await makeDir(outputDir) 244 | await Promise.all(['package.json', 'file1.js', 'file2.js'].map( 245 | file => fs.copyFile(path.join(sourceDir, file), path.join(outputDir, file)) 246 | )) 247 | 248 | const { status } = await runNYC({ 249 | args: [ 250 | 'instrument', 251 | '--in-place', 252 | '--include', 253 | 'file1.js', 254 | '.' 255 | ], 256 | cwd: outputDir 257 | }) 258 | 259 | t.equal(status, 0) 260 | 261 | const file1 = path.resolve(outputDir, 'file1.js') 262 | t.match(await fs.readFile(file1, 'utf8'), /function cov_/) 263 | 264 | const file2 = path.resolve(outputDir, 'file2.js') 265 | t.notMatch(await fs.readFile(file2, 'utf8'), /function cov_/) 266 | 267 | await testSuccess(t, { 268 | args: ['--all', process.execPath, '-e', ''], 269 | cwd: outputDir 270 | }) 271 | }) 272 | 273 | t.test('aborts if trying to delete while writing files in place', async t => { 274 | const { status, stderr } = await runNYC({ 275 | args: [ 276 | 'instrument', 277 | '--in-place', 278 | '--delete', 279 | '--include', 280 | 'file1.js', 281 | './instrument-inplace' 282 | ] 283 | }) 284 | 285 | t.equal(status, 1) 286 | t.match(stderr, /cannot use '--delete' when instrumenting files in place/) 287 | }) 288 | 289 | t.test('aborts if trying to instrument files from outside the project root directory', async t => { 290 | const { status, stderr } = await runNYC({ 291 | args: [ 292 | 'instrument', 293 | '--delete', 294 | '../', 295 | './' 296 | ] 297 | }) 298 | 299 | t.equal(status, 1) 300 | t.match(stderr, /cannot instrument files outside project root directory/) 301 | }) 302 | 303 | t.test('cleans the output directory if `--delete` is specified', async t => { 304 | await makeDir(removedByClean) 305 | const { status } = await runNYC({ 306 | args: [ 307 | 'instrument', 308 | '--delete', 309 | 'true', 310 | './subdir/input-dir', 311 | outputDir 312 | ] 313 | }) 314 | 315 | t.equal(status, 0) 316 | 317 | const files = await fs.readdir(outputDir) 318 | t.equal(files.includes('removed-by-clean'), false) 319 | t.equal(files.includes('exclude-me'), true) 320 | }) 321 | 322 | t.test('does not clean the output directory by default', async t => { 323 | await makeDir(removedByClean) 324 | 325 | const { status } = await runNYC({ 326 | args: [ 327 | 'instrument', 328 | './subdir/input-dir', 329 | outputDir 330 | ] 331 | }) 332 | 333 | t.equal(status, 0) 334 | 335 | const files = await fs.readdir(outputDir) 336 | t.equal(files.includes('removed-by-clean'), true) 337 | }) 338 | 339 | t.test('aborts if trying to clean process.cwd()', async t => { 340 | const { status, stderr } = await runNYC({ 341 | args: ['instrument', '--delete', './src', './'] 342 | }) 343 | 344 | t.equal(status, 1) 345 | t.match(stderr, /attempt to delete/) 346 | }) 347 | 348 | t.test('aborts if trying to clean outside working directory', async t => { 349 | const { status, stderr } = await runNYC({ 350 | args: ['instrument', '--delete', './', '../'] 351 | }) 352 | 353 | t.equal(status, 1) 354 | t.match(stderr, /attempt to delete/) 355 | }) 356 | -------------------------------------------------------------------------------- /test/issue-190.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('../lib/fs-promises') 4 | const path = require('path') 5 | 6 | const t = require('tap') 7 | const isWindows = require('is-windows')() 8 | 9 | const { spawn, fixturesCLI, nycBin } = require('./helpers') 10 | 11 | const fakebin = path.resolve(fixturesCLI, 'fakebin') 12 | 13 | const spawnOptions = { 14 | cwd: path.resolve(fixturesCLI, 'run-npm-test-recursive'), 15 | env: { 16 | PATH: fakebin + ':' + process.env.PATH 17 | } 18 | } 19 | 20 | async function writeFakeNPM (shebang) { 21 | const targetPath = path.resolve(fakebin, 'npm') 22 | const source = await fs.readFile(path.resolve(fakebin, 'npm-template.js')) 23 | await fs.writeFile(targetPath, '#!' + shebang + '\n' + source) 24 | await fs.chmod(targetPath, 493) // 0o755 25 | } 26 | 27 | async function runFakeNPM (t) { 28 | const args = [nycBin, 'npm', 'test'] 29 | const { stderr, status } = await spawn(process.execPath, args, spawnOptions) 30 | 31 | t.equal(status, 0) 32 | t.equal(stderr, '') 33 | } 34 | 35 | t.beforeEach(() => Promise.all([ 36 | fs.unlink(path.resolve(fakebin, 'node')).catch(() => {}), 37 | fs.unlink(path.resolve(fakebin, 'npm')).catch(() => {}) 38 | ])) 39 | 40 | t.test('can run "npm test", absolute shebang edition', async t => { 41 | if (isWindows) { 42 | return 43 | } 44 | 45 | await writeFakeNPM(process.execPath) 46 | await runFakeNPM(t) 47 | }) 48 | 49 | t.test('can run "npm test", weird bash+dirname shebang edition', async t => { 50 | if (isWindows) { 51 | return 52 | } 53 | 54 | // This string is taken verbatim from tools/install.py in Node core v5.x 55 | await writeFakeNPM('/bin/sh\n// 2>/dev/null; exec "`dirname "$0"`/node" "$0" "$@"') 56 | await fs.symlink(process.execPath, path.resolve(fakebin, 'node')) 57 | await runFakeNPM(t) 58 | }) 59 | -------------------------------------------------------------------------------- /test/nyc-integration.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const fs = require('../lib/fs-promises') 5 | const os = require('os') 6 | const { promisify } = require('util') 7 | 8 | const t = require('tap') 9 | const glob = promisify(require('glob')) 10 | const rimraf = promisify(require('rimraf')) 11 | 12 | const { fixturesCLI, nycBin, runNYC, tempDirSetup, testSuccess, testFailure, envCheckConfig } = require('./helpers') 13 | 14 | const nycConfigJS = path.resolve(fixturesCLI, 'nyc-config-js') 15 | const nycrcDir = path.resolve(fixturesCLI, 'nycrc') 16 | const fixturesSourceMaps = path.resolve(fixturesCLI, '../source-maps') 17 | const fixturesENM = path.resolve(fixturesCLI, '../exclude-node-modules') 18 | const fixturesAllTypeModule = path.resolve(fixturesCLI, '../all-type-module') 19 | 20 | const executeNodeModulesArgs = [ 21 | '--all=true', 22 | '--cache=false', 23 | '--per-file=true', 24 | '--exclude-node-modules=false', 25 | '--include=node_modules/@istanbuljs/fake-module-1/**' 26 | ] 27 | 28 | t.jobs = os.cpus().length 29 | 30 | tempDirSetup(t, __filename) 31 | 32 | t.test('--include can be used to limit bin to instrumenting specific files', t => testSuccess(t, { 33 | args: ['--all', '--include', 'half-covered.js', process.execPath, './half-covered.js'] 34 | })) 35 | 36 | t.test('--exclude should allow default exclude rules to be overridden', t => testSuccess(t, { 37 | args: [ 38 | '--all', 39 | '--exclude=**/half-covered.js', 40 | '--exclude=**/coverage', 41 | process.execPath, 42 | './half-covered.js' 43 | ] 44 | })) 45 | 46 | t.test('report and check should show coverage check along with report', async t => { 47 | await testSuccess(t, { 48 | args: ['--silent', process.execPath, './half-covered.js'] 49 | }) 50 | await testFailure(t, { 51 | args: ['report', '--check-coverage', '--lines=100'] 52 | }) 53 | }) 54 | 55 | t.test('--ignore-class-method skips methods that match ignored name but still catches those that are not', t => testSuccess(t, { 56 | args: ['--all', '--ignore-class-method', 'skip', process.execPath, './classes.js'] 57 | })) 58 | 59 | t.test('--check-coverage fails when the expected coverage is below a threshold', t => testFailure(t, { 60 | args: ['--check-coverage', '--lines', '51', process.execPath, './half-covered.js'] 61 | })) 62 | 63 | // https://github.com/istanbuljs/nyc/issues/384 64 | t.test('check-coverage command is equivalent to the flag', async t => { 65 | await testSuccess(t, { 66 | args: [process.execPath, './half-covered.js'] 67 | }) 68 | 69 | await testFailure(t, { 70 | args: ['check-coverage', '--lines', '51'] 71 | }) 72 | }) 73 | 74 | t.test('--check-coverage succeeds when the expected coverage is above a threshold', t => testSuccess(t, { 75 | args: ['--check-coverage', '--lines', '49', process.execPath, './half-covered.js'] 76 | })) 77 | 78 | // https://github.com/bcoe/nyc/issues/209 79 | t.test('--check-coverage fails in any case when the underlying test failed', t => testFailure(t, { 80 | args: ['--check-coverage', '--lines', '49', process.execPath, './half-covered-failing.js'] 81 | })) 82 | 83 | t.test('--check-coverage fails when the expected file coverage is below a threshold', t => testFailure(t, { 84 | args: ['--check-coverage', '--lines', '51', '--per-file', process.execPath, './half-covered.js'] 85 | })) 86 | 87 | t.test('passes configuration via environment variables', t => envCheckConfig(t, { 88 | configArgs: [ 89 | '--silent', 90 | '--require=make-dir', 91 | '--include=env.js', 92 | '--exclude=batman.js', 93 | '--extension=.js', 94 | '--cache=false', 95 | '--cache-dir=/tmp', 96 | '--source-map=true' 97 | ], 98 | checkOptions: [ 99 | 'instrumenter', 100 | 'silent', 101 | 'cacheDir', 102 | 'cache', 103 | 'sourceMap', 104 | 'require', 105 | 'include', 106 | 'exclude', 107 | 'extension' 108 | ] 109 | })) 110 | 111 | t.test('allows package.json configuration to be overridden with command line args', t => testSuccess(t, { 112 | args: ['--reporter=text-lcov', process.execPath, './half-covered.js'] 113 | })) 114 | 115 | t.test('loads configuration from package.json and nyc.config.js', t => testSuccess(t, { 116 | args: [process.execPath, './index.js'], 117 | cwd: nycConfigJS 118 | })) 119 | 120 | t.test('loads configuration from different module rather than nyc.config.js', t => testFailure(t, { 121 | args: ['--all', '--nycrc-path', './nycrc-config.js', process.execPath, './index.js'], 122 | cwd: nycConfigJS 123 | })) 124 | 125 | t.test('allows nyc.config.js configuration to be overridden with command line args', t => testSuccess(t, { 126 | args: ['--all', '--exclude=foo.js', process.execPath, './index.js'], 127 | cwd: nycConfigJS 128 | })) 129 | 130 | t.test('loads configuration from package.json and .nycrc', t => testSuccess(t, { 131 | args: [process.execPath, './index.js'], 132 | cwd: nycrcDir 133 | })) 134 | 135 | t.test('loads configuration from different file rather than .nycrc', t => testFailure(t, { 136 | args: ['--nycrc-path', './.nycrc-config.json', process.execPath, './index.js'], 137 | cwd: nycrcDir 138 | })) 139 | 140 | t.test('loads configuration from .nycrc.yml', t => testSuccess(t, { 141 | args: ['--nycrc-path', './.nycrc.yml', process.execPath, './index.js'], 142 | cwd: nycrcDir 143 | })) 144 | 145 | t.test('loads configuration from .nycrc.yaml', t => testSuccess(t, { 146 | args: ['--nycrc-path', './.nycrc.yaml', process.execPath, './index.js'], 147 | cwd: nycrcDir 148 | })) 149 | 150 | t.test('allows .nycrc configuration to be overridden with command line args', t => testSuccess(t, { 151 | args: ['--exclude=foo.js', process.execPath, './index.js'], 152 | cwd: nycrcDir 153 | })) 154 | 155 | t.test('reports appropriate coverage information for es6 source files', t => testSuccess(t, { 156 | args: ['--reporter=lcov', '--reporter=text', process.execPath, './es6.js'] 157 | })) 158 | 159 | t.test('hooks provide coverage for requireJS and AMD modules', t => testSuccess(t, { 160 | args: [ 161 | /* This effectively excludes ./index.js, normalizing results before/after node.js 11.11.0 */ 162 | '--include=lib/**', 163 | '--hook-run-in-this-context', 164 | '--hook-require=false', 165 | process.execPath, 166 | './index.js' 167 | ], 168 | cwd: path.resolve(__dirname, './fixtures/hooks') 169 | })) 170 | 171 | t.test('run-in-context provide coverage for vm.runInContext', t => testSuccess(t, { 172 | args: [ 173 | '--hook-run-in-context', 174 | '--hook-require=false', 175 | process.execPath, 176 | './run-in-context.js' 177 | ], 178 | cwd: path.resolve(__dirname, './fixtures/hooks') 179 | })) 180 | 181 | t.test('does not interpret args intended for instrumented bin', async t => { 182 | const { status, stderr, stdout } = await runNYC({ 183 | tempDir: t.tempDir, 184 | args: ['--silent', process.execPath, 'args.js', '--help', '--version'], 185 | leavePathSep: true 186 | }) 187 | t.equal(status, 0) 188 | t.equal(stderr, '') 189 | t.matchSnapshot(JSON.parse(stdout).slice(2)) 190 | }) 191 | 192 | t.test('interprets first args after -- as Node.js execArgv', t => testSuccess(t, { 193 | args: ['--', '--expose-gc', path.resolve(fixturesCLI, 'gc.js')] 194 | })) 195 | 196 | t.test('--show-process-tree displays a tree of spawned processes', t => testSuccess(t, { 197 | args: ['--show-process-tree', process.execPath, 'selfspawn-fibonacci.js', '5'] 198 | })) 199 | 200 | t.test('--use-spawn-wrap=true is functional', t => testSuccess(t, { 201 | args: ['--use-spawn-wrap=true', process.execPath, 'selfspawn-fibonacci.js', '5'] 202 | })) 203 | 204 | t.test('--use-spawn-wrap=false is functional', t => testSuccess(t, { 205 | args: ['--use-spawn-wrap=false', process.execPath, 'selfspawn-fibonacci.js', '5'] 206 | })) 207 | 208 | t.test('can run "npm test" which directly invokes a test file', t => testSuccess(t, { 209 | args: ['npm', 'test'], 210 | cwd: path.resolve(fixturesCLI, 'run-npm-test') 211 | })) 212 | 213 | t.test('can run "npm test" which indirectly invokes a test file', t => testSuccess(t, { 214 | args: ['npm', 'test'], 215 | cwd: path.resolve(fixturesCLI, 'run-npm-test-recursive') 216 | })) 217 | 218 | t.test('nyc instrument single file to console', async t => { 219 | const { status, stderr, originalText } = await runNYC({ 220 | tempDir: t.tempDir, 221 | args: ['instrument', './half-covered.js'] 222 | }) 223 | 224 | t.equal(status, 0) 225 | t.equal(stderr, '') 226 | t.match(originalText.stdout, `path:${JSON.stringify(path.resolve(fixturesCLI, 'half-covered.js'))}`) 227 | }) 228 | 229 | t.test('nyc instrument disabled instrument', async t => { 230 | const { status, stderr, stdout } = await runNYC({ 231 | tempDir: t.tempDir, 232 | args: ['instrument', '--instrument=false', 'half-covered.js'] 233 | }) 234 | 235 | t.equal(status, 0) 236 | t.equal(stderr, '') 237 | t.match(stdout, 'var a = 0') 238 | t.notMatch(stdout, 'cov_') 239 | }) 240 | 241 | t.test('nyc instrument a directory of files', async t => { 242 | const { status, stderr, originalText } = await runNYC({ 243 | tempDir: t.tempDir, 244 | args: ['instrument', './'] 245 | }) 246 | 247 | t.equal(status, 0) 248 | t.equal(stderr, '') 249 | t.match(originalText.stdout, `path:${JSON.stringify(path.resolve(fixturesCLI, 'half-covered.js'))}`) 250 | t.match(originalText.stdout, `path:${JSON.stringify(path.resolve(fixturesCLI, 'half-covered-failing.js'))}`) 251 | t.notMatch(originalText.stdout, `path:${JSON.stringify(path.resolve(fixturesCLI, 'test.js'))}`) 252 | }) 253 | 254 | t.test('nyc instrument returns unmodified source if there is no transform', async t => { 255 | const { status, stderr, stdout } = await runNYC({ 256 | tempDir: t.tempDir, 257 | args: ['instrument', './no-transform/half-covered.xjs'] 258 | }) 259 | 260 | t.equal(status, 0) 261 | t.equal(stderr, '') 262 | t.match(stdout, 'var a = 0') 263 | t.notMatch(stdout, 'cov_') 264 | }) 265 | 266 | t.test('nyc instrument on file with `package` keyword when es-modules is disabled', async t => { 267 | const { status, stderr, originalText } = await runNYC({ 268 | tempDir: t.tempDir, 269 | args: ['instrument', '--no-es-modules', './not-strict.js'] 270 | }) 271 | 272 | t.equal(status, 0) 273 | t.equal(stderr, '') 274 | t.match(originalText.stdout, `path:${JSON.stringify(path.resolve(fixturesCLI, 'not-strict.js'))}`) 275 | }) 276 | 277 | t.test('nyc instrument fails on file with `package` keyword when es-modules is enabled', t => testFailure(t, { 278 | args: ['instrument', '--exit-on-error', './not-strict.js'] 279 | })) 280 | 281 | t.test('nyc displays help to stderr when it doesn\'t know what to do', async t => { 282 | const help = await runNYC({ 283 | tempDir: t.tempDir, 284 | args: ['--help'] 285 | }) 286 | 287 | t.equal(help.status, 0) 288 | t.equal(help.stderr, '') 289 | t.strictNotSame(help.stdout, '') 290 | 291 | const { status, stderr, stdout } = await runNYC({ 292 | tempDir: t.tempDir, 293 | args: [] 294 | }) 295 | 296 | t.equal(status, 1) 297 | t.equal(stdout, '') 298 | t.match(stderr, help.stdout) 299 | }) 300 | 301 | t.test('handles --clean / --no-clean properly', async t => { 302 | await testSuccess(t, { 303 | args: [ 304 | '--clean', 305 | process.execPath, 306 | './by-arg2.js', 307 | '1' 308 | ] 309 | }) 310 | 311 | await testSuccess(t, { 312 | args: [ 313 | '--no-clean', 314 | process.execPath, 315 | './by-arg2.js', 316 | '2' 317 | ] 318 | }) 319 | }) 320 | 321 | t.test('setting instrument to "false" configures noop instrumenter', t => envCheckConfig(t, { 322 | configArgs: [ 323 | '--silent', 324 | '--no-instrument', 325 | '--no-source-map' 326 | ], 327 | checkOptions: [ 328 | 'silent', 329 | 'instrument', 330 | 'sourceMap', 331 | 'instrumenter' 332 | ] 333 | })) 334 | 335 | t.test('extracts coverage headers from unexecuted files', async t => { 336 | await envCheckConfig(t, { 337 | configArgs: [ 338 | '--all', 339 | '--silent', 340 | '--no-instrument', 341 | '--no-source-map' 342 | ], 343 | checkOptions: [ 344 | 'all', 345 | 'silent', 346 | 'instrument', 347 | 'sourceMap', 348 | 'instrumenter' 349 | ] 350 | }) 351 | 352 | const files = await glob(path.join(t.tempDir, '*.json')) 353 | const coverage = [] 354 | await Promise.all(files.map(async file => { 355 | const data = JSON.parse(await fs.readFile(file, 'utf-8')) 356 | if (data['./external-instrumenter.js']) { 357 | coverage.push(data['./external-instrumenter.js']) 358 | } 359 | })) 360 | 361 | t.ok(coverage.length !== 0) 362 | t.ok(coverage.every(data => typeof data === 'object')) 363 | // we should not have executed file, so all counts sould be 0. 364 | t.ok(coverage.every(data => Object.values(data.s).every(s => s === 0))) 365 | }) 366 | 367 | t.test('allows an alternative cache folder to be specified', async t => { 368 | const cacheDir = path.resolve(fixturesCLI, 'foo-cache') 369 | 370 | await testSuccess(t, { 371 | args: [ 372 | `--cache-dir=${cacheDir}`, 373 | '--cache=true', 374 | process.execPath, 375 | './half-covered.js' 376 | ] 377 | }) 378 | 379 | // we should have created foo-cache rather 380 | // than the default ./node_modules/.cache. 381 | t.equal(1, (await fs.readdir(cacheDir)).length) 382 | 383 | await rimraf(cacheDir) 384 | }) 385 | 386 | // see: https://github.com/istanbuljs/nyc/issues/563 387 | t.test('does not create .cache folder if cache is "false"', async t => { 388 | const cacheDir = path.resolve(fixturesCLI, './disabled-cache') 389 | 390 | await testSuccess(t, { 391 | args: [ 392 | `--cache-dir=${cacheDir}`, 393 | '--cache=false', 394 | process.execPath, 395 | './half-covered.js' 396 | ] 397 | }) 398 | 399 | t.notOk(fs.existsSync(cacheDir)) 400 | }) 401 | 402 | t.test('allows alternative high and low watermarks to be configured', t => testSuccess(t, { 403 | args: [ 404 | '--watermarks.lines=90', 405 | '--watermarks.lines=100', 406 | '--watermarks.statements=30', 407 | '--watermarks.statements=40', 408 | '--cache=true', 409 | process.execPath, 410 | './half-covered.js' 411 | ], 412 | env: { 413 | FORCE_COLOR: true 414 | } 415 | })) 416 | 417 | t.test('--all includes files with both .map files and inline source-maps', t => testSuccess(t, { 418 | args: [ 419 | '--all', 420 | '--cache=false', 421 | '--exclude-after-remap=false', 422 | '--exclude=original', 423 | process.execPath, 424 | './instrumented/s1.min.js' 425 | ], 426 | cwd: fixturesSourceMaps 427 | })) 428 | 429 | t.test('--all uses source-maps to exclude original sources from reports', t => testSuccess(t, { 430 | args: [ 431 | '--all', 432 | '--cache=false', 433 | '--exclude=original/s1.js', 434 | process.execPath, 435 | './instrumented/s1.min.js' 436 | ], 437 | cwd: fixturesSourceMaps 438 | })) 439 | 440 | t.test('--all does not fail on ERR_REQUIRE_ESM', t => testSuccess(t, { 441 | args: [ 442 | '--all', 443 | process.execPath, 444 | 'script.cjs' 445 | ], 446 | cwd: fixturesAllTypeModule 447 | })) 448 | 449 | t.test('caches source-maps from .map files', async t => { 450 | await testSuccess(t, { 451 | args: [ 452 | process.execPath, 453 | './instrumented/s1.min.js' 454 | ], 455 | cwd: fixturesSourceMaps 456 | }) 457 | 458 | const files = await fs.readdir(path.resolve(fixturesSourceMaps, 'node_modules/.cache/nyc')) 459 | t.ok(files.some(f => f.startsWith('s1.min-') && f.endsWith('.map'))) 460 | }) 461 | 462 | t.test('caches inline source-maps', async t => { 463 | await testSuccess(t, { 464 | args: [ 465 | process.execPath, 466 | './instrumented/s2.min.js' 467 | ], 468 | cwd: fixturesSourceMaps 469 | }) 470 | 471 | const files = await fs.readdir(path.resolve(fixturesSourceMaps, 'node_modules/.cache/nyc')) 472 | t.ok(files.some(f => f.startsWith('s2.min-') && f.endsWith('.map'))) 473 | }) 474 | 475 | t.test('appropriately instruments file with corresponding .map file', t => testSuccess(t, { 476 | args: [ 477 | '--cache=false', 478 | '--exclude-after-remap=false', 479 | '--exclude=original', 480 | process.execPath, 481 | './instrumented/s1.min.js' 482 | ], 483 | cwd: fixturesSourceMaps 484 | })) 485 | 486 | t.test('appropriately instruments file with inline source-map', t => testSuccess(t, { 487 | args: [ 488 | '--cache=false', 489 | '--exclude-after-remap=false', 490 | '--exclude=original', 491 | process.execPath, 492 | './instrumented/s2.min.js' 493 | ], 494 | cwd: fixturesSourceMaps 495 | })) 496 | 497 | t.test('skip-empty does not display 0-line files', t => testSuccess(t, { 498 | args: [ 499 | '--cache=false', 500 | '--skip-empty=true', 501 | process.execPath, 502 | './empty.js' 503 | ] 504 | })) 505 | 506 | t.test('skip-full does not display files with 100% statement, branch, and function coverage', t => testSuccess(t, { 507 | args: [ 508 | '--skip-full', 509 | process.execPath, 510 | './skip-full.js' 511 | ] 512 | })) 513 | 514 | t.test('allows reserved word when es-modules is disabled', t => testSuccess(t, { 515 | args: [ 516 | '--cache=false', 517 | '--es-modules=false', 518 | process.execPath, 519 | './not-strict.js' 520 | ] 521 | })) 522 | 523 | t.test('forbids reserved word when es-modules is not disabled', t => testFailure(t, { 524 | args: [ 525 | '--cache=false', 526 | '--exit-on-error=true', 527 | process.execPath, 528 | './not-strict.js' 529 | ] 530 | })) 531 | 532 | t.test('execute with exclude-node-modules=false', async t => { 533 | await testFailure(t, { 534 | args: [ 535 | ...executeNodeModulesArgs, 536 | '--check-coverage=true', 537 | process.execPath, 538 | './bin/do-nothing.js' 539 | ], 540 | cwd: fixturesENM 541 | }) 542 | 543 | await testFailure(t, { 544 | args: [ 545 | ...executeNodeModulesArgs, 546 | '--check-coverage=true', 547 | 'report' 548 | ], 549 | cwd: fixturesENM 550 | }) 551 | 552 | await testSuccess(t, { 553 | args: [ 554 | ...executeNodeModulesArgs, 555 | '--check-coverage=false', 556 | 'report' 557 | ], 558 | cwd: fixturesENM 559 | }) 560 | 561 | await testFailure(t, { 562 | args: [ 563 | ...executeNodeModulesArgs, 564 | 'check-coverage' 565 | ], 566 | cwd: fixturesENM 567 | }) 568 | }) 569 | 570 | t.test('instrument with exclude-node-modules=false', async t => { 571 | const { status, stderr, stdout } = await runNYC({ 572 | tempDir: t.tempDir, 573 | args: [ 574 | ...executeNodeModulesArgs, 575 | 'instrument', 576 | 'node_modules' 577 | ], 578 | cwd: fixturesENM 579 | }) 580 | 581 | t.equal(status, 0) 582 | t.equal(stderr, '') 583 | t.match(stdout, 'fake-module-1') 584 | }) 585 | 586 | t.test('recursive run does not throw', t => testSuccess(t, { 587 | args: [ 588 | process.execPath, 589 | nycBin, 590 | process.execPath, 591 | nycBin, 592 | process.execPath, 593 | nycBin, 594 | 'echo', 595 | 'hello' 596 | ], 597 | cwd: path.resolve(__dirname, 'fixtures/recursive-run') 598 | })) 599 | 600 | t.test('combines multiple coverage reports', async t => { 601 | await testSuccess(t, { 602 | args: ['merge', './merge-input'] 603 | }) 604 | 605 | const mergedCoverage = require('./fixtures/cli/coverage') 606 | // the combined reports should have 100% function 607 | // branch and statement coverage. 608 | t.same( 609 | mergedCoverage['/private/tmp/contrived/library.js'].s, 610 | { 0: 2, 1: 1, 2: 1, 3: 2, 4: 1, 5: 1 } 611 | ) 612 | t.same( 613 | mergedCoverage['/private/tmp/contrived/library.js'].f, 614 | { 0: 1, 1: 1, 2: 2 } 615 | ) 616 | t.same( 617 | mergedCoverage['/private/tmp/contrived/library.js'].b, 618 | { 0: [1, 1] } 619 | ) 620 | await rimraf(path.resolve(fixturesCLI, 'coverage.json')) 621 | }) 622 | 623 | t.test('reports error if input directory is missing', t => testFailure(t, { 624 | args: ['merge', './DIRECTORY_THAT_IS_MISSING'] 625 | })) 626 | 627 | t.test('reports error if input is not a directory', t => testFailure(t, { 628 | args: ['merge', './package.json'] 629 | })) 630 | 631 | t.test('--all instruments unknown extensions as js', t => testSuccess(t, { 632 | cwd: path.resolve(fixturesCLI, '../conf-multiple-extensions'), 633 | args: ['--all', process.execPath, './run.js'] 634 | })) 635 | 636 | t.test('instrument with invalid --require fails when using node-preload', async t => { 637 | const { status, stderr, stdout } = await runNYC({ 638 | tempDir: t.tempDir, 639 | args: [ 640 | '--require=@istanbuljs/this-module-does-not-exist', 641 | 'instrument', 642 | './skip-full.js' 643 | ] 644 | }) 645 | 646 | t.equal(status, 1) 647 | t.match(stderr, /Cannot find module '@istanbuljs\/this-module-does-not-exist'/) 648 | t.equal(stdout, '') 649 | }) 650 | 651 | t.test('invalid --require fails when using node-preload', async t => { 652 | const { status, stderr, stdout } = await runNYC({ 653 | tempDir: t.tempDir, 654 | args: [ 655 | '--require=@istanbuljs/this-module-does-not-exist', 656 | './skip-full.js' 657 | ] 658 | }) 659 | 660 | t.equal(status, 1) 661 | t.match(stderr, /Cannot find module '@istanbuljs\/this-module-does-not-exist'/) 662 | t.equal(stdout, '') 663 | }) 664 | 665 | t.test('invalid --require fails when using spawn-wrap', async t => { 666 | const { status, stderr, stdout } = await runNYC({ 667 | tempDir: t.tempDir, 668 | args: [ 669 | '--use-spawn-wrap=true', 670 | '--require=@istanbuljs/this-module-does-not-exist', 671 | 'instrument', 672 | './skip-full.js' 673 | ] 674 | }) 675 | 676 | t.equal(status, 1) 677 | t.match(stderr, /Cannot find module '@istanbuljs\/this-module-does-not-exist'/) 678 | t.equal(stdout, '') 679 | }) 680 | -------------------------------------------------------------------------------- /test/parser-plugins.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | 5 | const t = require('tap') 6 | 7 | const { runNYC } = require('./helpers') 8 | 9 | const cwd = path.resolve(__dirname, './fixtures/parser-plugins') 10 | 11 | t.test('parser plugin set', async t => { 12 | const { status, stdout, stderr } = await runNYC({ 13 | args: ['instrument', 'v8.js'], 14 | cwd 15 | }) 16 | t.equal(status, 0) 17 | t.equal(stderr, '') 18 | t.match(stdout, /function cov_/) 19 | }) 20 | 21 | t.test('parser plugin unset', async t => { 22 | const { status, stdout, stderr } = await runNYC({ 23 | args: ['instrument', '--nycrc-path=no-plugins.json', 'v8.js'], 24 | cwd 25 | }) 26 | t.equal(status, 0) 27 | t.equal(stderr, '') 28 | t.notMatch(stdout, /function cov_/) 29 | }) 30 | -------------------------------------------------------------------------------- /test/process-args.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const yargs = require('yargs/yargs') 5 | 6 | const processArgs = require('../self-coverage/lib/process-args') 7 | 8 | const nycBin = require.resolve('../self-coverage/bin/nyc.js') 9 | 10 | test('hideInstrumenterArgs removes dashed options that proceed bin', async t => { 11 | process.argv = [ 12 | process.execPath, 13 | nycBin, 14 | '--reporter', 15 | 'lcov', 16 | 'node', 17 | 'test/nyc-tap.js' 18 | ] 19 | 20 | const { argv } = yargs(process.argv.slice(2)) 21 | const munged = processArgs.hideInstrumenterArgs(argv) 22 | 23 | t.strictSame(munged, ['node', 'test/nyc-tap.js']) 24 | }) 25 | 26 | test('hideInstrumenterArgs parses extra args directly after -- as Node execArgv', async t => { 27 | process.argv = [ 28 | process.execPath, 29 | nycBin, 30 | '--', 31 | '--expose-gc', 32 | 'index.js' 33 | ] 34 | 35 | const { argv } = yargs(process.argv.slice(2)) 36 | const munged = processArgs.hideInstrumenterArgs(argv) 37 | 38 | t.strictSame(munged, [process.execPath, '--expose-gc', 'index.js']) 39 | }) 40 | 41 | test('hideInstrumenteeArgs ignores arguments after the instrumented bin', async t => { 42 | process.argv = [ 43 | process.execPath, 44 | nycBin, 45 | '--reporter', 46 | 'lcov', 47 | 'node', 48 | 'test/nyc-tap.js', 49 | '--arg', 50 | '--' 51 | ] 52 | 53 | const munged = processArgs.hideInstrumenteeArgs() 54 | t.strictSame(munged, ['--reporter', 'lcov', 'node']) 55 | }) 56 | 57 | test('hideInstrumenteeArgs does not ignore arguments if command is recognized', async t => { 58 | process.argv = [ 59 | process.execPath, 60 | nycBin, 61 | 'report', 62 | '--reporter', 63 | 'lcov' 64 | ] 65 | 66 | const munged = processArgs.hideInstrumenteeArgs() 67 | t.strictSame(munged, ['report', '--reporter', 'lcov']) 68 | }) 69 | 70 | test('hideInstrumenteeArgs does not ignore arguments if no command is provided', async t => { 71 | process.argv = [ 72 | process.execPath, 73 | nycBin, 74 | '--version' 75 | ] 76 | 77 | const munged = processArgs.hideInstrumenteeArgs() 78 | t.strictSame(munged, ['--version']) 79 | }) 80 | -------------------------------------------------------------------------------- /test/processinfo.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { resolve } = require('path') 4 | const { promisify } = require('util') 5 | const { spawn } = require('child_process') 6 | const t = require('tap') 7 | const rimraf = promisify(require('rimraf')) 8 | const fs = require('../lib/fs-promises') 9 | 10 | const node = process.execPath 11 | const bin = resolve(__dirname, '../self-coverage/bin/nyc') 12 | const fixturesCLI = resolve(__dirname, './fixtures/cli') 13 | const tmp = 'processinfo-test' 14 | const resolvedJS = resolve(fixturesCLI, 'selfspawn-fibonacci.js') 15 | 16 | rimraf.sync(resolve(fixturesCLI, tmp)) 17 | t.teardown(() => rimraf(resolve(fixturesCLI, tmp))) 18 | 19 | t.test('build some processinfo', t => { 20 | var args = [ 21 | bin, '-t', tmp, 22 | node, 'selfspawn-fibonacci.js', '5' 23 | ] 24 | var proc = spawn(process.execPath, args, { 25 | cwd: fixturesCLI, 26 | env: { 27 | PATH: process.env.PATH, 28 | NYC_PROCESSINFO_EXTERNAL_ID: 'blorp' 29 | } 30 | }) 31 | // don't actually care about the output for this test, just the data 32 | proc.stderr.resume() 33 | proc.stdout.resume() 34 | proc.on('close', (code, signal) => { 35 | t.equal(code, 0) 36 | t.equal(signal, null) 37 | t.end() 38 | }) 39 | }) 40 | 41 | t.test('validate the created processinfo data', async t => { 42 | const covs = (await fs.readdir(resolve(fixturesCLI, tmp))) 43 | .filter(f => f !== 'processinfo') 44 | 45 | await Promise.all(covs.map(async f => { 46 | const covdata = JSON.parse(await fs.readFile(resolve(fixturesCLI, tmp, f), 'utf8')) 47 | t.same(Object.keys(covdata), [resolvedJS]) 48 | 49 | // should have matching processinfo for each cov json 50 | const procInfoFile = resolve(fixturesCLI, tmp, 'processinfo', f) 51 | const procInfoData = JSON.parse(await fs.readFile(procInfoFile, 'utf8')) 52 | t.match(procInfoData, { 53 | pid: Number, 54 | ppid: Number, 55 | uuid: f.replace(/\.json$/, ''), 56 | argv: [ 57 | node, 58 | resolvedJS, 59 | /[1-5]/ 60 | ], 61 | execArgv: [], 62 | cwd: fixturesCLI, 63 | time: Number, 64 | coverageFilename: resolve(fixturesCLI, tmp, f), 65 | files: [resolvedJS] 66 | }) 67 | })) 68 | }) 69 | 70 | t.test('check out the index', async t => { 71 | const indexFile = resolve(fixturesCLI, tmp, 'processinfo', 'index.json') 72 | const indexJson = await fs.readFile(indexFile, 'utf-8') 73 | const index = JSON.parse(indexJson) 74 | const u = /^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/ 75 | t.match(index, { 76 | processes: {}, 77 | files: { 78 | [resolvedJS]: [u, u, u, u, u, u, u, u, u] 79 | }, 80 | externalIds: { 81 | blorp: { 82 | children: [u, u, u, u, u, u, u, u] 83 | } 84 | } 85 | }) 86 | }) 87 | -------------------------------------------------------------------------------- /test/report.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const path = require('path') 5 | const { promisify } = require('util') 6 | 7 | const t = require('tap') 8 | const isWindows = require('is-windows')() 9 | const rimraf = promisify(require('rimraf')) 10 | 11 | const NYC = require('../self-coverage') 12 | 13 | const { parseArgv, runNYC, resetState } = require('./helpers') 14 | 15 | const fixtures = path.resolve(__dirname, 'fixtures') 16 | 17 | t.beforeEach(resetState) 18 | 19 | async function testSignal (t, signal) { 20 | if (isWindows) { 21 | t.end() 22 | 23 | return 24 | } 25 | 26 | const nyc = new NYC(await parseArgv(fixtures)) 27 | await runNYC({ 28 | args: [`./${signal}.js`], 29 | cwd: fixtures 30 | }) 31 | 32 | const checkFile = path.join(fixtures, `${signal}.js`) 33 | const reports = (await nyc.coverageData()).filter(report => report[checkFile]) 34 | 35 | t.equal(reports.length, 1) 36 | } 37 | 38 | t.test('writes coverage report when process is killed with SIGTERM', t => testSignal(t, 'sigterm')) 39 | 40 | t.test('writes coverage report when process is killed with SIGINT', t => testSignal(t, 'sigint')) 41 | 42 | t.test('allows coverage report to be output in an alternative directory', async t => { 43 | const nyc = new NYC(await parseArgv(undefined, [ 44 | '--report-dir=./alternative-report', 45 | '--reporter=lcov' 46 | ])) 47 | await nyc.reset() 48 | 49 | await nyc.report() 50 | t.equal(fs.existsSync('./alternative-report/lcov.info'), true) 51 | await rimraf('./alternative-report') 52 | }) 53 | -------------------------------------------------------------------------------- /test/should-instrument.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const NYC = require('../self-coverage') 5 | 6 | const { parseArgv } = require('./helpers') 7 | 8 | const fixtures = path.resolve(__dirname, './fixtures') 9 | 10 | const t = require('tap') 11 | 12 | const rootDir = path.resolve('/') 13 | t.test('should exclude appropriately with defaults', async t => { 14 | const nyc = new NYC(await parseArgv(rootDir, [ 15 | '--exclude=test/**', 16 | '--exclude=test{,-*}.js', 17 | '--exclude=**/*.test.js', 18 | '--exclude=**/__tests__/**' 19 | ])) 20 | 21 | // nyc always excludes "node_modules/**" 22 | t.ok(nyc.exclude.shouldInstrument(path.join(rootDir, 'foo.js'), 'foo.js')) 23 | t.notOk(nyc.exclude.shouldInstrument(path.join(rootDir, 'node_modules/bar.js'), 'node_modules/bar.js')) 24 | t.notOk(nyc.exclude.shouldInstrument(path.join(rootDir, 'foo/node_modules/bar.js'), 'foo/node_modules/bar.js')) 25 | t.notOk(nyc.exclude.shouldInstrument(path.join(rootDir, 'test.js'), 'test.js')) 26 | t.ok(nyc.exclude.shouldInstrument(path.join(rootDir, 'testfoo.js'), 'testfoo.js')) 27 | t.notOk(nyc.exclude.shouldInstrument(path.join(rootDir, 'test-foo.js'), 'test-foo.js')) 28 | t.ok(nyc.exclude.shouldInstrument(path.join(rootDir, 'lib/test.js'), 'lib/test.js')) 29 | t.notOk(nyc.exclude.shouldInstrument(path.join(rootDir, 'foo/bar/test.js'), './test.js')) 30 | t.notOk(nyc.exclude.shouldInstrument(path.join(rootDir, 'foo/bar/test.js'), '.\\test.js')) 31 | t.notOk(nyc.exclude.shouldInstrument(path.join(rootDir, 'foo/bar/foo.test.js'), './foo.test.js')) 32 | t.notOk(nyc.exclude.shouldInstrument(path.join(rootDir, 'foo/bar/__tests__/foo.js'), './__tests__/foo.js')) 33 | }) 34 | 35 | t.test('should exclude appropriately with config.exclude', async t => { 36 | const nyc = new NYC(await parseArgv(fixtures)) 37 | 38 | // fixtures/package.json configures excludes: "blarg", "blerg" 39 | t.notOk(nyc.exclude.shouldInstrument(path.join(rootDir, 'blarg.js'), 'blarg.js')) 40 | t.notOk(nyc.exclude.shouldInstrument(path.join(rootDir, 'blarg/foo.js'), 'blarg/foo.js')) 41 | t.notOk(nyc.exclude.shouldInstrument(path.join(rootDir, 'blerg.js'), 'blerg.js')) 42 | t.notOk(nyc.exclude.shouldInstrument(path.join(rootDir, 'blerg.js'), './blerg.js')) 43 | t.notOk(nyc.exclude.shouldInstrument(path.join(rootDir, 'blerg.js'), '.\\blerg.js')) 44 | }) 45 | 46 | t.test('should exclude outside of the current working directory', async t => { 47 | const nyc = new NYC(await parseArgv(path.join(rootDir, 'foo'))) 48 | t.notOk(nyc.exclude.shouldInstrument(path.join(rootDir, 'bar.js'), '../bar.js')) 49 | }) 50 | 51 | t.test('should not exclude if the current working directory is inside node_modules', async t => { 52 | const cwd = path.join(rootDir, 'node_modules', 'foo') 53 | const nyc = new NYC(await parseArgv(cwd)) 54 | t.ok(nyc.exclude.shouldInstrument(path.join(cwd, 'bar.js'), './bar.js')) 55 | t.ok(nyc.exclude.shouldInstrument(path.join(cwd, 'bar.js'), '.\\bar.js')) 56 | }) 57 | 58 | t.test('allows files to be explicitly included, rather than excluded', async t => { 59 | const nyc = new NYC(await parseArgv(rootDir, ['--include=foo.js'])) 60 | 61 | t.ok(nyc.exclude.shouldInstrument(path.join(rootDir, 'foo.js'), 'foo.js')) 62 | t.notOk(nyc.exclude.shouldInstrument(path.join(rootDir, 'index.js'), 'index.js')) 63 | }) 64 | 65 | t.test('exclude overrides include', async t => { 66 | const nyc = new NYC(await parseArgv(rootDir, [ 67 | '--include=foo.js', 68 | '--include=test.js', 69 | '--exclude=**/node_modules/**', 70 | '--exclude=test/**', 71 | '--exclude=test{,-*}.js' 72 | ])) 73 | 74 | t.ok(nyc.exclude.shouldInstrument(path.join(rootDir, 'foo.js'), 'foo.js')) 75 | t.notOk(nyc.exclude.shouldInstrument(path.join(rootDir, 'test.js'), 'test.js')) 76 | }) 77 | -------------------------------------------------------------------------------- /test/source-map-support.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const t = require('tap') 4 | 5 | const NYC = require('../self-coverage') 6 | 7 | const { parseArgv, resetState } = require('./helpers') 8 | 9 | // we test exit handlers in nyc-integration.js. 10 | NYC.prototype._wrapExit = () => {} 11 | 12 | require('source-map-support').install({ hookRequire: true }) 13 | 14 | t.beforeEach(resetState) 15 | 16 | t.test('handles stack traces', async t => { 17 | const nyc = new NYC(await parseArgv(undefined, [ 18 | '--produce-source-map=true' 19 | ])) 20 | await nyc.reset() 21 | nyc.wrap() 22 | 23 | const check = require('./fixtures/stack-trace') 24 | t.match(check(), /stack-trace.js:4:/) 25 | }) 26 | 27 | t.test('does not handle stack traces when disabled', async t => { 28 | const nyc = new NYC(await parseArgv(undefined, [ 29 | '--produce-source-map=false' 30 | ])) 31 | await nyc.reset() 32 | nyc.wrap() 33 | 34 | const check = require('./fixtures/stack-trace') 35 | t.notMatch(check(), /stack-trace.js:4:/) 36 | }) 37 | -------------------------------------------------------------------------------- /test/temp-dir.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('../lib/fs-promises') 4 | const path = require('path') 5 | const { promisify } = require('util') 6 | 7 | const t = require('tap') 8 | const rimraf = promisify(require('rimraf')) 9 | 10 | const { runNYC, fixturesCLI } = require('./helpers') 11 | 12 | function cleanup () { 13 | return Promise.all([ 14 | rimraf(path.resolve(fixturesCLI, '.nyc_output')), 15 | rimraf(path.resolve(fixturesCLI, '.temp_directory')), 16 | rimraf(path.resolve(fixturesCLI, '.temp_dir')) 17 | ]) 18 | } 19 | 20 | t.beforeEach(cleanup) 21 | t.teardown(cleanup) 22 | 23 | t.test('creates the default \'tempDir\' when none is specified', async t => { 24 | const { status } = await runNYC({ 25 | args: [process.execPath, './half-covered.js'] 26 | }) 27 | 28 | t.equal(status, 0) 29 | 30 | const cliFiles = await fs.readdir(path.resolve(fixturesCLI)) 31 | t.equal(cliFiles.includes('.nyc_output'), true) 32 | t.equal(cliFiles.includes('.temp_dir'), false) 33 | t.equal(cliFiles.includes('.temp_directory'), false) 34 | 35 | const tempFiles = await fs.readdir(path.resolve(fixturesCLI, '.nyc_output')) 36 | t.equal(tempFiles.length, 2) // the coverage file, and processinfo 37 | }) 38 | 39 | t.test('prefers \'tempDirectory\' to \'tempDir\'', async t => { 40 | const { status } = await runNYC({ 41 | args: [ 42 | '--tempDirectory', 43 | '.temp_directory', 44 | '--tempDir', 45 | '.temp_dir', 46 | process.execPath, 47 | './half-covered.js' 48 | ] 49 | }) 50 | 51 | t.equal(status, 0) 52 | 53 | const cliFiles = await fs.readdir(path.resolve(fixturesCLI)) 54 | t.equal(cliFiles.includes('.nyc_output'), false) 55 | t.equal(cliFiles.includes('.temp_dir'), false) 56 | t.equal(cliFiles.includes('.temp_directory'), true) 57 | 58 | const tempFiles = await fs.readdir(path.resolve(fixturesCLI, '.temp_directory')) 59 | t.equal(tempFiles.length, 2) 60 | }) 61 | 62 | t.test('uses the \'tempDir\' option if \'tempDirectory\' is not set', async t => { 63 | const { status } = await runNYC({ 64 | args: [ 65 | '--tempDir', 66 | '.temp_dir', 67 | process.execPath, 68 | './half-covered.js' 69 | ] 70 | }) 71 | 72 | t.equal(status, 0) 73 | 74 | const cliFiles = await fs.readdir(path.resolve(fixturesCLI)) 75 | t.equal(cliFiles.includes('.nyc_output'), false) 76 | t.equal(cliFiles.includes('.temp_dir'), true) 77 | t.equal(cliFiles.includes('.temp_directory'), false) 78 | 79 | const tempFiles = await fs.readdir(path.resolve(fixturesCLI, '.temp_dir')) 80 | t.equal(tempFiles.length, 2) 81 | }) 82 | -------------------------------------------------------------------------------- /test/tsc.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const t = require('tap') 5 | 6 | const { testSuccess } = require('./helpers') 7 | const fixturesTSC = path.resolve(__dirname, 'fixtures/tsc') 8 | 9 | t.test('reads source-map', t => testSuccess(t, { 10 | cwd: fixturesTSC, 11 | args: [ 12 | '--produce-source-map=true', 13 | '--cache=false', 14 | process.execPath, 15 | 'mapping.js' 16 | ] 17 | })) 18 | 19 | t.test('ignore source-map', t => testSuccess(t, { 20 | cwd: fixturesTSC, 21 | args: [ 22 | '--produce-source-map=true', 23 | '--no-source-map', 24 | '--cache=false', 25 | process.execPath, 26 | 'mapping.js' 27 | ] 28 | })) 29 | -------------------------------------------------------------------------------- /test/wrap.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const path = require('path') 5 | 6 | const t = require('tap') 7 | 8 | const NYC = require('../self-coverage') 9 | 10 | const { parseArgv, resetState } = require('./helpers') 11 | 12 | // we test exit handlers in nyc-integration.js. 13 | NYC.prototype._wrapExit = () => {} 14 | 15 | const fixtures = path.resolve(__dirname, 'fixtures') 16 | const configMultExt = path.resolve(fixtures, 'conf-multiple-extensions') 17 | 18 | t.beforeEach(resetState) 19 | 20 | t.test('wraps modules with coverage counters when they are required', async t => { 21 | const nyc = new NYC(await parseArgv()) 22 | await nyc.reset() 23 | nyc.wrap() 24 | 25 | const check = require('./fixtures/check-instrumented') 26 | t.equal(check(), true) 27 | }) 28 | 29 | t.test('wraps modules with coverage counters when the custom require hook compiles them', async t => { 30 | let required = false 31 | const hook = function (module, filename) { 32 | if (filename.indexOf('check-instrumented.js') !== -1) { 33 | required = true 34 | } 35 | module._compile(fs.readFileSync(filename, 'utf8'), filename) 36 | } 37 | 38 | const nyc = new NYC(await parseArgv()) 39 | await nyc.reset() 40 | nyc.wrap() 41 | 42 | // install the custom require hook 43 | require.extensions['.js'] = hook // eslint-disable-line 44 | 45 | const check = require('./fixtures/check-instrumented') 46 | t.equal(check(), true) 47 | t.equal(required, true) 48 | }) 49 | 50 | t.test('assigns a function to custom extensions', async t => { 51 | const nyc = new NYC(await parseArgv(configMultExt)) 52 | await nyc.reset() 53 | nyc.wrap() 54 | 55 | t.type(require.extensions['.es6'], 'function') // eslint-disable-line 56 | t.type(require.extensions['.foo.bar'], 'function') // eslint-disable-line 57 | 58 | // default should still exist 59 | t.type(require.extensions['.js'], 'function') // eslint-disable-line 60 | }) 61 | 62 | t.test('calls the `_handleJs` function for custom file extensions', async t => { 63 | const required = {} 64 | const nyc = new NYC(await parseArgv(configMultExt)) 65 | 66 | nyc._handleJs = (code, options) => { 67 | if (options.filename.includes('check-instrumented.es6')) { 68 | required.es6 = true 69 | } 70 | 71 | if (options.filename.includes('check-instrumented.foo.bar')) { 72 | required.custom = true 73 | } 74 | 75 | return code 76 | } 77 | 78 | await nyc.reset() 79 | nyc.wrap() 80 | 81 | require('./fixtures/conf-multiple-extensions/check-instrumented.es6') 82 | require('./fixtures/conf-multiple-extensions/check-instrumented.foo.bar') 83 | t.equal(required.custom, true) 84 | t.equal(required.es6, true) 85 | }) 86 | 87 | t.test('does not output coverage for files that have not been included, by default', async t => { 88 | const nyc = new NYC(await parseArgv(process.cwd())) 89 | nyc.wrap() 90 | await nyc.reset() 91 | 92 | const reports = (await nyc.coverageData()).filter(report => report['./test/fixtures/not-loaded.js']) 93 | t.equal(reports.length, 0) 94 | }) 95 | 96 | t.test('tracks coverage appropriately once the file is required', async t => { 97 | const nyc = new NYC(await parseArgv(fixtures)) 98 | await nyc.reset() 99 | nyc.wrap() 100 | 101 | require('./fixtures/not-loaded') 102 | 103 | nyc.writeCoverageFile() 104 | 105 | const notLoadedPath = path.join(fixtures, './not-loaded.js') 106 | const reports = (await nyc.coverageData()).filter(report => report[notLoadedPath]) 107 | const report = reports[0][notLoadedPath] 108 | 109 | t.equal(reports.length, 1) 110 | t.equal(report.s['0'], 1) 111 | t.equal(report.s['1'], 1) 112 | }) 113 | --------------------------------------------------------------------------------