From 600c867ab1c6676f50d36698e86d2f963cb4b33c Mon Sep 17 00:00:00 2001 From: Corey Farrell Date: Wed, 24 Apr 2019 06:24:25 -0400 Subject: [PATCH] chore: Convert some tap tests to run parallel and use snapshots. (#1075) --- .babelrc | 3 - package-lock.json | 25 + package.json | 12 +- .../test-nyc-integration.js-TAP.test.js | 566 +++++ test/fixtures/cli/half-covered-failing.js | 2 +- test/helpers/index.js | 12 + test/helpers/paths.js | 8 + test/helpers/run-nyc.js | 40 + test/helpers/spawn.js | 23 + test/helpers/temp-dir-setup.js | 33 + test/helpers/test-failure.js | 14 + test/helpers/test-success.js | 14 + test/nyc-integration-old.js | 1402 +++++++++++ test/nyc-integration.js | 2126 ++--------------- 14 files changed, 2314 insertions(+), 1966 deletions(-) delete mode 100644 .babelrc create mode 100644 tap-snapshots/test-nyc-integration.js-TAP.test.js create mode 100644 test/helpers/index.js create mode 100644 test/helpers/paths.js create mode 100644 test/helpers/run-nyc.js create mode 100644 test/helpers/spawn.js create mode 100644 test/helpers/temp-dir-setup.js create mode 100644 test/helpers/test-failure.js create mode 100644 test/helpers/test-success.js create mode 100644 test/nyc-integration-old.js diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 002b4aa0d..000000000 --- a/.babelrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "presets": ["env"] -} diff --git a/package-lock.json b/package-lock.json index 0fbf2cc3d..1f32fa7ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -864,6 +864,31 @@ "safe-buffer": "^5.0.1" } }, + "cross-env": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.2.0.tgz", + "integrity": "sha512-jtdNFfFW1hB7sMhr/H6rW1Z45LFqyI431m3qU6bFXcQ3Eh7LtBuG3h74o7ohHZ3crrRkkqHlo4jYHFPcjroANg==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.5", + "is-windows": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + } + } + }, "cross-spawn": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", diff --git a/package.json b/package.json index 372e2c28d..e0aa83eb7 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,14 @@ "description": "the Istanbul command line interface", "main": "index.js", "scripts": { - "pretest": "npm run clean && npm run instrument", - "test": "tap -t360 --no-cov -b ./test/*.js && npm run report", - "posttest": "standard", + "lint": "standard", + "pretest": "npm run lint && npm run clean && npm run instrument", + "test": "tap -t360 --no-cov -b test/*.js", + "snap": "cross-env TAP_SNAPSHOT=1 tap -t360 --no-cov -b test/*.js", + "posttest": "npm run report", "clean": "rimraf ./.nyc_output ./node_modules/.cache ./.self_coverage ./test/fixtures/.nyc_output ./test/fixtures/node_modules/.cache ./self-coverage", "instrument": "node ./build-self-coverage.js", - "report": "node ./self-coverage/bin/nyc report --temp-dir ./.self_coverage/ -r text -r lcov", + "report": "node ./bin/nyc report --temp-dir ./.self_coverage/ -r text -r lcov", "release": "standard-version" }, "bin": { @@ -96,9 +98,11 @@ "any-path": "^1.3.0", "chai": "^4.2.0", "coveralls": "^3.0.3", + "cross-env": "^5.2.0", "is-windows": "^1.0.2", "lodash": "^4.17.11", "newline-regex": "^0.2.1", + "pify": "^4.0.1", "requirejs": "^2.3.6", "sanitize-filename": "^1.6.1", "source-map-support": "^0.5.12", diff --git a/tap-snapshots/test-nyc-integration.js-TAP.test.js b/tap-snapshots/test-nyc-integration.js-TAP.test.js new file mode 100644 index 000000000..df52956a9 --- /dev/null +++ b/tap-snapshots/test-nyc-integration.js-TAP.test.js @@ -0,0 +1,566 @@ +/* IMPORTANT + * This snapshot file is auto-generated, but designed for humans. + * It should be checked into source control and tracked carefully. + * Re-generate by setting TAP_SNAPSHOT=1 and running tests. + * Make sure to inspect the output below. Do not ignore changes! + */ +'use strict' +exports[`test/nyc-integration.js TAP --include can be used to limit bin to instrumenting specific files > stdout 1`] = ` +-----------------|----------|----------|----------|----------|-------------------| +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | +-----------------|----------|----------|----------|----------|-------------------| +All files | 50 | 50 | 100 | 50 | | + half-covered.js | 50 | 50 | 100 | 50 | 6,7,8 | +-----------------|----------|----------|----------|----------|-------------------| + +` + +exports[`test/nyc-integration.js TAP --exclude should allow default exclude rules to be overridden > stdout 1`] = ` +---------------------------------|----------|----------|----------|----------|-------------------| +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | +---------------------------------|----------|----------|----------|----------|-------------------| +All files | 0 | 0 | 0 | 0 | | + cli | 0 | 0 | 0 | 0 | | + args.js | 0 | 100 | 100 | 0 | 1 | + by-arg2.js | 0 | 0 | 100 | 0 | 1,2,3,4,5,7 | + classes.js | 0 | 100 | 0 | 0 | 5,6,11,15 | + empty.js | 0 | 0 | 0 | 0 | | + env.js | 0 | 100 | 100 | 0 | 1 | + es6.js | 0 | 100 | 0 | 0 |... 11,16,17,22,23 | + external-instrumenter.js | 0 | 0 | 0 | 0 | 1 | + gc.js | 0 | 100 | 100 | 0 | 2,3 | + half-covered-failing.js | 0 | 0 | 100 | 0 | 1,3,5,6,7,8 | + selfspawn-fibonacci.js | 0 | 0 | 0 | 0 |... 24,25,26,27,28 | + skip-full.js | 0 | 100 | 100 | 0 | 1,2 | + test.js | 0 | 0 | 0 | 0 | | + cli/fakebin | 0 | 100 | 100 | 0 | | + npm-template.js | 0 | 100 | 100 | 0 | 2,3,4,7,9 | + cli/nyc-config-js | 0 | 0 | 100 | 0 | | + ignore.js | 0 | 100 | 100 | 0 | 1 | + index.js | 0 | 0 | 100 | 0 | 1,3,5,7,8,9,10 | + nyc.config.js | 0 | 100 | 100 | 0 | 1 | + nycrc-config.js | 0 | 100 | 100 | 0 | 1 | + cli/nycrc | 0 | 0 | 100 | 0 | | + ignore.js | 0 | 100 | 100 | 0 | 1 | + index.js | 0 | 0 | 100 | 0 | 1,3,5,7,8,9,10 | + cli/subdir/input-dir | 0 | 100 | 100 | 0 | | + index.js | 0 | 100 | 100 | 0 | 2 | + cli/subdir/input-dir/exclude-me | 0 | 100 | 100 | 0 | | + index.js | 0 | 100 | 100 | 0 | 2 | + cli/subdir/input-dir/include-me | 0 | 100 | 100 | 0 | | + exclude-me.js | 0 | 100 | 100 | 0 | 2 | + include-me.js | 0 | 100 | 100 | 0 | 2 | +---------------------------------|----------|----------|----------|----------|-------------------| + +` + +exports[`test/nyc-integration.js TAP report and check should show coverage check along with report > stdout 1`] = ` + +` + +exports[`test/nyc-integration.js TAP report and check should show coverage check along with report > stderr 1`] = ` +ERROR: Coverage for lines (50%) does not meet global threshold (100%) + +` + +exports[`test/nyc-integration.js TAP report and check should show coverage check along with report > stdout 2`] = ` +-----------------|----------|----------|----------|----------|-------------------| +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | +-----------------|----------|----------|----------|----------|-------------------| +All files | 50 | 50 | 100 | 50 | | + half-covered.js | 50 | 50 | 100 | 50 | 6,7,8 | +-----------------|----------|----------|----------|----------|-------------------| + +` + +exports[`test/nyc-integration.js TAP --ignore-class-method skips methods that match ignored name but still catches those that are not > stdout 1`] = ` +---------------------------------|----------|----------|----------|----------|-------------------| +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | +---------------------------------|----------|----------|----------|----------|-------------------| +All files | 1.61 | 0 | 5.56 | 2.15 | | + cli | 2.33 | 0 | 5.56 | 3.64 | | + args.js | 0 | 100 | 100 | 0 | 1 | + by-arg2.js | 0 | 0 | 100 | 0 | 1,2,3,4,5,7 | + classes.js | 66.67 | 100 | 50 | 66.67 | 6 | + empty.js | 0 | 0 | 0 | 0 | | + env.js | 0 | 100 | 100 | 0 | 1 | + es6.js | 0 | 100 | 0 | 0 |... 11,16,17,22,23 | + external-instrumenter.js | 0 | 0 | 0 | 0 | 1 | + gc.js | 0 | 100 | 100 | 0 | 2,3 | + half-covered-failing.js | 0 | 0 | 100 | 0 | 1,3,5,6,7,8 | + half-covered.js | 0 | 0 | 100 | 0 | 1,3,5,6,7,8 | + selfspawn-fibonacci.js | 0 | 0 | 0 | 0 |... 24,25,26,27,28 | + skip-full.js | 0 | 100 | 100 | 0 | 1,2 | + cli/fakebin | 0 | 100 | 100 | 0 | | + npm-template.js | 0 | 100 | 100 | 0 | 2,3,4,7,9 | + cli/nyc-config-js | 0 | 0 | 100 | 0 | | + ignore.js | 0 | 100 | 100 | 0 | 1 | + index.js | 0 | 0 | 100 | 0 | 1,3,5,7,8,9,10 | + nycrc-config.js | 0 | 100 | 100 | 0 | 1 | + cli/nycrc | 0 | 0 | 100 | 0 | | + ignore.js | 0 | 100 | 100 | 0 | 1 | + index.js | 0 | 0 | 100 | 0 | 1,3,5,7,8,9,10 | + cli/run-npm-test | 0 | 0 | 100 | 0 | | + half-covered.js | 0 | 0 | 100 | 0 | 1,3,5,6,7,8 | + cli/run-npm-test-recursive | 0 | 0 | 100 | 0 | | + half-covered.js | 0 | 0 | 100 | 0 | 1,3,5,6,7,8 | + cli/subdir/input-dir | 0 | 100 | 100 | 0 | | + index.js | 0 | 100 | 100 | 0 | 2 | + cli/subdir/input-dir/exclude-me | 0 | 100 | 100 | 0 | | + index.js | 0 | 100 | 100 | 0 | 2 | + cli/subdir/input-dir/include-me | 0 | 100 | 100 | 0 | | + exclude-me.js | 0 | 100 | 100 | 0 | 2 | + include-me.js | 0 | 100 | 100 | 0 | 2 | +---------------------------------|----------|----------|----------|----------|-------------------| + +` + +exports[`test/nyc-integration.js TAP --check-coverage fails when the expected coverage is below a threshold > stderr 1`] = ` +ERROR: Coverage for lines (50%) does not meet global threshold (51%) + +` + +exports[`test/nyc-integration.js TAP --check-coverage fails when the expected coverage is below a threshold > stdout 1`] = ` +-----------------|----------|----------|----------|----------|-------------------| +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | +-----------------|----------|----------|----------|----------|-------------------| +All files | 50 | 50 | 100 | 50 | | + half-covered.js | 50 | 50 | 100 | 50 | 6,7,8 | +-----------------|----------|----------|----------|----------|-------------------| + +` + +exports[`test/nyc-integration.js TAP --check-coverage fails when check-coverage command is used rather than flag > stdout 1`] = ` +-----------------|----------|----------|----------|----------|-------------------| +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | +-----------------|----------|----------|----------|----------|-------------------| +All files | 50 | 50 | 100 | 50 | | + half-covered.js | 50 | 50 | 100 | 50 | 6,7,8 | +-----------------|----------|----------|----------|----------|-------------------| + +` + +exports[`test/nyc-integration.js TAP --check-coverage fails when check-coverage command is used rather than flag > stderr 1`] = ` +ERROR: Coverage for lines (50%) does not meet global threshold (51%) + +` + +exports[`test/nyc-integration.js TAP --check-coverage fails when check-coverage command is used rather than flag > stdout 2`] = ` + +` + +exports[`test/nyc-integration.js TAP --check-coverage succeeds when the expected coverage is above a threshold > stdout 1`] = ` +-----------------|----------|----------|----------|----------|-------------------| +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | +-----------------|----------|----------|----------|----------|-------------------| +All files | 50 | 50 | 100 | 50 | | + half-covered.js | 50 | 50 | 100 | 50 | 6,7,8 | +-----------------|----------|----------|----------|----------|-------------------| + +` + +exports[`test/nyc-integration.js TAP --check-coverage fails in any case when the underlying test failed > stderr 1`] = ` +ERROR: Coverage for lines (33.33%) does not meet global threshold (49%) + +` + +exports[`test/nyc-integration.js TAP --check-coverage fails in any case when the underlying test failed > stdout 1`] = ` +-------------------------|----------|----------|----------|----------|-------------------| +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | +-------------------------|----------|----------|----------|----------|-------------------| +All files | 33.33 | 0 | 100 | 33.33 | | + half-covered-failing.js | 33.33 | 0 | 100 | 33.33 | 5,6,7,8 | +-------------------------|----------|----------|----------|----------|-------------------| + +` + +exports[`test/nyc-integration.js TAP --check-coverage fails when the expected file coverage is below a threshold > stderr 1`] = ` +ERROR: Coverage for lines (50%) does not meet threshold (51%) for ./half-covered.js + +` + +exports[`test/nyc-integration.js TAP --check-coverage fails when the expected file coverage is below a threshold > stdout 1`] = ` +-----------------|----------|----------|----------|----------|-------------------| +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | +-----------------|----------|----------|----------|----------|-------------------| +All files | 50 | 50 | 100 | 50 | | + half-covered.js | 50 | 50 | 100 | 50 | 6,7,8 | +-----------------|----------|----------|----------|----------|-------------------| + +` + +exports[`test/nyc-integration.js TAP passes configuration via environment variables > undefined 1`] = ` +{ silent: true, + cache: false, + sourceMap: true, + require: 'make-dir', + include: 'env.js', + exclude: 'batman.js', + extension: '.js', + cacheDir: '/tmp', + instrumenter: './lib/instrumenters/istanbul' } +` + +exports[`test/nyc-integration.js TAP allows package.json configuration to be overridden with command line args > stdout 1`] = ` +TN: +SF:./half-covered.js +FNF:0 +FNH:0 +DA:1,1 +DA:3,1 +DA:5,1 +DA:6,0 +DA:7,0 +DA:8,0 +LF:6 +LH:3 +BRDA:5,0,0,0 +BRDA:5,0,1,1 +BRF:2 +BRH:1 +end_of_record + +` + +exports[`test/nyc-integration.js TAP loads configuration from package.json and nyc.config.js > stdout 1`] = ` +TN: +SF:./index.js +FNF:0 +FNH:0 +DA:1,1 +DA:3,1 +DA:5,1 +DA:7,1 +DA:8,0 +DA:9,0 +DA:10,0 +LF:7 +LH:4 +BRDA:7,0,0,0 +BRDA:7,0,1,1 +BRF:2 +BRH:1 +end_of_record + +` + +exports[`test/nyc-integration.js TAP loads configuration from different module rather than nyc.config.js > stderr 1`] = ` +ERROR: Coverage for lines (57.14%) does not meet threshold (100%) for ./index.js +ERROR: Coverage for branches (50%) does not meet threshold (100%) for ./index.js +ERROR: Coverage for statements (57.14%) does not meet threshold (100%) for ./index.js +ERROR: Coverage for lines (0%) does not meet threshold (100%) for ./nyc.config.js +ERROR: Coverage for statements (0%) does not meet threshold (100%) for ./nyc.config.js +ERROR: Coverage for lines (0%) does not meet threshold (100%) for ./nycrc-config.js +ERROR: Coverage for statements (0%) does not meet threshold (100%) for ./nycrc-config.js + +` + +exports[`test/nyc-integration.js TAP loads configuration from different module rather than nyc.config.js > stdout 1`] = ` +TN: +SF:./ignore.js +FNF:0 +FNH:0 +DA:1,1 +LF:1 +LH:1 +BRF:0 +BRH:0 +end_of_record +TN: +SF:./index.js +FNF:0 +FNH:0 +DA:1,1 +DA:3,1 +DA:5,1 +DA:7,1 +DA:8,0 +DA:9,0 +DA:10,0 +LF:7 +LH:4 +BRDA:7,0,0,0 +BRDA:7,0,1,1 +BRF:2 +BRH:1 +end_of_record +TN: +SF:./nyc.config.js +FNF:0 +FNH:0 +DA:1,0 +LF:1 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:./nycrc-config.js +FNF:0 +FNH:0 +DA:1,0 +LF:1 +LH:0 +BRF:0 +BRH:0 +end_of_record + +` + +exports[`test/nyc-integration.js TAP allows nyc.config.js configuration to be overridden with command line args > stdout 1`] = ` +TN: +SF:./ignore.js +FNF:0 +FNH:0 +DA:1,1 +LF:1 +LH:1 +BRF:0 +BRH:0 +end_of_record +TN: +SF:./index.js +FNF:0 +FNH:0 +DA:1,1 +DA:3,1 +DA:5,1 +DA:7,1 +DA:8,0 +DA:9,0 +DA:10,0 +LF:7 +LH:4 +BRDA:7,0,0,0 +BRDA:7,0,1,1 +BRF:2 +BRH:1 +end_of_record +TN: +SF:./nyc.config.js +FNF:0 +FNH:0 +DA:1,0 +LF:1 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:./nycrc-config.js +FNF:0 +FNH:0 +DA:1,0 +LF:1 +LH:0 +BRF:0 +BRH:0 +end_of_record + +` + +exports[`test/nyc-integration.js TAP loads configuration from package.json and .nycrc > stdout 1`] = ` +TN: +SF:./index.js +FNF:0 +FNH:0 +DA:1,1 +DA:3,1 +DA:5,1 +DA:7,1 +DA:8,0 +DA:9,0 +DA:10,0 +LF:7 +LH:4 +BRDA:7,0,0,0 +BRDA:7,0,1,1 +BRF:2 +BRH:1 +end_of_record + +` + +exports[`test/nyc-integration.js TAP loads configuration from different file rather than .nycrc > stderr 1`] = ` +ERROR: Coverage for lines (57.14%) does not meet threshold (100%) for ./index.js +ERROR: Coverage for branches (50%) does not meet threshold (100%) for ./index.js +ERROR: Coverage for statements (57.14%) does not meet threshold (100%) for ./index.js + +` + +exports[`test/nyc-integration.js TAP loads configuration from different file rather than .nycrc > stdout 1`] = ` +TN: +SF:./ignore.js +FNF:0 +FNH:0 +DA:1,1 +LF:1 +LH:1 +BRF:0 +BRH:0 +end_of_record +TN: +SF:./index.js +FNF:0 +FNH:0 +DA:1,1 +DA:3,1 +DA:5,1 +DA:7,1 +DA:8,0 +DA:9,0 +DA:10,0 +LF:7 +LH:4 +BRDA:7,0,0,0 +BRDA:7,0,1,1 +BRF:2 +BRH:1 +end_of_record + +` + +exports[`test/nyc-integration.js TAP loads configuration from .nycrc.yml > stdout 1`] = ` +TN: +SF:./index.js +FNF:0 +FNH:0 +DA:1,1 +DA:3,1 +DA:5,1 +DA:7,1 +DA:8,0 +DA:9,0 +DA:10,0 +LF:7 +LH:4 +BRDA:7,0,0,0 +BRDA:7,0,1,1 +BRF:2 +BRH:1 +end_of_record + +` + +exports[`test/nyc-integration.js TAP loads configuration from .nycrc.yaml > stdout 1`] = ` +TN: +SF:./index.js +FNF:0 +FNH:0 +DA:1,1 +DA:3,1 +DA:5,1 +DA:7,1 +DA:8,0 +DA:9,0 +DA:10,0 +LF:7 +LH:4 +BRDA:7,0,0,0 +BRDA:7,0,1,1 +BRF:2 +BRH:1 +end_of_record + +` + +exports[`test/nyc-integration.js TAP allows .nycrc configuration to be overridden with command line args > stdout 1`] = ` +TN: +SF:./ignore.js +FNF:0 +FNH:0 +DA:1,1 +LF:1 +LH:1 +BRF:0 +BRH:0 +end_of_record +TN: +SF:./index.js +FNF:0 +FNH:0 +DA:1,1 +DA:3,1 +DA:5,1 +DA:7,1 +DA:8,0 +DA:9,0 +DA:10,0 +LF:7 +LH:4 +BRDA:7,0,0,0 +BRDA:7,0,1,1 +BRF:2 +BRH:1 +end_of_record + +` + +exports[`test/nyc-integration.js TAP reports appropriate coverage information for es6 source files > stdout 1`] = ` +sup +do not hit +----------|----------|----------|----------|----------|-------------------| +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | +----------|----------|----------|----------|----------|-------------------| +All files | 62.5 | 100 | 40 | 62.5 | | + es6.js | 62.5 | 100 | 40 | 62.5 | 11,16,17 | +----------|----------|----------|----------|----------|-------------------| + +` + +exports[`test/nyc-integration.js TAP hooks provide coverage for requireJS and AMD modules > stdout 1`] = ` +----------|----------|----------|----------|----------|-------------------| +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | +----------|----------|----------|----------|----------|-------------------| +All files | 100 | 100 | 100 | 100 | | + ipsum.js | 100 | 100 | 100 | 100 | | + lorem.js | 100 | 100 | 100 | 100 | | +----------|----------|----------|----------|----------|-------------------| + +` + +exports[`test/nyc-integration.js TAP does not interpret args intended for instrumented bin > undefined 1`] = ` +[ '--help', '--version' ] +` + +exports[`test/nyc-integration.js TAP interprets first args after -- as Node.js execArgv > stdout 1`] = ` +I’m still running +----------|----------|----------|----------|----------|-------------------| +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | +----------|----------|----------|----------|----------|-------------------| +All files | 100 | 100 | 100 | 100 | | + gc.js | 100 | 100 | 100 | 100 | | +----------|----------|----------|----------|----------|-------------------| + +` + +exports[`test/nyc-integration.js TAP --show-process-tree displays a tree of spawned processes > stdout 1`] = ` +3 +------------------------|----------|----------|----------|----------|-------------------| +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | +------------------------|----------|----------|----------|----------|-------------------| +All files | 90.91 | 70 | 100 | 100 | | + selfspawn-fibonacci.js | 90.91 | 70 | 100 | 100 | 4,25,27 | +------------------------|----------|----------|----------|----------|-------------------| +nyc +└─┬ node ./selfspawn-fibonacci.js 5 + │ 100 % Lines + ├─┬ node ./selfspawn-fibonacci.js 4 + │ │ 100 % Lines + │ ├─┬ node ./selfspawn-fibonacci.js 3 + │ │ │ 100 % Lines + │ │ ├── node ./selfspawn-fibonacci.js 2 + │ │ │ 31.58 % Lines + │ │ └── node ./selfspawn-fibonacci.js 1 + │ │ 26.32 % Lines + │ └── node ./selfspawn-fibonacci.js 2 + │ 31.58 % Lines + └─┬ node ./selfspawn-fibonacci.js 3 + │ 100 % Lines + ├── node ./selfspawn-fibonacci.js 2 + │ 31.58 % Lines + └── node ./selfspawn-fibonacci.js 1 + 26.32 % Lines + + +` diff --git a/test/fixtures/cli/half-covered-failing.js b/test/fixtures/cli/half-covered-failing.js index 254a54f0b..026512b64 100644 --- a/test/fixtures/cli/half-covered-failing.js +++ b/test/fixtures/cli/half-covered-failing.js @@ -1,6 +1,6 @@ var a = 0 -throw new Error +process.exit(1) if (a === 0) { a++; diff --git a/test/helpers/index.js b/test/helpers/index.js new file mode 100644 index 000000000..78297caf8 --- /dev/null +++ b/test/helpers/index.js @@ -0,0 +1,12 @@ +'use strict' + +const { fixturesCLI, nycBin } = require('./paths') + +module.exports = { + fixturesCLI, + nycBin, + testSuccess: require('./test-success'), + testFailure: require('./test-failure'), + runNYC: require('./run-nyc'), + tempDirSetup: require('./temp-dir-setup') +} diff --git a/test/helpers/paths.js b/test/helpers/paths.js new file mode 100644 index 000000000..0bca0156a --- /dev/null +++ b/test/helpers/paths.js @@ -0,0 +1,8 @@ +'use strict' + +const path = require('path') + +module.exports = { + nycBin: require.resolve('../../self-coverage/bin/nyc'), + fixturesCLI: path.resolve(__dirname, '../fixtures/cli') +} diff --git a/test/helpers/run-nyc.js b/test/helpers/run-nyc.js new file mode 100644 index 000000000..c8ac93d1d --- /dev/null +++ b/test/helpers/run-nyc.js @@ -0,0 +1,40 @@ +'use strict' + +const { nycBin, fixturesCLI } = require('./paths') +const spawn = require('./spawn') + +const env = { + PATH: process.env.PATH +} + +function sanitizeString (str, cwd, leavePathSep) { + /* + * File paths are different on different systems: + * - make everything relative to cwd + * - replace full node path with 'node' + * - replace all Windows path separators ('\\') with POSIX path separators + */ + str = str + .split(cwd).join('.') + .split(process.execPath).join('node') + + if (!leavePathSep) { + str = str.replace(/\\/g, '/') + } + + return str +} + +function runNYC ({ args, tempDir, leavePathSep, cwd = fixturesCLI }) { + const runArgs = [nycBin].concat(tempDir ? ['--temp-dir', tempDir] : [], args) + return spawn(process.execPath, runArgs, { + cwd: cwd, + env + }).then(({ status, stderr, stdout }) => ({ + status, + stderr: sanitizeString(stderr, cwd, leavePathSep), + stdout: sanitizeString(stdout, cwd, leavePathSep) + })) +} + +module.exports = runNYC diff --git a/test/helpers/spawn.js b/test/helpers/spawn.js new file mode 100644 index 000000000..c8aae3029 --- /dev/null +++ b/test/helpers/spawn.js @@ -0,0 +1,23 @@ +const cp = require('child_process') + +function spawn (exe, args, opts) { + return new Promise((resolve, reject) => { + const proc = cp.spawn(exe, args, opts) + const stdout = [] + const stderr = [] + + proc.stdout.on('data', buf => stdout.push(buf)) + proc.stderr.on('data', buf => stderr.push(buf)) + + proc.on('error', reject) + proc.on('close', status => { + resolve({ + status, + stdout: Buffer.concat(stdout).toString(), + stderr: Buffer.concat(stderr).toString() + }) + }) + }) +} + +module.exports = spawn diff --git a/test/helpers/temp-dir-setup.js b/test/helpers/temp-dir-setup.js new file mode 100644 index 000000000..bb9beceb1 --- /dev/null +++ b/test/helpers/temp-dir-setup.js @@ -0,0 +1,33 @@ +'use strict' + +const path = require('path') +const fs = require('fs') +const makeDir = require('make-dir') +const _rimraf = require('rimraf') +const pify = require('pify') + +const rimraf = pify(_rimraf) +const mkdtemp = pify(fs.mkdtemp) + +function tempDirSetup (t, testFile) { + const { dir, name } = path.parse(testFile) + const tempDirBase = path.resolve(dir, 'temp-dir-' + name) + + makeDir.sync(tempDirBase) + + // Do not use arrow function for beforeEach + // or afterEach, they need this from tap. + t.beforeEach(function () { + return mkdtemp(tempDirBase + '/').then(tempDir => { + this.tempDir = tempDir + }) + }) + + t.afterEach(function () { + return rimraf(this.tempDir) + }) + + t.tearDown(() => rimraf(tempDirBase)) +} + +module.exports = tempDirSetup diff --git a/test/helpers/test-failure.js b/test/helpers/test-failure.js new file mode 100644 index 000000000..7d891d24c --- /dev/null +++ b/test/helpers/test-failure.js @@ -0,0 +1,14 @@ +'use strict' + +const runNYC = require('./run-nyc') + +function testFailure (t, opts) { + opts.tempDir = t.tempDir + return runNYC(opts).then(({ status, stderr, stdout }) => { + t.equal(status, 1) + t.matchSnapshot(stderr, 'stderr') + t.matchSnapshot(stdout, 'stdout') + }) +} + +module.exports = testFailure diff --git a/test/helpers/test-success.js b/test/helpers/test-success.js new file mode 100644 index 000000000..90abbf724 --- /dev/null +++ b/test/helpers/test-success.js @@ -0,0 +1,14 @@ +'use strict' + +const runNYC = require('./run-nyc') + +function testSuccess (t, opts) { + opts.tempDir = t.tempDir + return runNYC(opts).then(({ status, stderr, stdout }) => { + t.equal(status, 0) + t.equal(stderr, '') + t.matchSnapshot(stdout, 'stdout') + }) +} + +module.exports = testSuccess diff --git a/test/nyc-integration-old.js b/test/nyc-integration-old.js new file mode 100644 index 000000000..ca7c28a43 --- /dev/null +++ b/test/nyc-integration-old.js @@ -0,0 +1,1402 @@ +/* global describe, it, beforeEach, afterEach */ + +// TODO: finish migrating these tests to use snapshots +const _ = require('lodash') +const path = require('path') +const bin = path.resolve(__dirname, '../self-coverage/bin/nyc') +const fixturesCLI = path.resolve(__dirname, './fixtures/cli') +const fixturesSourceMaps = path.resolve(__dirname, './fixtures/source-maps') +const fakebin = path.resolve(fixturesCLI, 'fakebin') +const fs = require('fs') +const glob = require('glob') +const isWindows = require('is-windows')() +const rimraf = require('rimraf') +const makeDir = require('make-dir') +const { spawn, spawnSync } = require('child_process') +const si = require('strip-indent') + +require('chai').should() +require('tap').mochaGlobals() + +// beforeEach +rimraf.sync(path.resolve(fakebin, 'node')) +rimraf.sync(path.resolve(fakebin, 'npm')) +rimraf.sync(path.resolve(fixturesCLI, 'subdir', 'output-dir')) + +describe('the nyc cli', function () { + var env = { PATH: process.env.PATH } + + // https://github.com/bcoe/nyc/issues/190 + describe('running "npm test"', function () { + it('can run "npm test" which directly invokes a test file', function (done) { + var args = [bin, 'npm', 'test'] + var directory = path.resolve(fixturesCLI, 'run-npm-test') + var proc = spawn(process.execPath, args, { + cwd: directory, + env: env + }) + + proc.on('close', function (code) { + code.should.equal(0) + done() + }) + }) + + it('can run "npm test" which indirectly invokes a test file', function (done) { + var args = [bin, 'npm', 'test'] + var directory = path.resolve(fixturesCLI, 'run-npm-test-recursive') + var proc = spawn(process.execPath, args, { + cwd: directory, + env: env + }) + + proc.on('close', function (code) { + code.should.equal(0) + done() + }) + }) + + function writeFakeNPM (shebang) { + var targetPath = path.resolve(fakebin, 'npm') + var source = fs.readFileSync(path.resolve(fakebin, 'npm-template.js')) + fs.writeFileSync(targetPath, '#!' + shebang + '\n' + source) + fs.chmodSync(targetPath, 493) // 0o755 + } + + it('can run "npm test", absolute shebang edition', function (done) { + if (isWindows) return done() + + writeFakeNPM(process.execPath) + + var args = [bin, 'npm', 'test'] + var directory = path.resolve(fixturesCLI, 'run-npm-test-recursive') + var proc = spawn(process.execPath, args, { + cwd: directory, + env: { + PATH: fakebin + ':' + env.PATH + } + }) + + proc.on('close', function (code) { + code.should.equal(0) + done() + }) + }) + + it('can run "npm test", weird bash+dirname shebang edition', function (done) { + if (isWindows) return done() + + // This string is taken verbatim from tools/install.py in Node core v5.x + writeFakeNPM('/bin/sh\n// 2>/dev/null; exec "`dirname "$0"`/node" "$0" "$@"') + fs.symlinkSync(process.execPath, path.resolve(fakebin, 'node')) + + var args = [bin, 'npm', 'test'] + var directory = path.resolve(fixturesCLI, 'run-npm-test-recursive') + var proc = spawn(process.execPath, args, { + cwd: directory, + env: { + PATH: fakebin + ':' + env.PATH + } + }) + + proc.on('close', function (code) { + code.should.equal(0) + done() + }) + }) + }) + + describe('instrument', function () { + beforeEach(() => { + rimraf.sync(path.resolve(fixturesCLI, 'subdir', 'output-dir')) + }) + + describe('no output folder', function () { + it('allows a single file to be instrumented', function (done) { + var args = [bin, 'instrument', './half-covered.js'] + + var proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + var stdout = '' + proc.stdout.on('data', function (chunk) { + stdout += chunk + }) + + proc.on('close', function (code) { + code.should.equal(0) + stdout.should.contain(`path:${JSON.stringify(path.resolve(fixturesCLI, 'half-covered.js'))}`) + done() + }) + }) + + it('allows a directory of files to be instrumented', function (done) { + var args = [bin, 'instrument', './'] + + var proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + var stdout = '' + proc.stdout.on('data', function (chunk) { + stdout += chunk + }) + + proc.on('close', function (code) { + code.should.equal(0) + stdout.should.match(/half-covered\.js"/) + stdout.should.match(/half-covered-failing\.js"/) + stdout.should.not.match(/spawn\.js"/) + done() + }) + }) + + it('returns unmodified source if there is no transform', function (done) { + const args = [bin, 'instrument', './no-transform/half-covered.xjs'] + + const proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + let stdout = '' + proc.stdout.on('data', function (chunk) { + stdout += chunk + }) + + proc.on('close', function (code) { + code.should.equal(0) + stdout.should.contain(`var a = 0`) + done() + }) + }) + }) + + describe('output folder specified', function () { + afterEach(function () { + rimraf.sync(path.resolve(fixturesCLI, 'output')) + }) + + it('works in directories without a package.json', function (done) { + const args = [bin, 'instrument', './input-dir', './output-dir'] + + const subdir = path.resolve(fixturesCLI, 'subdir') + const proc = spawn(process.execPath, args, { + cwd: subdir, + env: env + }) + + proc.on('exit', function (code) { + code.should.equal(0) + const target = path.resolve(subdir, 'output-dir', 'index.js') + fs.readFileSync(target, 'utf8') + .should.match(/console.log\('Hello, World!'\)/) + done() + }) + }) + + it('can be configured to exit on error', function (done) { + const args = [bin, 'instrument', '--exit-on-error', './input-dir', './output-dir'] + + const subdir = path.resolve(fixturesCLI, 'subdir') + const proc = spawn(process.execPath, args, { + cwd: subdir, + env: env + }) + + proc.on('exit', function (code) { + code.should.equal(1) + done() + }) + }) + + it('allows a single file to be instrumented', function (done) { + const args = [bin, 'instrument', './half-covered.js', './output'] + + const inputPath = path.resolve(fixturesCLI, './half-covered.js') + const inputMode = fs.statSync(inputPath).mode & 0o7777 + const newMode = 0o775 + if (process.platform !== 'win32') { + fs.chmodSync(inputPath, newMode) + } + + const proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + proc.on('close', function (code) { + code.should.equal(0) + const files = fs.readdirSync(path.resolve(fixturesCLI, './output')) + files.length.should.equal(1) + files.should.include('half-covered.js') + + if (process.platform !== 'win32') { + const outputPath = path.resolve(fixturesCLI, 'output', 'half-covered.js') + const outputMode = fs.statSync(outputPath).mode & 0o7777 + outputMode.should.equal(newMode) + + fs.chmodSync(inputPath, inputMode) + } + + done() + }) + }) + + it('allows a directory of files to be instrumented', function (done) { + const args = [bin, 'instrument', './nyc-config-js', './output'] + + const proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + proc.on('close', function (code) { + code.should.equal(0) + const files = fs.readdirSync(path.resolve(fixturesCLI, './output')) + files.should.include('index.js') + files.should.include('ignore.js') + files.should.not.include('package.json') + files.should.not.include('node_modules') + const includeTarget = path.resolve(fixturesCLI, 'output', 'ignore.js') + fs.readFileSync(includeTarget, 'utf8') + .should.match(/var cov_/) + done() + }) + }) + + it('copies all files from to as well as those that have been instrumented', function (done) { + const args = [bin, 'instrument', '--complete-copy', './nyc-config-js', './output'] + + // force node_modules to exist so we can verify that it is copied. + const nmDir = path.resolve(fixturesCLI, 'nyc-config-js', 'node_modules') + makeDir.sync(nmDir) + fs.writeFileSync(path.join(nmDir, 'test-file'), '') + const proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + proc.on('close', function (code) { + code.should.equal(0) + const files = fs.readdirSync(path.resolve(fixturesCLI, './output')) + files.should.include('index.js') + files.should.include('ignore.js') + files.should.include('package.json') + files.should.include('node_modules') + const includeTarget = path.resolve(fixturesCLI, 'output', 'ignore.js') + fs.readFileSync(includeTarget, 'utf8') + .should.match(/var cov_/) + done() + }) + }) + + it('can instrument the project directory', function (done) { + const args = [bin, 'instrument', '.', './output'] + + const proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + proc.on('close', function (code) { + code.should.equal(0) + const files = fs.readdirSync(path.resolve(fixturesCLI, './output')) + files.should.include('args.js') + files.should.include('subdir') + done() + }) + }) + + it('allows a sub-directory of files to be instrumented', function (done) { + const args = [bin, 'instrument', './subdir/input-dir', './output'] + + const proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + proc.on('close', function (code) { + code.should.equal(0) + const files = fs.readdirSync(path.resolve(fixturesCLI, './output')) + files.should.include('index.js') + done() + }) + }) + + it('allows a subdirectory to be excluded via .nycrc file', function (done) { + const args = [bin, 'instrument', '--nycrc-path', './.instrument-nycrc', './subdir/input-dir', './output'] + + const proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + proc.on('close', function (code) { + code.should.equal(0) + const files = fs.readdirSync(path.resolve(fixturesCLI, './output')) + files.length.should.not.equal(0) + files.should.include('exclude-me') + files.should.include('node_modules') + files.should.include('index.js') + files.should.include('bad.js') + const includeTarget = path.resolve(fixturesCLI, 'output', 'index.js') + fs.readFileSync(includeTarget, 'utf8') + .should.match(/var cov_/) + const excludeTarget = path.resolve(fixturesCLI, 'output', 'exclude-me', 'index.js') + fs.readFileSync(excludeTarget, 'utf8') + .should.not.match(/var cov_/) + done() + }) + }) + + it('allows a file to be excluded', function (done) { + const args = [bin, 'instrument', '--complete-copy', '--exclude', 'exclude-me/index.js', './subdir/input-dir', './output'] + + const proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + proc.on('close', function (code) { + code.should.equal(0) + const files = fs.readdirSync(path.resolve(fixturesCLI, './output')) + files.length.should.not.equal(0) + files.should.include('exclude-me') + const excludeTarget = path.resolve(fixturesCLI, 'output', 'exclude-me', 'index.js') + fs.readFileSync(excludeTarget, 'utf8') + .should.not.match(/var cov_/) + done() + }) + }) + + it('allows specifying a single sub-directory to be included', function (done) { + const args = [bin, 'instrument', '--include', '**/include-me/**', './subdir/input-dir', './output'] + + const proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + proc.on('close', function (code) { + code.should.equal(0) + const files = fs.readdirSync(path.resolve(fixturesCLI, './output')) + files.length.should.not.equal(0) + files.should.include('include-me') + const instrumented = path.resolve(fixturesCLI, 'output', 'include-me', 'include-me.js') + fs.readFileSync(instrumented, 'utf8') + .should.match(/var cov_/) + done() + }) + }) + + it('allows a file to be excluded from an included directory', function (done) { + const args = [bin, 'instrument', '--complete-copy', '--exclude', '**/exclude-me.js', '--include', '**/include-me/**', './subdir/input-dir', './output'] + + const proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + proc.on('close', function (code) { + code.should.equal(0) + const files = fs.readdirSync(path.resolve(fixturesCLI, './output')) + files.length.should.not.equal(0) + files.should.include('include-me') + const includeMeFiles = fs.readdirSync(path.resolve(fixturesCLI, 'output', 'include-me')) + includeMeFiles.length.should.not.equal(0) + includeMeFiles.should.include('include-me.js') + includeMeFiles.should.include('exclude-me.js') + const includeTarget = path.resolve(fixturesCLI, 'output', 'include-me', 'include-me.js') + fs.readFileSync(includeTarget, 'utf8') + .should.match(/var cov_/) + const excludeTarget = path.resolve(fixturesCLI, 'output', 'exclude-me', 'index.js') + fs.readFileSync(excludeTarget, 'utf8') + .should.not.match(/var cov_/) + done() + }) + }) + + it('aborts if trying to write files in place', function (done) { + const args = [bin, 'instrument', '--delete', './', './'] + + const proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + let stderr = '' + proc.stderr.on('data', function (chunk) { + stderr += chunk + }) + + proc.on('close', function (code) { + code.should.equal(1) + stderr.should.include('nyc instrument failed: cannot instrument files in place') + done() + }) + }) + + it('aborts if trying to instrument files from outside the project root directory', function (done) { + const args = [bin, 'instrument', '--delete', '../', './'] + + const proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + let stderr = '' + proc.stderr.on('data', function (chunk) { + stderr += chunk + }) + + proc.on('close', function (code) { + code.should.equal(1) + stderr.should.include('nyc instrument failed: cannot instrument files outside of project root directory') + done() + }) + }) + + describe('es-modules', function () { + afterEach(function () { + rimraf.sync(path.resolve(fixturesCLI, './output')) + }) + + it('instruments file with `package` keyword when es-modules is disabled', function (done) { + const args = [bin, 'instrument', '--no-es-modules', './not-strict.js', './output'] + + const proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + proc.on('close', function (code) { + code.should.equal(0) + const subdirExists = fs.existsSync(path.resolve(fixturesCLI, './output')) + subdirExists.should.equal(true) + const files = fs.readdirSync(path.resolve(fixturesCLI, './output')) + files.should.include('not-strict.js') + done() + }) + }) + + it('fails on file with `package` keyword when es-modules is enabled', function (done) { + const args = [bin, 'instrument', '--exit-on-error', './not-strict.js', './output'] + + const proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + let stderr = '' + proc.stderr.on('data', function (chunk) { + stderr += chunk + }) + + proc.on('close', function (code) { + code.should.equal(1) + stdoutShouldEqual(stderr, ` + Failed to instrument ${path.resolve(fixturesCLI, 'not-strict.js')}`) + const subdirExists = fs.existsSync(path.resolve(fixturesCLI, './output')) + subdirExists.should.equal(false) + done() + }) + }) + }) + + describe('delete', function () { + beforeEach(function () { + makeDir.sync(path.resolve(fixturesCLI, 'output', 'removed-by-clean')) + }) + + it('cleans the output directory if `--delete` is specified', function (done) { + const args = [bin, 'instrument', '--delete', 'true', './subdir/input-dir', './output'] + + const proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + proc.on('close', function (code) { + code.should.equal(0) + const subdirExists = fs.existsSync(path.resolve(fixturesCLI, './output')) + subdirExists.should.equal(true) + const files = fs.readdirSync(path.resolve(fixturesCLI, './output')) + files.should.not.include('removed-by-clean') + files.should.include('exclude-me') + done() + }) + }) + + it('does not clean the output directory by default', function (done) { + const args = [bin, 'instrument', './subdir/input-dir', './output'] + + const proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + proc.on('close', function (code) { + code.should.equal(0) + const subdirExists = fs.existsSync(path.resolve(fixturesCLI, './output')) + subdirExists.should.equal(true) + const files = fs.readdirSync(path.resolve(fixturesCLI, './output')) + files.should.include('removed-by-clean') + done() + }) + }) + + it('aborts if trying to clean process.cwd()', function (done) { + const args = [bin, 'instrument', '--delete', './src', './'] + + const proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + let stderr = '' + proc.stderr.on('data', function (chunk) { + stderr += chunk + }) + + proc.on('close', function (code) { + code.should.equal(1) + stderr.should.include('nyc instrument failed: attempt to delete') + done() + }) + }) + + it('aborts if trying to clean outside working directory', function (done) { + const args = [bin, 'instrument', '--delete', './', '../'] + + const proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + let stderr = '' + proc.stderr.on('data', function (chunk) { + stderr += chunk + }) + + proc.on('close', function (code) { + code.should.equal(1) + stderr.should.include('nyc instrument failed: attempt to delete') + done() + }) + }) + }) + }) + }) + + it('help shows to stderr when main command doesn\'t know what to do', () => { + const opts = { + cwd: fixturesCLI, + env, + encoding: 'utf8' + } + + const help = spawnSync(process.execPath, [bin, '--help'], opts) + const unknown = spawnSync(process.execPath, [bin], opts) + help.status.should.equal(0) + unknown.status.should.equal(1) + help.stderr.should.equal('') + unknown.stdout.should.equal('') + help.stdout.should.not.equal('') + help.stdout.should.equal(unknown.stderr) + }) + + describe('--temp-dir', function () { + beforeEach(() => { + rimraf.sync(path.resolve(fixturesCLI, '.nyc_output')) + rimraf.sync(path.resolve(fixturesCLI, '.temp_directory')) + rimraf.sync(path.resolve(fixturesCLI, '.temp_dir')) + }) + + it('creates the default \'tempDir\' when none is specified', function (done) { + var args = [bin, process.execPath, './half-covered.js'] + + var proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + proc.on('close', function (code) { + code.should.equal(0) + var tempFiles = fs.readdirSync(path.resolve(fixturesCLI, '.nyc_output')) + tempFiles.length.should.equal(2) // the coverage file, and processinfo + var cliFiles = fs.readdirSync(path.resolve(fixturesCLI)) + cliFiles.should.include('.nyc_output') + cliFiles.should.not.include('.temp_dir') + cliFiles.should.not.include('.temp_directory') + done() + }) + }) + + it('prefers \'tempDirectory\' to \'tempDir\'', function (done) { + var args = [bin, '--tempDirectory', '.temp_directory', '--tempDir', '.temp_dir', process.execPath, './half-covered.js'] + + var proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + proc.on('exit', function (code) { + code.should.equal(0) + var tempFiles = fs.readdirSync(path.resolve(fixturesCLI, '.temp_directory')) + tempFiles.length.should.equal(2) + var cliFiles = fs.readdirSync(path.resolve(fixturesCLI)) + cliFiles.should.not.include('.nyc_output') + cliFiles.should.not.include('.temp_dir') + cliFiles.should.include('.temp_directory') + done() + }) + }) + + it('uses the \'tempDir\' option if \'tempDirectory\' is not set', function (done) { + var args = [bin, '--tempDir', '.temp_dir', process.execPath, './half-covered.js'] + + var proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + proc.on('exit', function (code) { + code.should.equal(0) + var tempFiles = fs.readdirSync(path.resolve(fixturesCLI, '.temp_dir')) + tempFiles.length.should.equal(2) + var cliFiles = fs.readdirSync(path.resolve(fixturesCLI)) + cliFiles.should.not.include('.nyc_output') + cliFiles.should.include('.temp_dir') + cliFiles.should.not.include('.temp_directory') + rimraf.sync(path.resolve(fixturesCLI, '.temp_dir')) + done() + }) + }) + }) + + it('handles --clean / --no-clean properly', () => { + rimraf.sync(path.resolve(fixturesCLI, '.nyc_output')) + const args = (doClean, arg) => [ + bin, + doClean ? '--clean' : '--no-clean', + process.execPath, + './by-arg2.js', + arg + ] + const opts = { + cwd: fixturesCLI, + env: env, + encoding: 'utf8' + } + + const proc1 = spawnSync(process.execPath, args(true, '1'), opts) + proc1.status.should.equal(0) + stdoutShouldEqual(proc1.stdout, ` + 1 + ------------|----------|----------|----------|----------|-------------------| + File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | + ------------|----------|----------|----------|----------|-------------------| + All files | 50 | 25 | 100 | 50 | | + by-arg2.js | 50 | 25 | 100 | 50 | 4,5,7 | + ------------|----------|----------|----------|----------|-------------------|` + ) + proc1.stderr.should.equal('') + + const proc2 = spawnSync(process.execPath, args(false, '2'), opts) + proc2.status.should.equal(0) + stdoutShouldEqual(proc2.stdout, ` + 2 + ------------|----------|----------|----------|----------|-------------------| + File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | + ------------|----------|----------|----------|----------|-------------------| + All files | 83.33 | 75 | 100 | 83.33 | | + by-arg2.js | 83.33 | 75 | 100 | 83.33 | 7 | + ------------|----------|----------|----------|----------|-------------------|` + ) + proc2.stderr.should.equal('') + }) + + describe('noop instrumenter', function () { + it('setting instrument to "false" configures noop instrumenter', function (done) { + var args = [ + bin, + '--silent', + '--no-instrument', + '--no-source-map', + process.execPath, + './env.js' + ] + var expected = { + silent: true, + instrument: false, + sourceMap: false, + instrumenter: './lib/instrumenters/noop' + } + + var proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + var stdout = '' + proc.stdout.on('data', function (chunk) { + stdout += chunk + }) + + proc.on('close', function (code) { + code.should.equal(0) + var env = JSON.parse(stdout) + var config = JSON.parse(env.NYC_CONFIG, null, 2) + config.should.include(expected) + done() + }) + }) + + describe('--all', function () { + it('extracts coverage headers from unexecuted files', function (done) { + var nycOutput = path.resolve(fixturesCLI, '.nyc_output') + rimraf.sync(nycOutput) + + var args = [ + bin, + '--all', + '--silent', + '--no-instrument', + '--no-source-map', + process.execPath, + // any file other than external-instrument.js, which we + // want to ensure has its header loaded. + './env.js' + ] + + var proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + proc.on('close', function (code) { + code.should.equal(0) + glob(nycOutput + '/*.json', function (_err, files) { + // we should have extracted the coverage header from external-instrumenter.js. + var coverage = {} + files.forEach(function (file) { + _.assign(coverage, JSON.parse( + fs.readFileSync(file, 'utf-8') + )) + }) + Object.keys(coverage).should.include('./external-instrumenter.js') + + // we should not have executed file, so all counts sould be 0. + var sum = 0 + Object.keys(coverage['./external-instrumenter.js'].s).forEach(function (k) { + sum += coverage['./external-instrumenter.js'].s[k] + }) + sum.should.equal(0) + + return done() + }) + }) + }) + }) + }) + + it('allows an alternative cache folder to be specified', function (done) { + var args = [bin, '--cache-dir=./foo-cache', '--cache=true', process.execPath, './half-covered.js'] + + var proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + proc.on('close', function (code) { + code.should.equal(0) + // we should have created ./foo-cache rather + // than the default ./node_modules/.cache. + fs.readdirSync(path.resolve( + fixturesCLI, './foo-cache' + )).length.should.equal(1) + rimraf.sync(path.resolve(fixturesCLI, 'foo-cache')) + done() + }) + }) + + // see: https://github.com/istanbuljs/nyc/issues/563 + it('does not create .cache folder if cache is "false"', function (done) { + var args = [bin, '--cache=false', process.execPath, './index.js'] + + var proc = spawn(process.execPath, args, { + cwd: process.cwd(), + env: env + }) + + rimraf.sync('./node_modules/.cache') + + proc.on('close', function (code) { + code.should.equal(0) + fs.existsSync('./node_modules/.cache').should.equal(false) + done() + }) + }) + + it('allows alternative high and low watermarks to be configured', function (done) { + var args = [ + bin, + '--watermarks.lines=90', + '--watermarks.lines=100', + '--watermarks.statements=30', + '--watermarks.statements=40', + '--cache=true', + process.execPath, + './half-covered.js' + ] + + var proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: { + PATH: process.env.PATH, + FORCE_COLOR: true + } + }) + + var stdout = '' + proc.stdout.on('data', function (chunk) { + stdout += chunk + }) + + proc.on('close', function (code) { + code.should.equal(0) + // 50% line coverage is below our low watermark (so it's red). + stdout.should.match(/\[31;1m\W+50\W+/) + // 50% statement coverage is above our high-watermark (so it's green). + stdout.should.match(/\[32;1m\W+50\W+/) + done() + }) + }) + + // the following tests exercise nyc's behavior around source-maps + // that have been included with pre-instrumented files. Perhaps, as an + // example, unit tests are being run against minified JavaScript. + // --exclude-after-remap will likely need to be set to false when + // using nyc with this type of configuration. + describe('source-maps', () => { + describe('--all', () => { + it('includes files with both .map files and inline source-maps', (done) => { + const args = [ + bin, + '--all', + '--cache', 'false', + '--exclude-after-remap', 'false', + '--exclude', 'original', + process.execPath, './instrumented/s1.min.js' + ] + + const proc = spawn(process.execPath, args, { + cwd: fixturesSourceMaps, + env: env + }) + + var stdout = '' + proc.stdout.on('data', function (chunk) { + stdout += chunk + }) + + proc.on('close', function (code) { + code.should.equal(0) + stdoutShouldEqual(stdout, ` + ----------|----------|----------|----------|----------|-------------------| + File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | + ----------|----------|----------|----------|----------|-------------------| + All files | 44.44 | 100 | 33.33 | 44.44 | | + s1.js | 80 | 100 | 50 | 80 | 7 | + s2.js | 0 | 100 | 0 | 0 | 1,2,4,6 | + ----------|----------|----------|----------|----------|-------------------|` + ) + done() + }) + }) + + it('uses source-maps to exclude original sources from reports', (done) => { + const args = [ + bin, + '--all', + '--cache', 'false', + '--exclude', 'original/s1.js', + process.execPath, './instrumented/s1.min.js' + ] + + const proc = spawn(process.execPath, args, { + cwd: fixturesSourceMaps, + env: env + }) + + var stdout = '' + proc.stdout.on('data', function (chunk) { + stdout += chunk + }) + + proc.on('close', function (code) { + code.should.equal(0) + stdoutShouldEqual(stdout, ` + ----------|----------|----------|----------|----------|-------------------| + File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | + ----------|----------|----------|----------|----------|-------------------| + All files | 0 | 100 | 0 | 0 | | + s2.js | 0 | 100 | 0 | 0 | 1,2,4,6 | + ----------|----------|----------|----------|----------|-------------------|` + ) + done() + }) + }) + }) + + describe('.map file', () => { + it('appropriately instruments file with corresponding .map file', (done) => { + const args = [ + bin, + '--cache', 'false', + '--exclude-after-remap', 'false', + '--exclude', 'original', + process.execPath, './instrumented/s1.min.js' + ] + + const proc = spawn(process.execPath, args, { + cwd: fixturesSourceMaps, + env: env + }) + + var stdout = '' + proc.stdout.on('data', function (chunk) { + stdout += chunk + }) + + proc.on('close', function (code) { + code.should.equal(0) + stdoutShouldEqual(stdout, ` + ----------|----------|----------|----------|----------|-------------------| + File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | + ----------|----------|----------|----------|----------|-------------------| + All files | 80 | 100 | 50 | 80 | | + s1.js | 80 | 100 | 50 | 80 | 7 | + ----------|----------|----------|----------|----------|-------------------|`) + done() + }) + }) + }) + + describe('inline', () => { + it('appropriately instruments a file with an inline source-map', (done) => { + const args = [ + bin, + '--cache', 'false', + '--exclude-after-remap', 'false', + '--exclude', 'original', + process.execPath, './instrumented/s2.min.js' + ] + + const proc = spawn(process.execPath, args, { + cwd: fixturesSourceMaps, + env: env + }) + + var stdout = '' + proc.stdout.on('data', function (chunk) { + stdout += chunk + }) + + proc.on('close', function (code) { + code.should.equal(0) + stdoutShouldEqual(stdout, ` + ----------|----------|----------|----------|----------|-------------------| + File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | + ----------|----------|----------|----------|----------|-------------------| + All files | 100 | 100 | 100 | 100 | | + s2.js | 100 | 100 | 100 | 100 | | + ----------|----------|----------|----------|----------|-------------------|`) + done() + }) + }) + }) + }) + + describe('skip-empty', () => { + it('does not display 0-line files in coverage output', (done) => { + const args = [ + bin, + '--cache', 'false', + '--skip-empty', 'true', + process.execPath, './empty.js' + ] + + const proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + var stdout = '' + proc.stdout.on('data', function (chunk) { + stdout += chunk + }) + + proc.stdout.on('error', function (chunk) { + stdout += chunk + }) + + proc.on('close', function (code) { + code.should.equal(0) + stdoutShouldEqual(stdout, ` + ----------|----------|----------|----------|----------|-------------------| + File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | + ----------|----------|----------|----------|----------|-------------------| + ----------|----------|----------|----------|----------|-------------------|`) + done() + }) + }) + }) + + describe('skip-full', () => { + it('does not display files with 100% statement, branch, and function coverage', (done) => { + const args = [ + bin, + '--skip-full', + process.execPath, './skip-full.js' + ] + + const proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + var stdout = '' + proc.stdout.on('data', function (chunk) { + stdout += chunk + }) + + proc.on('close', function (code) { + code.should.equal(0) + stdoutShouldEqual(stdout, ` + -----------------|----------|----------|----------|----------|-------------------| + File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | + -----------------|----------|----------|----------|----------|-------------------| + All files | 62.5 | 50 | 100 | 62.5 | | + half-covered.js | 50 | 50 | 100 | 50 | 6,7,8 | + -----------------|----------|----------|----------|----------|-------------------|`) + done() + }) + }) + }) + + describe('es-modules', () => { + it('allows reserved word when es-modules is disabled', (done) => { + const args = [ + bin, + '--cache', 'false', + '--es-modules', 'false', + process.execPath, './not-strict.js' + ] + + const proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + var stdout = '' + proc.stdout.on('data', function (chunk) { + stdout += chunk + }) + + proc.on('close', function (code) { + code.should.equal(0) + stdoutShouldEqual(stdout, ` + ---------------|----------|----------|----------|----------|-------------------| + File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | + ---------------|----------|----------|----------|----------|-------------------| + All files | 100 | 100 | 100 | 100 | | + not-strict.js | 100 | 100 | 100 | 100 | | + ---------------|----------|----------|----------|----------|-------------------|`) + done() + }) + }) + + it('forbids reserved word when es-modules is not disabled', (done) => { + const args = [ + bin, + '--cache', 'false', + '--exit-on-error', 'true', + process.execPath, './not-strict.js' + ] + + const proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + var stdout = '' + proc.stdout.on('data', function (chunk) { + stdout += chunk + }) + + var stderr = '' + proc.stderr.on('data', function (chunk) { + stderr += chunk + }) + + proc.on('close', function (code) { + code.should.equal(1) + stdoutShouldEqual(stderr, ` + Failed to instrument ${path.join(fixturesCLI, 'not-strict.js')}`) + stdoutShouldEqual(stdout, ` + ----------|----------|----------|----------|----------|-------------------| + File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | + ----------|----------|----------|----------|----------|-------------------| + All files | 0 | 0 | 0 | 0 | | + ----------|----------|----------|----------|----------|-------------------|`) + done() + }) + }) + }) + + describe('merge', () => { + it('combines multiple coverage reports', (done) => { + const args = [ + bin, + 'merge', + './merge-input' + ] + + const proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + proc.on('close', function (code) { + const mergedCoverage = require('./fixtures/cli/coverage') + // the combined reports should have 100% function + // branch and statement coverage. + mergedCoverage['/private/tmp/contrived/library.js'] + .s.should.eql({ '0': 2, '1': 1, '2': 1, '3': 2, '4': 1, '5': 1 }) + mergedCoverage['/private/tmp/contrived/library.js'] + .f.should.eql({ '0': 1, '1': 1, '2': 2 }) + mergedCoverage['/private/tmp/contrived/library.js'] + .b.should.eql({ '0': [1, 1] }) + rimraf.sync(path.resolve(fixturesCLI, 'coverage.json')) + return done() + }) + }) + + it('reports error if input directory is missing', (done) => { + const args = [ + bin, + 'merge', + './DIRECTORY_THAT_IS_MISSING' + ] + + const proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + var stderr = '' + proc.stderr.on('data', function (chunk) { + stderr += chunk + }) + + proc.on('close', function (code) { + stderr.should.match(/failed access input directory/) + return done() + }) + }) + + it('reports error if input is not a directory', (done) => { + const args = [ + bin, + 'merge', + './package.json' + ] + + const proc = spawn(process.execPath, args, { + cwd: fixturesCLI, + env: env + }) + + var stderr = '' + proc.stderr.on('data', function (chunk) { + stderr += chunk + }) + + proc.on('close', function (code) { + stderr.should.match(/was not a directory/) + return done() + }) + }) + }) + + describe('exclude-node-modules', () => { + const fixturesENM = path.resolve(__dirname, './fixtures/exclude-node-modules') + const globalArgs = [ + bin, + '--all=true', + '--cache=false', + '--per-file=true', + '--exclude-node-modules=false', + '--include=node_modules/@istanbuljs/fake-module-1/**' + ] + const spawnOpts = { + cwd: fixturesENM, + env: env + } + const noCoverageError = `ERROR: Coverage for lines (0%) does not meet threshold (90%) for ${path.join(fixturesENM, 'node_modules/@istanbuljs/fake-module-1/index.js')}\n` + + it('execute', done => { + function checkReport (code, stderr, stdout, next) { + code.should.equal(1) + stderr.should.equal(noCoverageError) + stdoutShouldEqual(stdout, ` + ----------|----------|----------|----------|----------|-------------------| + File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | + ----------|----------|----------|----------|----------|-------------------| + All files | 0 | 100 | 100 | 0 | | + index.js | 0 | 100 | 100 | 0 | 1 | + ----------|----------|----------|----------|----------|-------------------|`) + next() + } + + function executeMainCommand () { + const args = [ + ...globalArgs, + '--check-coverage=true', + process.execPath, './bin/do-nothing.js' + ] + + const proc = spawn(process.execPath, args, spawnOpts) + + var stderr = '' + proc.stderr.on('data', function (chunk) { + stderr += chunk + }) + + var stdout = '' + proc.stdout.on('data', function (chunk) { + stdout += chunk + }) + + proc.on('close', code => checkReport(code, stderr, stdout, executeReport)) + } + + function executeReport () { + const args = [ + ...globalArgs, + '--check-coverage=true', + 'report' + ] + + const proc = spawn(process.execPath, args, spawnOpts) + + var stderr = '' + proc.stderr.on('data', function (chunk) { + stderr += chunk + }) + + var stdout = '' + proc.stdout.on('data', function (chunk) { + stdout += chunk + }) + + proc.on('close', code => checkReport(code, stderr, stdout, executeCheckCoverage)) + } + + function executeCheckCoverage () { + const args = [ + ...globalArgs, + 'check-coverage' + ] + + const proc = spawn(process.execPath, args, spawnOpts) + + var stderr = '' + proc.stderr.on('data', function (chunk) { + stderr += chunk + }) + + var stdout = '' + proc.stdout.on('data', function (chunk) { + stdout += chunk + }) + + proc.on('close', code => { + code.should.equal(1) + stderr.should.equal(noCoverageError) + stdoutShouldEqual(stdout, '') + done() + }) + } + + executeMainCommand() + }) + + it('instrument', done => { + const args = [ + ...globalArgs, + 'instrument', + 'node_modules' + ] + + const proc = spawn(process.execPath, args, spawnOpts) + + var stderr = '' + proc.stderr.on('data', function (chunk) { + stderr += chunk + }) + + var stdout = '' + proc.stdout.on('data', function (chunk) { + stdout += chunk + }) + + proc.on('close', code => { + code.should.equal(0) + stderr.should.equal('') + stdout.should.match(/fake-module-1/) + stdout.should.not.match(/fake-module-2/) + done() + }) + }) + }) + + it('recursive run does not throw', done => { + const args = [ + bin, + process.execPath, + bin, + process.execPath, + bin, + process.execPath, + bin, + 'true' + ] + const proc = spawn(process.execPath, args, { + cwd: path.resolve(__dirname, './fixtures/recursive-run') + }) + + let stdio = '' + proc.stderr.on('data', chunk => { + stdio += chunk + }) + + proc.stdout.on('data', chunk => { + stdio += chunk + }) + + proc.on('close', code => { + code.should.equal(0) + stdio.should.equal('') + done() + }) + }) +}) + +function stdoutShouldEqual (stdout, expected) { + `\n${stdout}`.should.equal(`${si(expected)}\n`) +} diff --git a/test/nyc-integration.js b/test/nyc-integration.js index 58fa87dcf..54f34da9d 100644 --- a/test/nyc-integration.js +++ b/test/nyc-integration.js @@ -1,1978 +1,188 @@ -/* global describe, it, beforeEach, afterEach */ +'use strict' -const _ = require('lodash') const path = require('path') -const bin = path.resolve(__dirname, '../self-coverage/bin/nyc') -const fixturesCLI = path.resolve(__dirname, './fixtures/cli') -const fixturesHooks = path.resolve(__dirname, './fixtures/hooks') -const fixturesSourceMaps = path.resolve(__dirname, './fixtures/source-maps') -const fakebin = path.resolve(fixturesCLI, 'fakebin') -const fs = require('fs') -const glob = require('glob') -const isWindows = require('is-windows')() -const rimraf = require('rimraf') -const makeDir = require('make-dir') -const { spawn, spawnSync } = require('child_process') -const si = require('strip-indent') +const os = require('os') -require('chai').should() -require('tap').mochaGlobals() +const t = require('tap') -// beforeEach -rimraf.sync(path.resolve(fakebin, 'node')) -rimraf.sync(path.resolve(fakebin, 'npm')) -rimraf.sync(path.resolve(fixturesCLI, 'subdir', 'output-dir')) +const { fixturesCLI, runNYC, tempDirSetup, testSuccess, testFailure } = require('./helpers') -describe('the nyc cli', function () { - var env = { PATH: process.env.PATH } +const nycConfigJS = path.resolve(fixturesCLI, 'nyc-config-js') +const nycrcDir = path.resolve(fixturesCLI, 'nycrc') - describe('--include', function () { - it('can be used to limit bin to instrumenting specific files', function (done) { - var args = [bin, '--all', '--include', 'half-covered.js', process.execPath, './half-covered.js'] - - var proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', function (code) { - code.should.equal(0) - stdout.should.match(/half-covered\.js/) - stdout.should.not.match(/half-covered-failing\.js/) - stdout.should.not.match(/test\.js/) - done() - }) - }) - }) - - describe('report and check', function () { - it('should show coverage check along with report', function (done) { - // generate some coverage info - var args = [bin, '--silent', process.execPath, './half-covered.js'] - - var proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - proc.on('close', function (code) { - code.should.equal(0) - var args = [bin, 'report', '--check-coverage', '--lines=100'] - var proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - var stderr = '' - proc.stderr.on('data', function (chunk) { - stderr += chunk - }) - - proc.on('close', function (code) { - code.should.not.equal(0) - stderr.should.equal('ERROR: Coverage for lines (50%) does not meet global threshold (100%)\n') - done() - }) - }) - }) - }) - - describe('--exclude', function () { - it('should allow default exclude rules to be overridden', function (done) { - var args = [bin, '--all', '--exclude', '**/half-covered.js', process.execPath, './half-covered.js'] - - var proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', function (code) { - code.should.equal(0) - stdout.should.not.match(/half-covered\.js/) - stdout.should.match(/test\.js/) - done() - }) - }) - }) - - describe('--ignore-class-method', function () { - it('skips methods that match ignored name but still catches those that are not', function (done) { - var args = [bin, '--all', '--ignore-class-method', 'skip', process.execPath, './classes.js'] - - var proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', function (code) { - code.should.equal(0) - var classesOutput = (stdout.match(/^(.*classes\.js).*$/m) || ['no result found'])[0] - classesOutput.should.match(/6 \|/) - done() - }) - }) - }) - - describe('--check-coverage', function () { - it('fails when the expected coverage is below a threshold', function (done) { - var args = [bin, '--check-coverage', '--lines', '51', process.execPath, './half-covered.js'] - var message = 'ERROR: Coverage for lines (50%) does not meet global threshold (51%)' - - var proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - var stderr = '' - proc.stderr.on('data', function (chunk) { - stderr += chunk - }) - - proc.on('close', function (code) { - code.should.not.equal(0) - stderr.trim().should.equal(message) - done() - }) - }) - - // https://github.com/istanbuljs/nyc/issues/384 - it('fails when check-coverage command is used rather than flag', function (done) { - var args = [bin, 'check-coverage', '--lines', '51', process.execPath, './half-covered.js'] - var message = 'ERROR: Coverage for lines (50%) does not meet global threshold (51%)' - - var proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - var stderr = '' - proc.stderr.on('data', function (chunk) { - stderr += chunk - }) - - proc.on('close', function (code) { - code.should.not.equal(0) - stderr.trim().should.equal(message) - done() - }) - }) - - it('succeeds when the expected coverage is above a threshold', function (done) { - var args = [bin, '--check-coverage', '--lines', '49', process.execPath, './half-covered.js'] - - var proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - proc.on('close', function (code) { - code.should.equal(0) - done() - }) - }) - - // https://github.com/bcoe/nyc/issues/209 - it('fails in any case when the underlying test failed', function (done) { - var args = [bin, '--check-coverage', '--lines', '49', process.execPath, './half-covered-failing.js'] - - var proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - proc.on('close', function (code) { - code.should.not.equal(0) - done() - }) - }) - - it('fails when the expected file coverage is below a threshold', function (done) { - var args = [bin, '--check-coverage', '--lines', '51', '--per-file', process.execPath, './half-covered.js'] - var matcher = RegExp('ERROR: Coverage for lines \\(50%\\) does not meet threshold \\(51%\\) for .+half-covered.js') - - var proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - var stderr = '' - proc.stderr.on('data', function (chunk) { - stderr += chunk - }) - - proc.on('close', function (code) { - code.should.not.equal(0) - stderr.trim().should.match(matcher) - done() - }) - }) - }) - - // https://github.com/bcoe/nyc/issues/190 - describe('running "npm test"', function () { - it('can run "npm test" which directly invokes a test file', function (done) { - var args = [bin, 'npm', 'test'] - var directory = path.resolve(fixturesCLI, 'run-npm-test') - var proc = spawn(process.execPath, args, { - cwd: directory, - env: env - }) - - proc.on('close', function (code) { - code.should.equal(0) - done() - }) - }) - - it('can run "npm test" which indirectly invokes a test file', function (done) { - var args = [bin, 'npm', 'test'] - var directory = path.resolve(fixturesCLI, 'run-npm-test-recursive') - var proc = spawn(process.execPath, args, { - cwd: directory, - env: env - }) - - proc.on('close', function (code) { - code.should.equal(0) - done() - }) - }) - - function writeFakeNPM (shebang) { - var targetPath = path.resolve(fakebin, 'npm') - var source = fs.readFileSync(path.resolve(fakebin, 'npm-template.js')) - fs.writeFileSync(targetPath, '#!' + shebang + '\n' + source) - fs.chmodSync(targetPath, 493) // 0o755 - } - - it('can run "npm test", absolute shebang edition', function (done) { - if (isWindows) return done() - - writeFakeNPM(process.execPath) - - var args = [bin, 'npm', 'test'] - var directory = path.resolve(fixturesCLI, 'run-npm-test-recursive') - var proc = spawn(process.execPath, args, { - cwd: directory, - env: { - PATH: fakebin + ':' + env.PATH - } - }) - - proc.on('close', function (code) { - code.should.equal(0) - done() - }) - }) - - it('can run "npm test", weird bash+dirname shebang edition', function (done) { - if (isWindows) return done() - - // This string is taken verbatim from tools/install.py in Node core v5.x - writeFakeNPM('/bin/sh\n// 2>/dev/null; exec "`dirname "$0"`/node" "$0" "$@"') - fs.symlinkSync(process.execPath, path.resolve(fakebin, 'node')) - - var args = [bin, 'npm', 'test'] - var directory = path.resolve(fixturesCLI, 'run-npm-test-recursive') - var proc = spawn(process.execPath, args, { - cwd: directory, - env: { - PATH: fakebin + ':' + env.PATH - } - }) - - proc.on('close', function (code) { - code.should.equal(0) - done() - }) - }) - }) - - describe('configuration', function () { - it('passes configuration via environment variables', function (done) { - var args = [ - bin, - '--silent', - '--require=make-dir', - '--include=env.js', - '--exclude=batman.js', - '--extension=.js', - '--cache=false', - '--cache-dir=/tmp', - '--source-map=true', - process.execPath, - './env.js' - ] - var expected = { - instrumenter: './lib/instrumenters/istanbul', - silent: true, - cacheDir: '/tmp', - cache: false, - sourceMap: true - } - - var proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', function (code) { - code.should.equal(0) - var env = JSON.parse(stdout) - var config = JSON.parse(env.NYC_CONFIG, null, 2) - config.should.include(expected) - config.include.should.include('env.js') - config.exclude.should.include('batman.js') - config.extension.should.include('.js') - done() - }) - }) - - it('allows package.json configuration to be overridden with command line args', function (done) { - var args = [bin, '--reporter=text-lcov', process.execPath, './half-covered.js'] - - var proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', function (code) { - code.should.equal(0) - stdout.should.match(/SF:.*half-covered\.js/) - done() - }) - }) - - describe('nyc.config.js', function () { - var cwd = path.resolve(fixturesCLI, './nyc-config-js') - - it('loads configuration from package.json and nyc.config.js', function (done) { - var args = [bin, process.execPath, './index.js'] - - var proc = spawn(process.execPath, args, { - cwd: cwd, - env: env - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', function (code) { - code.should.equal(0) - stdout.should.match(/SF:.*index\.js/) - stdout.should.not.match(/SF:.*ignore\.js/) - stdout.should.not.match(/SF:.*nyc\.config\.js/) - stdout.should.not.match(/SF:.*nycrc-config\.js/) - done() - }) - }) - - it('loads configuration from different module rather than nyc.config.js', function (done) { - var args = [bin, '--all', '--nycrc-path', './nycrc-config.js', process.execPath, './index.js'] - - var proc = spawn(process.execPath, args, { - cwd: cwd, - env: env - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', function (code) { - // should be 1 due to coverage check - code.should.equal(1) - stdout.should.match(/SF:.*index\.js/) - stdout.should.match(/SF:.*ignore\.js/) - stdout.should.match(/SF:.*nyc\.config\.js/) - stdout.should.match(/SF:.*nycrc-config\.js/) - done() - }) - }) - - it('allows nyc.config.js configuration to be overridden with command line args', function (done) { - var args = [bin, '--all', '--exclude=foo.js', process.execPath, './index.js'] - - var proc = spawn(process.execPath, args, { - cwd: cwd, - env: env - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', function (code) { - code.should.equal(0) - stdout.should.match(/SF:.*index\.js/) - stdout.should.match(/SF:.*ignore\.js/) - stdout.should.match(/SF:.*nyc\.config\.js/) - stdout.should.match(/SF:.*nycrc-config\.js/) - done() - }) - }) - }) - - describe('.nycrc', function () { - var cwd = path.resolve(fixturesCLI, './nycrc') - - it('loads configuration from package.json and .nycrc', function (done) { - var args = [bin, process.execPath, './index.js'] - - var proc = spawn(process.execPath, args, { - cwd: cwd, - env: env - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', function (code) { - code.should.equal(0) - stdout.should.match(/SF:.*index\.js/) - stdout.should.not.match(/SF:.*ignore\.js/) - done() - }) - }) - - it('loads configuration from different file rather than .nycrc', function (done) { - var args = [bin, '--nycrc-path', './.nycrc-config.json', process.execPath, './index.js'] - - var proc = spawn(process.execPath, args, { - cwd: cwd, - env: env - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', function (code) { - // should be 1 due to coverage check - code.should.equal(1) - stdout.should.match(/SF:.*index\.js/) - stdout.should.match(/SF:.*ignore\.js/) - done() - }) - }) - - it('loads configuration from .nycrc.yml', function (done) { - var args = [bin, '--nycrc-path', './.nycrc.yml', process.execPath, './index.js'] - - var proc = spawn(process.execPath, args, { - cwd: cwd, - env: env - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', function (code) { - code.should.equal(0) - stdout.should.match(/SF:.*index\.js/) - stdout.should.not.match(/SF:.*ignore\.js/) - done() - }) - }) - - it('loads configuration from .nycrc.yaml', function (done) { - var args = [bin, '--nycrc-path', './.nycrc.yaml', process.execPath, './index.js'] - - var proc = spawn(process.execPath, args, { - cwd: cwd, - env: env - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', function (code) { - code.should.equal(0) - stdout.should.match(/SF:.*index\.js/) - stdout.should.not.match(/SF:.*ignore\.js/) - done() - }) - }) - - it('allows .nycrc configuration to be overridden with command line args', function (done) { - var args = [bin, '--exclude=foo.js', process.execPath, './index.js'] - - var proc = spawn(process.execPath, args, { - cwd: cwd, - env: env - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', function (code) { - code.should.equal(0) - stdout.should.match(/SF:.*index\.js/) - stdout.should.match(/SF:.*ignore\.js/) - done() - }) - }) - }) - }) - - describe('coverage', function () { - it('reports appropriate coverage information for es6 source files', function (done) { - var args = [bin, '--reporter=lcov', '--reporter=text', process.execPath, './es6.js'] - - var proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', function (code) { - code.should.equal(0) - // we should miss covering the appropriate lines. - stdout.should.match(/11,16,17/) - done() - }) - }) - }) - - describe('instrument', function () { - beforeEach(() => { - rimraf.sync(path.resolve(fixturesCLI, 'subdir', 'output-dir')) - }) - - describe('no output folder', function () { - it('allows a single file to be instrumented', function (done) { - var args = [bin, 'instrument', './half-covered.js'] - - var proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', function (code) { - code.should.equal(0) - stdout.should.contain(`path:${JSON.stringify(path.resolve(fixturesCLI, 'half-covered.js'))}`) - done() - }) - }) - - it('allows a directory of files to be instrumented', function (done) { - var args = [bin, 'instrument', './'] - - var proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', function (code) { - code.should.equal(0) - stdout.should.match(/half-covered\.js"/) - stdout.should.match(/half-covered-failing\.js"/) - stdout.should.not.match(/spawn\.js"/) - done() - }) - }) - - it('returns unmodified source if there is no transform', function (done) { - const args = [bin, 'instrument', './no-transform/half-covered.xjs'] - - const proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - let stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', function (code) { - code.should.equal(0) - stdout.should.contain(`var a = 0`) - done() - }) - }) - }) - - describe('output folder specified', function () { - afterEach(function () { - rimraf.sync(path.resolve(fixturesCLI, 'output')) - }) - - it('works in directories without a package.json', function (done) { - const args = [bin, 'instrument', './input-dir', './output-dir'] - - const subdir = path.resolve(fixturesCLI, 'subdir') - const proc = spawn(process.execPath, args, { - cwd: subdir, - env: env - }) - - proc.on('exit', function (code) { - code.should.equal(0) - const target = path.resolve(subdir, 'output-dir', 'index.js') - fs.readFileSync(target, 'utf8') - .should.match(/console.log\('Hello, World!'\)/) - done() - }) - }) - - it('can be configured to exit on error', function (done) { - const args = [bin, 'instrument', '--exit-on-error', './input-dir', './output-dir'] - - const subdir = path.resolve(fixturesCLI, 'subdir') - const proc = spawn(process.execPath, args, { - cwd: subdir, - env: env - }) - - proc.on('exit', function (code) { - code.should.equal(1) - done() - }) - }) - - it('allows a single file to be instrumented', function (done) { - const args = [bin, 'instrument', './half-covered.js', './output'] - - const inputPath = path.resolve(fixturesCLI, './half-covered.js') - const inputMode = fs.statSync(inputPath).mode & 0o7777 - const newMode = 0o775 - if (process.platform !== 'win32') { - fs.chmodSync(inputPath, newMode) - } - - const proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - proc.on('close', function (code) { - code.should.equal(0) - const files = fs.readdirSync(path.resolve(fixturesCLI, './output')) - files.length.should.equal(1) - files.should.include('half-covered.js') - - if (process.platform !== 'win32') { - const outputPath = path.resolve(fixturesCLI, 'output', 'half-covered.js') - const outputMode = fs.statSync(outputPath).mode & 0o7777 - outputMode.should.equal(newMode) - - fs.chmodSync(inputPath, inputMode) - } - - done() - }) - }) - - it('allows a directory of files to be instrumented', function (done) { - const args = [bin, 'instrument', './nyc-config-js', './output'] - - const proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - proc.on('close', function (code) { - code.should.equal(0) - const files = fs.readdirSync(path.resolve(fixturesCLI, './output')) - files.should.include('index.js') - files.should.include('ignore.js') - files.should.not.include('package.json') - files.should.not.include('node_modules') - const includeTarget = path.resolve(fixturesCLI, 'output', 'ignore.js') - fs.readFileSync(includeTarget, 'utf8') - .should.match(/var cov_/) - done() - }) - }) - - it('copies all files from to as well as those that have been instrumented', function (done) { - const args = [bin, 'instrument', '--complete-copy', './nyc-config-js', './output'] - - const proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - proc.on('close', function (code) { - code.should.equal(0) - const files = fs.readdirSync(path.resolve(fixturesCLI, './output')) - files.should.include('index.js') - files.should.include('ignore.js') - files.should.include('package.json') - files.should.include('node_modules') - const includeTarget = path.resolve(fixturesCLI, 'output', 'ignore.js') - fs.readFileSync(includeTarget, 'utf8') - .should.match(/var cov_/) - done() - }) - }) - - it('can instrument the project directory', function (done) { - const args = [bin, 'instrument', '.', './output'] - - const proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - proc.on('close', function (code) { - code.should.equal(0) - const files = fs.readdirSync(path.resolve(fixturesCLI, './output')) - files.should.include('args.js') - files.should.include('subdir') - done() - }) - }) - - it('allows a sub-directory of files to be instrumented', function (done) { - const args = [bin, 'instrument', './subdir/input-dir', './output'] - - const proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - proc.on('close', function (code) { - code.should.equal(0) - const files = fs.readdirSync(path.resolve(fixturesCLI, './output')) - files.should.include('index.js') - done() - }) - }) - - it('allows a subdirectory to be excluded via .nycrc file', function (done) { - const args = [bin, 'instrument', '--nycrc-path', './.instrument-nycrc', './subdir/input-dir', './output'] - - const proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - proc.on('close', function (code) { - code.should.equal(0) - const files = fs.readdirSync(path.resolve(fixturesCLI, './output')) - files.length.should.not.equal(0) - files.should.include('exclude-me') - files.should.include('node_modules') - files.should.include('index.js') - files.should.include('bad.js') - const includeTarget = path.resolve(fixturesCLI, 'output', 'index.js') - fs.readFileSync(includeTarget, 'utf8') - .should.match(/var cov_/) - const excludeTarget = path.resolve(fixturesCLI, 'output', 'exclude-me', 'index.js') - fs.readFileSync(excludeTarget, 'utf8') - .should.not.match(/var cov_/) - done() - }) - }) - - it('allows a file to be excluded', function (done) { - const args = [bin, 'instrument', '--complete-copy', '--exclude', 'exclude-me/index.js', './subdir/input-dir', './output'] - - const proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - proc.on('close', function (code) { - code.should.equal(0) - const files = fs.readdirSync(path.resolve(fixturesCLI, './output')) - files.length.should.not.equal(0) - files.should.include('exclude-me') - const excludeTarget = path.resolve(fixturesCLI, 'output', 'exclude-me', 'index.js') - fs.readFileSync(excludeTarget, 'utf8') - .should.not.match(/var cov_/) - done() - }) - }) - - it('allows specifying a single sub-directory to be included', function (done) { - const args = [bin, 'instrument', '--include', '**/include-me/**', './subdir/input-dir', './output'] - - const proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - proc.on('close', function (code) { - code.should.equal(0) - const files = fs.readdirSync(path.resolve(fixturesCLI, './output')) - files.length.should.not.equal(0) - files.should.include('include-me') - const instrumented = path.resolve(fixturesCLI, 'output', 'include-me', 'include-me.js') - fs.readFileSync(instrumented, 'utf8') - .should.match(/var cov_/) - done() - }) - }) - - it('allows a file to be excluded from an included directory', function (done) { - const args = [bin, 'instrument', '--complete-copy', '--exclude', '**/exclude-me.js', '--include', '**/include-me/**', './subdir/input-dir', './output'] - - const proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - proc.on('close', function (code) { - code.should.equal(0) - const files = fs.readdirSync(path.resolve(fixturesCLI, './output')) - files.length.should.not.equal(0) - files.should.include('include-me') - const includeMeFiles = fs.readdirSync(path.resolve(fixturesCLI, 'output', 'include-me')) - includeMeFiles.length.should.not.equal(0) - includeMeFiles.should.include('include-me.js') - includeMeFiles.should.include('exclude-me.js') - const includeTarget = path.resolve(fixturesCLI, 'output', 'include-me', 'include-me.js') - fs.readFileSync(includeTarget, 'utf8') - .should.match(/var cov_/) - const excludeTarget = path.resolve(fixturesCLI, 'output', 'exclude-me', 'index.js') - fs.readFileSync(excludeTarget, 'utf8') - .should.not.match(/var cov_/) - done() - }) - }) - - it('aborts if trying to write files in place', function (done) { - const args = [bin, 'instrument', '--delete', './', './'] - - const proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - let stderr = '' - proc.stderr.on('data', function (chunk) { - stderr += chunk - }) - - proc.on('close', function (code) { - code.should.equal(1) - stderr.should.include('nyc instrument failed: cannot instrument files in place') - done() - }) - }) - - it('aborts if trying to instrument files from outside the project root directory', function (done) { - const args = [bin, 'instrument', '--delete', '../', './'] - - const proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - let stderr = '' - proc.stderr.on('data', function (chunk) { - stderr += chunk - }) - - proc.on('close', function (code) { - code.should.equal(1) - stderr.should.include('nyc instrument failed: cannot instrument files outside of project root directory') - done() - }) - }) - - describe('es-modules', function () { - afterEach(function () { - rimraf.sync(path.resolve(fixturesCLI, './output')) - }) - - it('instruments file with `package` keyword when es-modules is disabled', function (done) { - const args = [bin, 'instrument', '--no-es-modules', './not-strict.js', './output'] - - const proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - proc.on('close', function (code) { - code.should.equal(0) - const subdirExists = fs.existsSync(path.resolve(fixturesCLI, './output')) - subdirExists.should.equal(true) - const files = fs.readdirSync(path.resolve(fixturesCLI, './output')) - files.should.include('not-strict.js') - done() - }) - }) - - it('fails on file with `package` keyword when es-modules is enabled', function (done) { - const args = [bin, 'instrument', '--exit-on-error', './not-strict.js', './output'] - - const proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - let stderr = '' - proc.stderr.on('data', function (chunk) { - stderr += chunk - }) - - proc.on('close', function (code) { - code.should.equal(1) - stdoutShouldEqual(stderr, ` - Failed to instrument ${path.resolve(fixturesCLI, 'not-strict.js')}`) - const subdirExists = fs.existsSync(path.resolve(fixturesCLI, './output')) - subdirExists.should.equal(false) - done() - }) - }) - }) - - describe('delete', function () { - beforeEach(function () { - makeDir.sync(path.resolve(fixturesCLI, 'output', 'removed-by-clean')) - }) - - it('cleans the output directory if `--delete` is specified', function (done) { - const args = [bin, 'instrument', '--delete', 'true', './subdir/input-dir', './output'] - - const proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - proc.on('close', function (code) { - code.should.equal(0) - const subdirExists = fs.existsSync(path.resolve(fixturesCLI, './output')) - subdirExists.should.equal(true) - const files = fs.readdirSync(path.resolve(fixturesCLI, './output')) - files.should.not.include('removed-by-clean') - files.should.include('exclude-me') - done() - }) - }) - - it('does not clean the output directory by default', function (done) { - const args = [bin, 'instrument', './subdir/input-dir', './output'] - - const proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - proc.on('close', function (code) { - code.should.equal(0) - const subdirExists = fs.existsSync(path.resolve(fixturesCLI, './output')) - subdirExists.should.equal(true) - const files = fs.readdirSync(path.resolve(fixturesCLI, './output')) - files.should.include('removed-by-clean') - done() - }) - }) - - it('aborts if trying to clean process.cwd()', function (done) { - const args = [bin, 'instrument', '--delete', './src', './'] - - const proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - let stderr = '' - proc.stderr.on('data', function (chunk) { - stderr += chunk - }) - - proc.on('close', function (code) { - code.should.equal(1) - stderr.should.include('nyc instrument failed: attempt to delete') - done() - }) - }) - - it('aborts if trying to clean outside working directory', function (done) { - const args = [bin, 'instrument', '--delete', './', '../'] - - const proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - let stderr = '' - proc.stderr.on('data', function (chunk) { - stderr += chunk - }) - - proc.on('close', function (code) { - code.should.equal(1) - stderr.should.include('nyc instrument failed: attempt to delete') - done() - }) - }) - }) - }) - }) - - describe('hooks', function () { - it('provides coverage for requireJS and AMD modules', function (done) { - var args = [bin, '--hook-run-in-this-context', '--hook-require=false', process.execPath, './index.js'] - - var proc = spawn(process.execPath, args, { - cwd: fixturesHooks, - env: process.env - }) - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - proc.on('close', function (code) { - code.should.equal(0) - stdout.should.match(/ipsum\.js/) - stdout.should.match(/lorem\.js/) - done() - }) - }) - }) - - it('help shows to stderr when main command doesn\'t know what to do', () => { - const opts = { - cwd: fixturesCLI, - env, - encoding: 'utf8' - } - - const help = spawnSync(process.execPath, [bin, '--help'], opts) - const unknown = spawnSync(process.execPath, [bin], opts) - help.status.should.equal(0) - unknown.status.should.equal(1) - help.stderr.should.equal('') - unknown.stdout.should.equal('') - help.stdout.should.not.equal('') - help.stdout.should.equal(unknown.stderr) - }) - - describe('args', function () { - it('does not interpret args intended for instrumented bin', function (done) { - var args = [bin, '--silent', process.execPath, 'args.js', '--help', '--version'] - - var proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', function (code) { - code.should.equal(0) - var args = JSON.parse(stdout) - args.should.include('--help') - args.should.include('--version') - args.should.not.include('--silent') - done() - }) - }) - - it('interprets first args after -- as Node.js execArgv', function (done) { - var args = [bin, '--', '--expose-gc', path.resolve(fixturesCLI, 'gc.js')] - - var proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - var stdout = '' - proc.stdout.setEncoding('utf8') - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', function (code) { - code.should.equal(0) - stdout.should.include('still running') - done() - }) - }) - }) - - describe('--show-process-tree', function () { - it('displays a tree of spawned processes', function (done) { - var args = [bin, '--show-process-tree', process.execPath, 'selfspawn-fibonacci.js', '5'] - - var proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - var stdout = '' - proc.stdout.setEncoding('utf8') - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', function (code) { - code.should.equal(0) - stdout.should.match(new RegExp( - 'nyc\n' + - '└─┬.*selfspawn-fibonacci.js 5\n' + - ' │.* % Lines\n' + - ' ├─┬.*selfspawn-fibonacci.js 4\n' + - ' │ │.* % Lines\n' + - ' │ ├─┬.*selfspawn-fibonacci.js 3\n' + - ' │ │ │.* % Lines\n' + - ' │ │ ├──.*selfspawn-fibonacci.js 2\n' + - ' │ │ │.* % Lines\n' + - ' │ │ └──.*selfspawn-fibonacci.js 1\n' + - ' │ │ .* % Lines\n' + - ' │ └──.*selfspawn-fibonacci.js 2\n' + - ' │ .* % Lines\n' + - ' └─┬.*selfspawn-fibonacci.js 3\n' + - ' │.* % Lines\n' + - ' ├──.*selfspawn-fibonacci.js 2\n' + - ' │.* % Lines\n' + - ' └──.*selfspawn-fibonacci.js 1\n' + - ' .* % Lines\n' - )) - done() - }) - }) - }) - - describe('--temp-dir', function () { - beforeEach(() => { - rimraf.sync(path.resolve(fixturesCLI, '.nyc_output')) - rimraf.sync(path.resolve(fixturesCLI, '.temp_directory')) - rimraf.sync(path.resolve(fixturesCLI, '.temp_dir')) - }) - - it('creates the default \'tempDir\' when none is specified', function (done) { - var args = [bin, process.execPath, './half-covered.js'] - - var proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - proc.on('close', function (code) { - code.should.equal(0) - var tempFiles = fs.readdirSync(path.resolve(fixturesCLI, '.nyc_output')) - tempFiles.length.should.equal(2) // the coverage file, and processinfo - var cliFiles = fs.readdirSync(path.resolve(fixturesCLI)) - cliFiles.should.include('.nyc_output') - cliFiles.should.not.include('.temp_dir') - cliFiles.should.not.include('.temp_directory') - done() - }) - }) - - it('prefers \'tempDirectory\' to \'tempDir\'', function (done) { - var args = [bin, '--tempDirectory', '.temp_directory', '--tempDir', '.temp_dir', process.execPath, './half-covered.js'] - - var proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - proc.on('exit', function (code) { - code.should.equal(0) - var tempFiles = fs.readdirSync(path.resolve(fixturesCLI, '.temp_directory')) - tempFiles.length.should.equal(2) - var cliFiles = fs.readdirSync(path.resolve(fixturesCLI)) - cliFiles.should.not.include('.nyc_output') - cliFiles.should.not.include('.temp_dir') - cliFiles.should.include('.temp_directory') - done() - }) - }) - - it('uses the \'tempDir\' option if \'tempDirectory\' is not set', function (done) { - var args = [bin, '--tempDir', '.temp_dir', process.execPath, './half-covered.js'] - - var proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - proc.on('exit', function (code) { - code.should.equal(0) - var tempFiles = fs.readdirSync(path.resolve(fixturesCLI, '.temp_dir')) - tempFiles.length.should.equal(2) - var cliFiles = fs.readdirSync(path.resolve(fixturesCLI)) - cliFiles.should.not.include('.nyc_output') - cliFiles.should.include('.temp_dir') - cliFiles.should.not.include('.temp_directory') - rimraf.sync(path.resolve(fixturesCLI, '.temp_dir')) - done() - }) - }) - }) - - it('handles --clean / --no-clean properly', () => { - rimraf.sync(path.resolve(fixturesCLI, '.nyc_output')) - const args = (doClean, arg) => [ - bin, - doClean ? '--clean' : '--no-clean', - process.execPath, - './by-arg2.js', - arg - ] - const opts = { - cwd: fixturesCLI, - env: env, - encoding: 'utf8' - } - - const proc1 = spawnSync(process.execPath, args(true, '1'), opts) - proc1.status.should.equal(0) - stdoutShouldEqual(proc1.stdout, ` - 1 - ------------|----------|----------|----------|----------|-------------------| - File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | - ------------|----------|----------|----------|----------|-------------------| - All files | 50 | 25 | 100 | 50 | | - by-arg2.js | 50 | 25 | 100 | 50 | 4,5,7 | - ------------|----------|----------|----------|----------|-------------------|` - ) - proc1.stderr.should.equal('') - - const proc2 = spawnSync(process.execPath, args(false, '2'), opts) - proc2.status.should.equal(0) - stdoutShouldEqual(proc2.stdout, ` - 2 - ------------|----------|----------|----------|----------|-------------------| - File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | - ------------|----------|----------|----------|----------|-------------------| - All files | 83.33 | 75 | 100 | 83.33 | | - by-arg2.js | 83.33 | 75 | 100 | 83.33 | 7 | - ------------|----------|----------|----------|----------|-------------------|` - ) - proc2.stderr.should.equal('') - }) - - describe('noop instrumenter', function () { - it('setting instrument to "false" configures noop instrumenter', function (done) { - var args = [ - bin, - '--silent', - '--no-instrument', - '--no-source-map', - process.execPath, - './env.js' - ] - var expected = { - silent: true, - instrument: false, - sourceMap: false, - instrumenter: './lib/instrumenters/noop' - } - - var proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', function (code) { - code.should.equal(0) - var env = JSON.parse(stdout) - var config = JSON.parse(env.NYC_CONFIG, null, 2) - config.should.include(expected) - done() - }) - }) - - describe('--all', function () { - it('extracts coverage headers from unexecuted files', function (done) { - var nycOutput = path.resolve(fixturesCLI, '.nyc_output') - rimraf.sync(nycOutput) - - var args = [ - bin, - '--all', - '--silent', - '--no-instrument', - '--no-source-map', - process.execPath, - // any file other than external-instrument.js, which we - // want to ensure has its header loaded. - './env.js' - ] - - var proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - proc.on('close', function (code) { - code.should.equal(0) - glob(nycOutput + '/*.json', function (_err, files) { - // we should have extracted the coverage header from external-instrumenter.js. - var coverage = {} - files.forEach(function (file) { - _.assign(coverage, JSON.parse( - fs.readFileSync(file, 'utf-8') - )) - }) - Object.keys(coverage).should.include('./external-instrumenter.js') - - // we should not have executed file, so all counts sould be 0. - var sum = 0 - Object.keys(coverage['./external-instrumenter.js'].s).forEach(function (k) { - sum += coverage['./external-instrumenter.js'].s[k] - }) - sum.should.equal(0) - - return done() - }) - }) - }) - }) - }) - - it('allows an alternative cache folder to be specified', function (done) { - var args = [bin, '--cache-dir=./foo-cache', '--cache=true', process.execPath, './half-covered.js'] - - var proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - proc.on('close', function (code) { - code.should.equal(0) - // we should have created ./foo-cache rather - // than the default ./node_modules/.cache. - fs.readdirSync(path.resolve( - fixturesCLI, './foo-cache' - )).length.should.equal(1) - rimraf.sync(path.resolve(fixturesCLI, 'foo-cache')) - done() - }) - }) - - // see: https://github.com/istanbuljs/nyc/issues/563 - it('does not create .cache folder if cache is "false"', function (done) { - var args = [bin, '--cache=false', process.execPath, './index.js'] - - var proc = spawn(process.execPath, args, { - cwd: process.cwd(), - env: env - }) +if (process.env.TAP_SNAPSHOT !== '1') { + t.jobs = os.cpus().length +} - rimraf.sync('./node_modules/.cache') +tempDirSetup(t, __filename) + +t.test('--include can be used to limit bin to instrumenting specific files', t => testSuccess(t, { + args: ['--all', '--include', 'half-covered.js', process.execPath, './half-covered.js'] +})) + +t.test('--exclude should allow default exclude rules to be overridden', t => testSuccess(t, { + args: [ + '--all', + '--exclude=**/half-covered.js', + '--exclude=**/coverage', + process.execPath, + './half-covered.js' + ] +})) + +t.test('report and check should show coverage check along with report', t => { + return testSuccess(t, { + args: ['--silent', process.execPath, './half-covered.js'] + }).then(() => testFailure(t, { + args: ['report', '--check-coverage', '--lines=100'] + })) +}) - proc.on('close', function (code) { - code.should.equal(0) - fs.existsSync('./node_modules/.cache').should.equal(false) - done() - }) - }) +t.test('--ignore-class-method skips methods that match ignored name but still catches those that are not', t => testSuccess(t, { + args: ['--all', '--ignore-class-method', 'skip', process.execPath, './classes.js'] +})) + +t.test('--check-coverage fails when the expected coverage is below a threshold', t => testFailure(t, { + args: ['--check-coverage', '--lines', '51', process.execPath, './half-covered.js'] +})) + +// https://github.com/istanbuljs/nyc/issues/384 +t.test('--check-coverage fails when check-coverage command is used rather than flag', t => { + return testSuccess(t, { + args: [process.execPath, './half-covered.js'] + }).then(() => testFailure(t, { + args: ['check-coverage', '--lines', '51', process.execPath, './half-covered.js'] + })) +}) - it('allows alternative high and low watermarks to be configured', function (done) { - var args = [ - bin, - '--watermarks.lines=90', - '--watermarks.lines=100', - '--watermarks.statements=30', - '--watermarks.statements=40', - '--cache=true', +t.test('--check-coverage succeeds when the expected coverage is above a threshold', t => testSuccess(t, { + args: ['--check-coverage', '--lines', '49', process.execPath, './half-covered.js'] +})) + +// https://github.com/bcoe/nyc/issues/209 +t.test('--check-coverage fails in any case when the underlying test failed', t => testFailure(t, { + args: ['--check-coverage', '--lines', '49', process.execPath, './half-covered-failing.js'] +})) + +t.test('--check-coverage fails when the expected file coverage is below a threshold', t => testFailure(t, { + args: ['--check-coverage', '--lines', '51', '--per-file', process.execPath, './half-covered.js'] +})) + +t.test('passes configuration via environment variables', t => { + return runNYC({ + tempDir: t.tempDir, + leavePathSep: true, + args: [ + '--silent', + '--require=make-dir', + '--include=env.js', + '--exclude=batman.js', + '--extension=.js', + '--cache=false', + '--cache-dir=/tmp', + '--source-map=true', process.execPath, - './half-covered.js' + './env.js' ] - - var proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: { - PATH: process.env.PATH, - FORCE_COLOR: true - } - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', function (code) { - code.should.equal(0) - // 50% line coverage is below our low watermark (so it's red). - stdout.should.match(/\[31;1m\W+50\W+/) - // 50% statement coverage is above our high-watermark (so it's green). - stdout.should.match(/\[32;1m\W+50\W+/) - done() - }) - }) - - // the following tests exercise nyc's behavior around source-maps - // that have been included with pre-instrumented files. Perhaps, as an - // example, unit tests are being run against minified JavaScript. - // --exclude-after-remap will likely need to be set to false when - // using nyc with this type of configuration. - describe('source-maps', () => { - describe('--all', () => { - it('includes files with both .map files and inline source-maps', (done) => { - const args = [ - bin, - '--all', - '--cache', 'false', - '--exclude-after-remap', 'false', - '--exclude', 'original', - process.execPath, './instrumented/s1.min.js' - ] - - const proc = spawn(process.execPath, args, { - cwd: fixturesSourceMaps, - env: env - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', function (code) { - code.should.equal(0) - stdoutShouldEqual(stdout, ` - ----------|----------|----------|----------|----------|-------------------| - File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | - ----------|----------|----------|----------|----------|-------------------| - All files | 44.44 | 100 | 33.33 | 44.44 | | - s1.js | 80 | 100 | 50 | 80 | 7 | - s2.js | 0 | 100 | 0 | 0 | 1,2,4,6 | - ----------|----------|----------|----------|----------|-------------------|` - ) - done() - }) - }) - - it('uses source-maps to exclude original sources from reports', (done) => { - const args = [ - bin, - '--all', - '--cache', 'false', - '--exclude', 'original/s1.js', - process.execPath, './instrumented/s1.min.js' - ] - - const proc = spawn(process.execPath, args, { - cwd: fixturesSourceMaps, - env: env - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', function (code) { - code.should.equal(0) - stdoutShouldEqual(stdout, ` - ----------|----------|----------|----------|----------|-------------------| - File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | - ----------|----------|----------|----------|----------|-------------------| - All files | 0 | 100 | 0 | 0 | | - s2.js | 0 | 100 | 0 | 0 | 1,2,4,6 | - ----------|----------|----------|----------|----------|-------------------|` - ) - done() - }) - }) - }) - - describe('.map file', () => { - it('appropriately instruments file with corresponding .map file', (done) => { - const args = [ - bin, - '--cache', 'false', - '--exclude-after-remap', 'false', - '--exclude', 'original', - process.execPath, './instrumented/s1.min.js' - ] - - const proc = spawn(process.execPath, args, { - cwd: fixturesSourceMaps, - env: env - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', function (code) { - code.should.equal(0) - stdoutShouldEqual(stdout, ` - ----------|----------|----------|----------|----------|-------------------| - File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | - ----------|----------|----------|----------|----------|-------------------| - All files | 80 | 100 | 50 | 80 | | - s1.js | 80 | 100 | 50 | 80 | 7 | - ----------|----------|----------|----------|----------|-------------------|`) - done() - }) - }) - }) - - describe('inline', () => { - it('appropriately instruments a file with an inline source-map', (done) => { - const args = [ - bin, - '--cache', 'false', - '--exclude-after-remap', 'false', - '--exclude', 'original', - process.execPath, './instrumented/s2.min.js' - ] - - const proc = spawn(process.execPath, args, { - cwd: fixturesSourceMaps, - env: env - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', function (code) { - code.should.equal(0) - stdoutShouldEqual(stdout, ` - ----------|----------|----------|----------|----------|-------------------| - File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | - ----------|----------|----------|----------|----------|-------------------| - All files | 100 | 100 | 100 | 100 | | - s2.js | 100 | 100 | 100 | 100 | | - ----------|----------|----------|----------|----------|-------------------|`) - done() - }) - }) - }) - }) - - describe('skip-empty', () => { - it('does not display 0-line files in coverage output', (done) => { - const args = [ - bin, - '--cache', 'false', - '--skip-empty', 'true', - process.execPath, './empty.js' - ] - - const proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.stdout.on('error', function (chunk) { - stdout += chunk - }) - - proc.on('close', function (code) { - code.should.equal(0) - stdoutShouldEqual(stdout, ` - ----------|----------|----------|----------|----------|-------------------| - File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | - ----------|----------|----------|----------|----------|-------------------| - ----------|----------|----------|----------|----------|-------------------|`) - done() - }) - }) - }) - - describe('skip-full', () => { - it('does not display files with 100% statement, branch, and function coverage', (done) => { - const args = [ - bin, - '--skip-full', - process.execPath, './skip-full.js' - ] - - const proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', function (code) { - code.should.equal(0) - stdoutShouldEqual(stdout, ` - -----------------|----------|----------|----------|----------|-------------------| - File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | - -----------------|----------|----------|----------|----------|-------------------| - All files | 62.5 | 50 | 100 | 62.5 | | - half-covered.js | 50 | 50 | 100 | 50 | 6,7,8 | - -----------------|----------|----------|----------|----------|-------------------|`) - done() - }) - }) - }) - - describe('es-modules', () => { - it('allows reserved word when es-modules is disabled', (done) => { - const args = [ - bin, - '--cache', 'false', - '--es-modules', 'false', - process.execPath, './not-strict.js' - ] - - const proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', function (code) { - code.should.equal(0) - stdoutShouldEqual(stdout, ` - ---------------|----------|----------|----------|----------|-------------------| - File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | - ---------------|----------|----------|----------|----------|-------------------| - All files | 100 | 100 | 100 | 100 | | - not-strict.js | 100 | 100 | 100 | 100 | | - ---------------|----------|----------|----------|----------|-------------------|`) - done() - }) - }) - - it('forbids reserved word when es-modules is not disabled', (done) => { - const args = [ - bin, - '--cache', 'false', - '--exit-on-error', 'true', - process.execPath, './not-strict.js' - ] - - const proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - var stderr = '' - proc.stderr.on('data', function (chunk) { - stderr += chunk - }) - - proc.on('close', function (code) { - code.should.equal(1) - stdoutShouldEqual(stderr, ` - Failed to instrument ${path.join(fixturesCLI, 'not-strict.js')}`) - stdoutShouldEqual(stdout, ` - ----------|----------|----------|----------|----------|-------------------| - File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | - ----------|----------|----------|----------|----------|-------------------| - All files | 0 | 0 | 0 | 0 | | - ----------|----------|----------|----------|----------|-------------------|`) - done() - }) - }) - }) - - describe('merge', () => { - it('combines multiple coverage reports', (done) => { - const args = [ - bin, - 'merge', - './merge-input' - ] - - const proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - proc.on('close', function (code) { - const mergedCoverage = require('./fixtures/cli/coverage') - // the combined reports should have 100% function - // branch and statement coverage. - mergedCoverage['/private/tmp/contrived/library.js'] - .s.should.eql({ '0': 2, '1': 1, '2': 1, '3': 2, '4': 1, '5': 1 }) - mergedCoverage['/private/tmp/contrived/library.js'] - .f.should.eql({ '0': 1, '1': 1, '2': 2 }) - mergedCoverage['/private/tmp/contrived/library.js'] - .b.should.eql({ '0': [1, 1] }) - rimraf.sync(path.resolve(fixturesCLI, 'coverage.json')) - return done() - }) - }) - - it('reports error if input directory is missing', (done) => { - const args = [ - bin, - 'merge', - './DIRECTORY_THAT_IS_MISSING' - ] - - const proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - var stderr = '' - proc.stderr.on('data', function (chunk) { - stderr += chunk - }) - - proc.on('close', function (code) { - stderr.should.match(/failed access input directory/) - return done() - }) - }) - - it('reports error if input is not a directory', (done) => { - const args = [ - bin, - 'merge', - './package.json' - ] - - const proc = spawn(process.execPath, args, { - cwd: fixturesCLI, - env: env - }) - - var stderr = '' - proc.stderr.on('data', function (chunk) { - stderr += chunk - }) - - proc.on('close', function (code) { - stderr.should.match(/was not a directory/) - return done() - }) - }) - }) - - describe('exclude-node-modules', () => { - const fixturesENM = path.resolve(__dirname, './fixtures/exclude-node-modules') - const globalArgs = [ - bin, - '--all=true', - '--cache=false', - '--per-file=true', - '--exclude-node-modules=false', - '--include=node_modules/@istanbuljs/fake-module-1/**' + }).then(({ stdout, stderr, status }) => { + const checkOptions = [ + 'instrumenter', + 'silent', + 'cacheDir', + 'cache', + 'sourceMap', + 'require', + 'include', + 'exclude', + 'extension' ] - const spawnOpts = { - cwd: fixturesENM, - env: env - } - const noCoverageError = `ERROR: Coverage for lines (0%) does not meet threshold (90%) for ${path.join(fixturesENM, 'node_modules/@istanbuljs/fake-module-1/index.js')}\n` - - it('execute', done => { - function checkReport (code, stderr, stdout, next) { - code.should.equal(1) - stderr.should.equal(noCoverageError) - stdoutShouldEqual(stdout, ` - ----------|----------|----------|----------|----------|-------------------| - File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | - ----------|----------|----------|----------|----------|-------------------| - All files | 0 | 100 | 100 | 0 | | - index.js | 0 | 100 | 100 | 0 | 1 | - ----------|----------|----------|----------|----------|-------------------|`) - next() - } - - function executeMainCommand () { - const args = [ - ...globalArgs, - '--check-coverage=true', - process.execPath, './bin/do-nothing.js' - ] - - const proc = spawn(process.execPath, args, spawnOpts) - - var stderr = '' - proc.stderr.on('data', function (chunk) { - stderr += chunk - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', code => checkReport(code, stderr, stdout, executeReport)) - } - - function executeReport () { - const args = [ - ...globalArgs, - '--check-coverage=true', - 'report' - ] - - const proc = spawn(process.execPath, args, spawnOpts) - - var stderr = '' - proc.stderr.on('data', function (chunk) { - stderr += chunk - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', code => checkReport(code, stderr, stdout, executeCheckCoverage)) - } - - function executeCheckCoverage () { - const args = [ - ...globalArgs, - 'check-coverage' - ] - const proc = spawn(process.execPath, args, spawnOpts) - - var stderr = '' - proc.stderr.on('data', function (chunk) { - stderr += chunk - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', code => { - code.should.equal(1) - stderr.should.equal(noCoverageError) - stdoutShouldEqual(stdout, '') - done() - }) - } - - executeMainCommand() + const { NYC_CONFIG } = JSON.parse(stdout) + const config = JSON.parse(NYC_CONFIG, (key, value) => { + return key === '' || checkOptions.includes(key) ? value : undefined }) - it('instrument', done => { - const args = [ - ...globalArgs, - 'instrument', - 'node_modules' - ] - - const proc = spawn(process.execPath, args, spawnOpts) - - var stderr = '' - proc.stderr.on('data', function (chunk) { - stderr += chunk - }) - - var stdout = '' - proc.stdout.on('data', function (chunk) { - stdout += chunk - }) - - proc.on('close', code => { - code.should.equal(0) - stderr.should.equal('') - stdout.should.match(/fake-module-1/) - stdout.should.not.match(/fake-module-2/) - done() - }) - }) + t.is(status, 0) + t.is(stderr, '') + t.matchSnapshot(config) }) +}) - it('recursive run does not throw', done => { - const args = [ - bin, - process.execPath, - bin, - process.execPath, - bin, - process.execPath, - bin, - 'true' - ] - const proc = spawn(process.execPath, args, { - cwd: path.resolve(__dirname, './fixtures/recursive-run') - }) - - let stdio = '' - proc.stderr.on('data', chunk => { - stdio += chunk - }) - - proc.stdout.on('data', chunk => { - stdio += chunk - }) - - proc.on('close', code => { - code.should.equal(0) - stdio.should.equal('') - done() - }) +t.test('allows package.json configuration to be overridden with command line args', t => testSuccess(t, { + args: ['--reporter=text-lcov', process.execPath, './half-covered.js'] +})) + +t.test('loads configuration from package.json and nyc.config.js', t => testSuccess(t, { + args: [process.execPath, './index.js'], + cwd: nycConfigJS +})) + +t.test('loads configuration from different module rather than nyc.config.js', t => testFailure(t, { + args: ['--all', '--nycrc-path', './nycrc-config.js', process.execPath, './index.js'], + cwd: nycConfigJS +})) + +t.test('allows nyc.config.js configuration to be overridden with command line args', t => testSuccess(t, { + args: ['--all', '--exclude=foo.js', process.execPath, './index.js'], + cwd: nycConfigJS +})) + +t.test('loads configuration from package.json and .nycrc', t => testSuccess(t, { + args: [process.execPath, './index.js'], + cwd: nycrcDir +})) + +t.test('loads configuration from different file rather than .nycrc', t => testFailure(t, { + args: ['--nycrc-path', './.nycrc-config.json', process.execPath, './index.js'], + cwd: nycrcDir +})) + +t.test('loads configuration from .nycrc.yml', t => testSuccess(t, { + args: ['--nycrc-path', './.nycrc.yml', process.execPath, './index.js'], + cwd: nycrcDir +})) + +t.test('loads configuration from .nycrc.yaml', t => testSuccess(t, { + args: ['--nycrc-path', './.nycrc.yaml', process.execPath, './index.js'], + cwd: nycrcDir +})) + +t.test('allows .nycrc configuration to be overridden with command line args', t => testSuccess(t, { + args: ['--exclude=foo.js', process.execPath, './index.js'], + cwd: nycrcDir +})) + +t.test('reports appropriate coverage information for es6 source files', t => testSuccess(t, { + args: ['--reporter=lcov', '--reporter=text', process.execPath, './es6.js'] +})) + +t.test('hooks provide coverage for requireJS and AMD modules', t => testSuccess(t, { + args: [ + /* This effectively excludes ./index.js, normalizing results before/after node.js 11.11.0 */ + '--include=lib/**', + '--hook-run-in-this-context', + '--hook-require=false', + process.execPath, + './index.js' + ], + cwd: path.resolve(__dirname, './fixtures/hooks') +})) + +t.test('does not interpret args intended for instrumented bin', t => { + return runNYC({ + args: ['--silent', process.execPath, 'args.js', '--help', '--version'], + leavePathSep: true + }).then(({ status, stderr, stdout }) => { + t.is(status, 0) + t.is(stderr, '') + t.matchSnapshot(JSON.parse(stdout).slice(2)) }) }) -function stdoutShouldEqual (stdout, expected) { - `\n${stdout}`.should.equal(`${si(expected)}\n`) -} +t.test('interprets first args after -- as Node.js execArgv', t => testSuccess(t, { + args: ['--', '--expose-gc', path.resolve(fixturesCLI, 'gc.js')] +})) + +t.test('--show-process-tree displays a tree of spawned processes', t => testSuccess(t, { + args: ['--show-process-tree', process.execPath, 'selfspawn-fibonacci.js', '5'] +}))