Repository: istanbuljs/nyc
Pull request #997 (2019-02-22 08:48:08)

test: consolidate on tap test runner, stop running build step
In this pull request I've refactored tests to stop running a build step. to facilitate this there's now a helper called `reset-state.js` that resets global state between test runs.

I've also opted to move us back to one test runner (tap) now that this consolidation has happened. Here's how the tests are laid out now:

`test/lib/reset-state.js`, called between test runs to reset state.
`test/nyc-index.js`, tests mainly for methods in `index.js`.
`test/nyc-integration.js`, CLI integration tests (@JaKXz after we land this, I think this would be a great candidate for using snapshots like we did with `c8`).
`test/process-args.js`, tests for the process-args.js helper.

TODO:

- [x] ~coverage for process-args.js is missing for some reason~ I'm not sure how this could have ever worked because we don't instrument process-args, we will have coverage for this file once we switch our coverage approach.

Note:

I've commented out the test:

`it('should include \'node_modules\' using exclude patterns')`

which also seems to be failing on `master`, CC: @AndrewFinlay.

Future Work:

Once we land this, I'd like to switch us to using `c8` shortly afterwards, and eliminate the other wonky build step we have in the codebase.

It would also be awesome to switch to snapshots for `test-integration.js`.



coreyfarrell: > I've commented out the test:
>
> `it('should include \'node_modules\' using exclude patterns')`
>
> which also seems to be failing on `master`, CC: @AndrewFinlay.

I just posted https://github.com/istanbuljs/nyc/commits/ci-test with a revert of `feat: add support to exclude files on coverage report generation (#982)`.  @AndrewFinlay was correct that his PR started failing once #982 was merged because travis-ci was testing the result of his branch merged with master.

Patch:
diff --git a/package-lock.json b/package-lock.json
index 10f0767..8ffc3f5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -241,12 +241,6 @@
       "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
       "dev": true
     },
-    "ast-types": {
-      "version": "0.8.15",
-      "resolved": "http://registry.npmjs.org/ast-types/-/ast-types-0.8.15.tgz",
-      "integrity": "sha1-ju8IJ/BN/w7IhXupJavj/qYZTlI=",
-      "dev": true
-    },
     "async": {
       "version": "2.6.2",
       "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz",
@@ -367,12 +361,6 @@
       "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==",
       "dev": true
     },
-    "browser-stdout": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
-      "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
-      "dev": true
-    },
     "buffer-from": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
@@ -1989,7 +1977,7 @@
         },
         "load-json-file": {
           "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
+          "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
           "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=",
           "dev": true,
           "requires": {
@@ -2352,16 +2340,6 @@
       "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
       "dev": true
     },
-    "forking-tap": {
-      "version": "0.1.1",
-      "resolved": "https://registry.npmjs.org/forking-tap/-/forking-tap-0.1.1.tgz",
-      "integrity": "sha1-1h0sMgrv7+1JEPTOAbCyrtluqx8=",
-      "dev": true,
-      "requires": {
-        "inline-source-map-comment": "^1.0.5",
-        "recast": "^0.10.39"
-      }
-    },
     "form-data": {
       "version": "2.3.3",
       "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
@@ -2786,12 +2764,6 @@
         "is-stream": "^1.0.1"
       }
     },
-    "he": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
-      "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=",
-      "dev": true
-    },
     "hosted-git-info": {
       "version": "2.7.1",
       "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz",
@@ -2854,67 +2826,6 @@
       "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
       "dev": true
     },
-    "inline-source-map-comment": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/inline-source-map-comment/-/inline-source-map-comment-1.0.5.tgz",
-      "integrity": "sha1-UKikTCp5DfrEQbXJTszVRiY1+vY=",
-      "dev": true,
-      "requires": {
-        "chalk": "^1.0.0",
-        "get-stdin": "^4.0.1",
-        "minimist": "^1.1.1",
-        "sum-up": "^1.0.1",
-        "xtend": "^4.0.0"
-      },
-      "dependencies": {
-        "ansi-regex": {
-          "version": "2.1.1",
-          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
-          "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
-          "dev": true
-        },
-        "ansi-styles": {
-          "version": "2.2.1",
-          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
-          "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
-          "dev": true
-        },
-        "chalk": {
-          "version": "1.1.3",
-          "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
-          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
-          "dev": true,
-          "requires": {
-            "ansi-styles": "^2.2.1",
-            "escape-string-regexp": "^1.0.2",
-            "has-ansi": "^2.0.0",
-            "strip-ansi": "^3.0.0",
-            "supports-color": "^2.0.0"
-          }
-        },
-        "minimist": {
-          "version": "1.2.0",
-          "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
-          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
-          "dev": true
-        },
-        "strip-ansi": {
-          "version": "3.0.1",
-          "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
-          "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
-          "dev": true,
-          "requires": {
-            "ansi-regex": "^2.0.0"
-          }
-        },
-        "supports-color": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
-          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
-          "dev": true
-        }
-      }
-    },
     "inquirer": {
       "version": "5.2.0",
       "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz",
@@ -2946,14 +2857,6 @@
       "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
       "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
     },
-    "is-builtin-module": {
-      "version": "1.0.0",
-      "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
-      "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=",
-      "requires": {
-        "builtin-modules": "^1.0.0"
-      }
-    },
     "is-callable": {
       "version": "1.1.4",
       "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz",
@@ -3572,71 +3475,6 @@
         }
       }
     },
-    "mocha": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz",
-      "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==",
-      "dev": true,
-      "requires": {
-        "browser-stdout": "1.3.1",
-        "commander": "2.15.1",
-        "debug": "3.1.0",
-        "diff": "3.5.0",
-        "escape-string-regexp": "1.0.5",
-        "glob": "7.1.2",
-        "growl": "1.10.5",
-        "he": "1.1.1",
-        "minimatch": "3.0.4",
-        "mkdirp": "0.5.1",
-        "supports-color": "5.4.0"
-      },
-      "dependencies": {
-        "commander": {
-          "version": "2.15.1",
-          "resolved": "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
-          "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
-          "dev": true
-        },
-        "debug": {
-          "version": "3.1.0",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
-          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
-          "dev": true,
-          "requires": {
-            "ms": "2.0.0"
-          }
-        },
-        "glob": {
-          "version": "7.1.2",
-          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
-          "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
-          "dev": true,
-          "requires": {
-            "fs.realpath": "^1.0.0",
-            "inflight": "^1.0.4",
-            "inherits": "2",
-            "minimatch": "^3.0.4",
-            "once": "^1.3.0",
-            "path-is-absolute": "^1.0.0"
-          }
-        },
-        "ms": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
-          "dev": true
-        },
-        "supports-color": {
-          "version": "5.4.0",
-          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
-          "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
-          "dev": true,
-          "requires": {
-            "has-flag": "^3.0.0"
-          }
-        }
-      }
-    },
     "modify-values": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz",
@@ -5057,12 +4895,6 @@
       "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
       "dev": true
     },
-    "private": {
-      "version": "0.1.8",
-      "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",
-      "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==",
-      "dev": true
-    },
     "process-nextick-args": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
@@ -5170,26 +5002,6 @@
         "util-deprecate": "~1.0.1"
       }
     },
-    "recast": {
-      "version": "0.10.43",
-      "resolved": "http://registry.npmjs.org/recast/-/recast-0.10.43.tgz",
-      "integrity": "sha1-uV1Q9tYHYaX2JS4V2AZ4FoSRzn8=",
-      "dev": true,
-      "requires": {
-        "ast-types": "0.8.15",
-        "esprima-fb": "~15001.1001.0-dev-harmony-fb",
-        "private": "~0.1.5",
-        "source-map": "~0.5.0"
-      },
-      "dependencies": {
-        "esprima-fb": {
-          "version": "15001.1001.0-dev-harmony-fb",
-          "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz",
-          "integrity": "sha1-Q761fsJujPI3092LM+QlM1d/Jlk=",
-          "dev": true
-        }
-      }
-    },
     "redent": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz",
@@ -5544,7 +5356,7 @@
         },
         "minimist": {
           "version": "1.2.0",
-          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
           "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
           "dev": true
         }
@@ -5908,57 +5720,6 @@
       "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
       "dev": true
     },
-    "sum-up": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/sum-up/-/sum-up-1.0.3.tgz",
-      "integrity": "sha1-HGYfZnBX9jvLeHWqFDi8FiUlFW4=",
-      "dev": true,
-      "requires": {
-        "chalk": "^1.0.0"
-      },
-      "dependencies": {
-        "ansi-regex": {
-          "version": "2.1.1",
-          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
-          "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
-          "dev": true
-        },
-        "ansi-styles": {
-          "version": "2.2.1",
-          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
-          "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
-          "dev": true
-        },
-        "chalk": {
-          "version": "1.1.3",
-          "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
-          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
-          "dev": true,
-          "requires": {
-            "ansi-styles": "^2.2.1",
-            "escape-string-regexp": "^1.0.2",
-            "has-ansi": "^2.0.0",
-            "strip-ansi": "^3.0.0",
-            "supports-color": "^2.0.0"
-          }
-        },
-        "strip-ansi": {
-          "version": "3.0.1",
-          "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
-          "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
-          "dev": true,
-          "requires": {
-            "ansi-regex": "^2.0.0"
-          }
-        },
-        "supports-color": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
-          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
-          "dev": true
-        }
-      }
-    },
     "supports-color": {
       "version": "5.5.0",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
diff --git a/package.json b/package.json
index 3265717..2325124 100644
--- a/package.json
+++ b/package.json
@@ -6,12 +6,9 @@
   "scripts": {
     "bundle": "bundle-dependencies update",
     "posttest": "standard",
-    "test": "npm run clean && npm run build && npm run instrument && npm run test-integration && npm run test-mocha && npm run report",
+    "test": "npm run clean && npm run instrument && tap -t360 --no-cov -b ./test/*.js && npm run report",
     "clean": "rimraf ./.nyc_output ./node_modules/.cache ./.self_coverage ./test/fixtures/.nyc_output ./test/fixtures/node_modules/.cache *covered.js ./lib/*covered.js",
-    "build": "node ./build-tests",
     "instrument": "node ./build-self-coverage.js",
-    "test-integration": "tap -t120 --no-cov -b ./test/build/*.js && mocha --timeout=15000 ./test/nyc-bin.js",
-    "test-mocha": "node ./bin/nyc --no-clean --silent --temp-dir=./.self_coverage mocha ./test/nyc.js ./test/process-args.js",
     "report": "node ./bin/nyc  --temp-dir ./.self_coverage/ -r text -r lcov report",
     "release": "standard-version"
   },
@@ -104,10 +101,8 @@
     "bundle-dependencies": "^1.0.2",
     "chai": "^4.2.0",
     "coveralls": "^3.0.2",
-    "forking-tap": "^0.1.1",
     "is-windows": "^1.0.2",
     "lodash": "^4.17.11",
-    "mocha": "^5.2.0",
     "newline-regex": "^0.2.1",
     "requirejs": "^2.3.6",
     "sanitize-filename": "^1.6.1",


Test patch:
diff --git a/build-tests.js b/build-tests.js
deleted file mode 100644
index 3889f88..0000000
--- a/build-tests.js
+++ /dev/null
@@ -1,56 +0,0 @@
-'use strict'
-
-var fs = require('fs')
-var path = require('path')
-var rimraf = require('rimraf')
-var mkdirp = require('make-dir')
-var forkingTap = require('forking-tap')
-var zeroFill = require('zero-fill')
-var sanitizeFilename = require('sanitize-filename')
-
-// Delete previous files.
-process.chdir(__dirname)
-rimraf.sync('test/build')
-mkdirp.sync(path.join(__dirname, 'test/build'))
-
-var testDir = path.join(__dirname, 'test/src')
-var buildDir = path.join(__dirname, 'test/build')
-var originalTestsFilename = path.join(testDir, 'nyc-tap.js')
-var originalTestSource = fs.readFileSync(originalTestsFilename, 'utf8')
-var individualTests = forkingTap(originalTestSource, {
-  filename: originalTestsFilename,
-  attachComment: true
-})
-
-function writeTest (test, i, enableCache) {
-  var filename = ['built', zeroFill(3, i)]
-    .concat(test.nestedName)
-    .join('-')
-
-  if (enableCache) {
-    filename += '-cache'
-  }
-
-  filename += '.js'
-
-  // file names with spaces are legal, but annoying to use w/ CLI commands
-  filename = filename.replace(/\s/g, '_')
-
-  // istanbul freaks out if the there are `'` characters in the file name
-  filename = filename.replace(/'/g, '')
-
-  // remove any illegal chars
-  filename = sanitizeFilename(filename)
-
-  var code = test.code
-  if (enableCache) {
-    code = code.replace('var enableCache = false', 'var enableCache = true')
-  }
-
-  fs.writeFileSync(path.join(buildDir, filename), code)
-}
-
-individualTests.forEach(function (test, i) {
-  writeTest(test, i * 2, false)
-  writeTest(test, i * 2 + 1, true)
-})
diff --git a/test/lib/reset-state.js b/test/lib/reset-state.js
new file mode 100644
index 0000000..dc73d32
--- /dev/null
+++ b/test/lib/reset-state.js
@@ -0,0 +1,23 @@
+// reset global state modified by nyc in non-integration tests.
+const extensions = Object.assign({}, require.extensions) // eslint-disable-line
+const glob = require('glob')
+const rimraf = require('rimraf')
+
+module.exports = function () {
+  // nuke any temporary files created during test runs.
+  glob.sync('test/**/*/{.nyc_output,.cache}').forEach(function (path) {
+    rimraf.sync(path)
+  })
+  // reset Node's require cache.
+  Object.keys(require.cache).forEach((key) => {
+    if (key.indexOf('node_modules') === -1) delete require.cache[key]
+  })
+  // reset any custom loaders for extensions, disabling the stack maintained
+  // by append-transform.
+  Object.keys(require.extensions).forEach((key) => { // eslint-disable-line
+    delete require.extensions[key] // eslint-disable-line
+    if (extensions[key]) require.extensions[key] = extensions[key] // eslint-disable-line
+  })
+  // reset any NYC-specific environment variables that might have been set.
+  delete process.env.NYC_CWD
+}
diff --git a/test/nyc-index.js b/test/nyc-index.js
new file mode 100644
index 0000000..00f7f8f
--- /dev/null
+++ b/test/nyc-index.js
@@ -0,0 +1,536 @@
+/* global describe, it, beforeEach */
+
+require('source-map-support').install({ hookRequire: true })
+
+const _ = require('lodash')
+const ap = require('any-path')
+const configUtil = require('../lib/config-util')
+const fs = require('fs')
+
+let NYC
+try {
+  NYC = require('../index.covered.js')
+} catch (e) {
+  NYC = require('../')
+}
+// we test exit handlers in nyc-integration.js.
+NYC.prototype._wrapExit = () => {}
+
+const path = require('path')
+const rimraf = require('rimraf')
+const isWindows = require('is-windows')()
+const spawn = require('child_process').spawn
+const fixtures = path.resolve(__dirname, './fixtures')
+const bin = path.resolve(__dirname, '../bin/nyc')
+const resetState = require('./lib/reset-state')
+
+require('chai').should()
+require('tap').mochaGlobals()
+
+describe('nyc', function () {
+  beforeEach(resetState)
+
+  describe('cwd', function () {
+    it('sets cwd to process.cwd() if no environment variable is set', function () {
+      var nyc = new NYC(configUtil.buildYargs().parse())
+
+      nyc.cwd.should.eql(process.cwd())
+    })
+
+    it('uses NYC_CWD environment variable for cwd if it is set', function () {
+      process.env.NYC_CWD = path.resolve(__dirname, './fixtures')
+      var nyc = new NYC(configUtil.buildYargs().parse())
+
+      nyc.cwd.should.equal(path.resolve(__dirname, './fixtures'))
+    })
+
+    it('will look upwards for package.json from cwd', function () {
+      var nyc = new NYC(configUtil.buildYargs(__dirname).parse())
+      nyc.cwd.should.eql(path.join(__dirname, '..'))
+    })
+
+    it('uses --cwd for cwd if it is set (highest priority and does not look upwards for package.json) ', function () {
+      var nyc = new NYC(configUtil.buildYargs(__dirname).parse(['--cwd', __dirname]))
+      nyc.cwd.should.eql(__dirname)
+    })
+  })
+
+  describe('config', function () {
+    it("loads 'exclude' patterns from package.json#nyc", function () {
+      var nyc = new NYC(configUtil.buildYargs(path.resolve(__dirname, './fixtures')).parse())
+      nyc.exclude.exclude.length.should.eql(8)
+    })
+
+    it("loads 'extension' patterns from package.json#nyc", function () {
+      var nyc = new NYC(configUtil.buildYargs(path.resolve(__dirname, './fixtures/conf-multiple-extensions')).parse())
+      nyc.extensions.length.should.eql(3)
+    })
+
+    it("ignores 'include' option if it's falsy or []", function () {
+      var nyc1 = new NYC(configUtil.buildYargs(path.resolve(__dirname, './fixtures/conf-empty')).parse())
+
+      nyc1.exclude.include.should.equal(false)
+
+      var nyc2 = new NYC({
+        include: []
+      })
+
+      nyc2.exclude.include.should.equal(false)
+    })
+
+    it("ignores 'exclude' option if it's falsy", function () {
+      var nyc1 = new NYC(configUtil.buildYargs(path.resolve(__dirname, './fixtures/conf-empty')).parse())
+      nyc1.exclude.exclude.length.should.eql(15)
+    })
+
+    it("allows for empty 'exclude'", function () {
+      var nyc2 = new NYC({ exclude: [] })
+
+      // an empty exclude still has **/node_modules/**, node_modules/** and added.
+      nyc2.exclude.exclude.length.should.eql(2)
+    })
+  })
+
+  describe('shouldInstrumentFile', function () {
+    it('should exclude appropriately with defaults', function () {
+      var nyc = new NYC(configUtil.buildYargs('/cwd').parse([
+        '--exclude=test/**',
+        '--exclude=test{,-*}.js',
+        '--exclude=**/*.test.js',
+        '--exclude=**/__tests__/**'
+      ]))
+
+      // nyc always excludes "node_modules/**"
+      nyc.exclude.shouldInstrument('/cwd/foo', 'foo').should.equal(true)
+      nyc.exclude.shouldInstrument('/cwd/node_modules/bar', 'node_modules/bar').should.equal(false)
+      nyc.exclude.shouldInstrument('/cwd/foo/node_modules/bar', 'foo/node_modules/bar').should.equal(false)
+      nyc.exclude.shouldInstrument('/cwd/test.js', 'test.js').should.equal(false)
+      nyc.exclude.shouldInstrument('/cwd/testfoo.js', 'testfoo.js').should.equal(true)
+      nyc.exclude.shouldInstrument('/cwd/test-foo.js', 'test-foo.js').should.equal(false)
+      nyc.exclude.shouldInstrument('/cwd/lib/test.js', 'lib/test.js').should.equal(true)
+      nyc.exclude.shouldInstrument('/cwd/foo/bar/test.js', './test.js').should.equal(false)
+      nyc.exclude.shouldInstrument('/cwd/foo/bar/test.js', '.\\test.js').should.equal(false)
+      nyc.exclude.shouldInstrument('/cwd/foo/bar/foo.test.js', './foo.test.js').should.equal(false)
+      nyc.exclude.shouldInstrument('/cwd/foo/bar/__tests__/foo.js', './__tests__/foo.js').should.equal(false)
+    })
+
+    it('should exclude appropriately with config.exclude', function () {
+      var nyc = new NYC(configUtil.buildYargs(fixtures).parse())
+
+      // fixtures/package.json configures excludes: "blarg", "blerg"
+      nyc.exclude.shouldInstrument('blarg', 'blarg').should.equal(false)
+      nyc.exclude.shouldInstrument('blarg/foo.js', 'blarg/foo.js').should.equal(false)
+      nyc.exclude.shouldInstrument('blerg', 'blerg').should.equal(false)
+      nyc.exclude.shouldInstrument('./blerg', './blerg').should.equal(false)
+      nyc.exclude.shouldInstrument('./blerg', '.\\blerg').should.equal(false)
+    })
+
+    it('should exclude outside of the current working directory', function () {
+      var nyc = new NYC(configUtil.buildYargs('/cwd/foo/').parse())
+      nyc.exclude.shouldInstrument('/cwd/bar', '../bar').should.equal(false)
+    })
+
+    it('should not exclude if the current working directory is inside node_modules', function () {
+      var nyc = new NYC(configUtil.buildYargs('/cwd/node_modules/foo/').parse())
+      nyc.exclude.shouldInstrument('/cwd/node_modules/foo/bar', './bar').should.equal(true)
+      nyc.exclude.shouldInstrument('/cwd/node_modules/foo/bar', '.\\bar').should.equal(true)
+    })
+
+    it('allows files to be explicitly included, rather than excluded', function () {
+      var nyc = new NYC(configUtil.buildYargs('/cwd/').parse(['--include=foo.js']))
+
+      nyc.exclude.shouldInstrument('/cwd/foo.js', 'foo.js').should.equal(true)
+      nyc.exclude.shouldInstrument('/cwd/index.js', 'index.js').should.equal(false)
+    })
+
+    it('exclude overrides include', function () {
+      var nyc = new NYC(configUtil.buildYargs('/cwd/').parse([
+        '--include=foo.js',
+        '--include=test.js',
+        '--exclude=**/node_modules/**',
+        '--exclude=test/**',
+        '--exclude=test{,-*}.js'
+      ]))
+
+      nyc.exclude.shouldInstrument('/cwd/foo.js', 'foo.js').should.equal(true)
+      nyc.exclude.shouldInstrument('/cwd/test.js', 'test.js').should.equal(false)
+    })
+  })
+
+  describe('wrap', function () {
+    it('wraps modules with coverage counters when they are required', function () {
+      var nyc = new NYC(configUtil.buildYargs().parse())
+      nyc.reset()
+      nyc.wrap()
+
+      var check = require('./fixtures/check-instrumented')
+      check().should.equal(true)
+    })
+
+    describe('custom require hooks are installed', function () {
+      it('wraps modules with coverage counters when the custom require hook compiles them', function () {
+        let required = false
+        const hook = function (module, filename) {
+          if (filename.indexOf('check-instrumented.js') !== -1) {
+            required = true
+          }
+          module._compile(fs.readFileSync(filename, 'utf8'), filename)
+        }
+
+        var nyc = new NYC(configUtil.buildYargs().parse())
+        nyc.reset()
+        nyc.wrap()
+
+        // install the custom require hook
+        require.extensions['.js'] = hook // eslint-disable-line
+
+        const check = require('./fixtures/check-instrumented')
+        check().should.equal(true)
+
+        // and the hook should have been called
+        required.should.equal(true)
+      })
+    })
+
+    describe('produce source map', function () {
+      it('handles stack traces', function () {
+        var nyc = new NYC(configUtil.buildYargs().parse('--produce-source-map'))
+        nyc.reset()
+        nyc.wrap()
+
+        var check = require('./fixtures/stack-trace')
+        check().should.match(/stack-trace.js:4:/)
+      })
+
+      it('does not handle stack traces when disabled', function () {
+        var nyc = new NYC(configUtil.buildYargs().parse())
+        nyc.reset()
+        nyc.wrap()
+
+        var check = require('./fixtures/stack-trace')
+        check().should.match(/stack-trace.js:1:/)
+      })
+    })
+
+    describe('compile handlers for custom extensions are assigned', function () {
+      it('assigns a function to custom extensions', function () {
+        var nyc = new NYC(configUtil.buildYargs(
+          path.resolve(__dirname, './fixtures/conf-multiple-extensions')
+        ).parse())
+        nyc.reset()
+        nyc.wrap()
+
+        require.extensions['.es6'].should.be.a('function') // eslint-disable-line
+        require.extensions['.foo.bar'].should.be.a('function') // eslint-disable-line
+
+        // default should still exist
+        require.extensions['.js'].should.be.a('function') // eslint-disable-line
+      })
+
+      it('calls the `_handleJs` function for custom file extensions', function () {
+        const required = {
+          es6: false,
+          custom: false
+        }
+        var nyc = new NYC(configUtil.buildYargs(
+          path.resolve(__dirname, './fixtures/conf-multiple-extensions')
+        ).parse())
+
+        nyc['_handleJs'] = (code, options) => {
+          if (options.filename.indexOf('check-instrumented.es6') !== -1) {
+            required.es6 = true
+          }
+          if (options.filename.indexOf('check-instrumented.foo.bar') !== -1) {
+            required.custom = true
+          }
+          return code
+        }
+
+        nyc.reset()
+        nyc.wrap()
+
+        require('./fixtures/conf-multiple-extensions/check-instrumented.es6')
+        require('./fixtures/conf-multiple-extensions/check-instrumented.foo.bar')
+        required.custom.should.equal(true)
+        required.es6.should.equal(true)
+      })
+    })
+
+    function testSignal (signal, done) {
+      var nyc = (new NYC(configUtil.buildYargs(fixtures).parse()))
+
+      var proc = spawn(process.execPath, [bin, './' + signal + '.js'], {
+        cwd: fixtures,
+        env: {},
+        stdio: 'ignore'
+      })
+
+      proc.on('close', function () {
+        var reports = _.filter(nyc.loadReports(), function (report) {
+          return report[path.join(fixtures, signal + '.js')]
+        })
+        reports.length.should.equal(1)
+        return done()
+      })
+    }
+
+    it('writes coverage report when process is killed with SIGTERM', function (done) {
+      if (isWindows) return done()
+      testSignal('sigterm', done)
+    })
+
+    it('writes coverage report when process is killed with SIGINT', function (done) {
+      if (isWindows) return done()
+      testSignal('sigint', done)
+    })
+
+    it('does not output coverage for files that have not been included, by default', function (done) {
+      var nyc = (new NYC(configUtil.buildYargs(process.cwd()).parse()))
+      nyc.wrap()
+      nyc.reset()
+
+      var reports = _.filter(nyc.loadReports(), function (report) {
+        return report['./test/fixtures/not-loaded.js']
+      })
+      reports.length.should.equal(0)
+      return done()
+    })
+  })
+
+  describe('report', function () {
+    it('allows coverage report to be output in an alternative directory', function (done) {
+      var nyc = new NYC(configUtil.buildYargs().parse(
+        ['--report-dir=./alternative-report', '--reporter=lcov']
+      ))
+      nyc.reset()
+
+      var proc = spawn(process.execPath, ['./test/fixtures/child-1.js'], {
+        cwd: process.cwd(),
+        env: process.env,
+        stdio: 'inherit'
+      })
+
+      proc.on('close', function () {
+        nyc.report()
+        fs.existsSync('./alternative-report/lcov.info').should.equal(true)
+        rimraf.sync('./alternative-report')
+        return done()
+      })
+    })
+  })
+
+  describe('addAllFiles', function () {
+    it('outputs an empty coverage report for all files that are not excluded', function (done) {
+      var nyc = new NYC(configUtil.buildYargs(fixtures).parse())
+      nyc.reset()
+      nyc.addAllFiles()
+
+      var notLoadedPath = path.join(fixtures, './not-loaded.js')
+      var reports = _.filter(nyc.loadReports(), function (report) {
+        return ap(report)[notLoadedPath]
+      })
+      var report = reports[0][notLoadedPath]
+
+      reports.length.should.equal(1)
+      report.s['0'].should.equal(0)
+      report.s['1'].should.equal(0)
+      return done()
+    })
+
+    it('outputs an empty coverage report for multiple configured extensions', function (done) {
+      var cwd = path.resolve(fixtures, './conf-multiple-extensions')
+      var nyc = new NYC(configUtil.buildYargs(cwd).parse())
+      nyc.reset()
+      nyc.addAllFiles()
+
+      var notLoadedPath1 = path.join(cwd, './not-loaded.es6')
+      var notLoadedPath2 = path.join(cwd, './not-loaded.js')
+      var reports = _.filter(nyc.loadReports(), function (report) {
+        var apr = ap(report)
+        return apr[notLoadedPath1] || apr[notLoadedPath2]
+      })
+
+      reports.length.should.equal(1)
+
+      var report1 = reports[0][notLoadedPath1]
+      report1.s['0'].should.equal(0)
+      report1.s['1'].should.equal(0)
+
+      var report2 = reports[0][notLoadedPath2]
+      report2.s['0'].should.equal(0)
+      report2.s['1'].should.equal(0)
+
+      return done()
+    })
+
+    it('tracks coverage appropriately once the file is required', function (done) {
+      var nyc = (new NYC(configUtil.buildYargs(fixtures).parse()))
+      nyc.reset()
+      nyc.wrap()
+
+      require('./fixtures/not-loaded')
+
+      nyc.writeCoverageFile()
+
+      var notLoadedPath = path.join(fixtures, './not-loaded.js')
+      var reports = _.filter(nyc.loadReports(), function (report) {
+        return report[notLoadedPath]
+      })
+      var report = reports[0][notLoadedPath]
+
+      reports.length.should.equal(1)
+      report.s['0'].should.equal(1)
+      report.s['1'].should.equal(1)
+
+      return done()
+    })
+
+    it('transpiles .js files added via addAllFiles', function (done) {
+      fs.writeFileSync(
+        './test/fixtures/needs-transpile.js',
+        '--> pork chop sandwiches <--\nvar a = 99',
+        'utf-8'
+      )
+
+      var nyc = (new NYC(configUtil.buildYargs(fixtures).parse(['--require', './test/fixtures/transpile-hook'])))
+      nyc.reset()
+      nyc.addAllFiles()
+
+      var needsTranspilePath = path.join(fixtures, './needs-transpile.js')
+      var reports = _.filter(nyc.loadReports(), function (report) {
+        return ap(report)[needsTranspilePath]
+      })
+      var report = reports[0][needsTranspilePath]
+
+      reports.length.should.equal(1)
+      report.s['0'].should.equal(0)
+
+      fs.unlinkSync(needsTranspilePath)
+      return done()
+    })
+
+    it('does not attempt to transpile files when they are excluded', function (done) {
+      var notNeedTranspilePath = path.join(fixtures, './do-not-need-transpile.do-not-transpile')
+      fs.writeFileSync(
+        notNeedTranspilePath,
+        '--> pork chop sandwiches <--\nvar a = 99',
+        'utf-8'
+      )
+
+      var nyc = (new NYC(configUtil.buildYargs(fixtures).parse([
+        '--require=./test/fixtures/transpile-hook',
+        '--extension=.do-not-transpile',
+        '--include=needs-transpile.do-not-transpile'
+      ])))
+
+      nyc.reset()
+      nyc.addAllFiles()
+      fs.unlinkSync(notNeedTranspilePath)
+      return done()
+    })
+  })
+
+  it('transpiles non-.js files added via addAllFiles', function (done) {
+    fs.writeFileSync(
+      './test/fixtures/needs-transpile.whatever',
+      '--> pork chop sandwiches <--\nvar a = 99',
+      'utf-8'
+    )
+
+    var nyc = (new NYC(configUtil.buildYargs(fixtures).parse([
+      '--require=./test/fixtures/transpile-hook',
+      '--extension=.whatever'
+    ])))
+
+    nyc.reset()
+    nyc.addAllFiles()
+
+    var needsTranspilePath = path.join(fixtures, './needs-transpile.whatever')
+    var reports = _.filter(nyc.loadReports(), function (report) {
+      return ap(report)[needsTranspilePath]
+    })
+    var report = reports[0][needsTranspilePath]
+
+    reports.length.should.equal(1)
+    report.s['0'].should.equal(0)
+
+    fs.unlinkSync(needsTranspilePath)
+    return done()
+  })
+
+  describe('cache', function () {
+    it('handles collisions', function (done) {
+      var nyc = new NYC(configUtil.buildYargs(fixtures).parse())
+      nyc.clearCache()
+
+      var args = [bin, process.execPath, './cache-collision-runner.js']
+
+      var proc = spawn(process.execPath, args, {
+        cwd: fixtures,
+        env: {}
+      })
+
+      proc.on('close', function (code) {
+        code.should.equal(0)
+        done()
+      })
+    })
+
+    it('handles identical files', function (done) {
+      var nyc = new NYC(configUtil.buildYargs(fixtures).parse())
+      nyc.clearCache()
+
+      var args = [bin, process.execPath, './identical-file-runner.js']
+
+      var proc = spawn(process.execPath, args, {
+        cwd: fixtures,
+        env: {}
+      })
+
+      proc.on('close', function (code) {
+        code.should.equal(0)
+        done()
+      })
+    })
+  })
+
+  describe('_disableCachingTransform', function () {
+    it('is disabled if cache is "false"', function () {
+      const nyc = new NYC({ cache: false })
+      nyc._disableCachingTransform().should.equal(true)
+    })
+
+    it('is enabled if cache is "true" and isChildProcess is "true"', function () {
+      const nyc = new NYC({
+        cache: true,
+        isChildProcess: true
+      })
+      nyc._disableCachingTransform().should.equal(false)
+    })
+
+    it('is disabled if cache is "true" and isChildProcess is "false"', function () {
+      const nyc = new NYC({
+        cache: true,
+        isChildProcess: true
+      })
+      nyc._disableCachingTransform().should.equal(false)
+    })
+  })
+
+  describe('cacheDirectory', function () {
+    it('should resolve default cache folder to absolute path', function () {
+      const nyc = new NYC({
+        cache: true
+      })
+      path.isAbsolute(nyc.cacheDirectory).should.equal(true)
+    })
+
+    it('should resolve custom cache folder to absolute path', function () {
+      const nyc = new NYC({
+        cacheDir: '.nyc_cache',
+        cache: true
+      })
+      path.isAbsolute(nyc.cacheDirectory).should.equal(true)
+    })
+  })
+})
diff --git a/test/nyc-bin.js b/test/nyc-integration.js
similarity index 99%
rename from test/nyc-bin.js
rename to test/nyc-integration.js
index 1aae65b..aa6b9a3 100644
--- a/test/nyc-bin.js
+++ b/test/nyc-integration.js
@@ -15,6 +15,7 @@ const spawn = require('child_process').spawn
 const si = require('strip-indent')
 
 require('chai').should()
+require('tap').mochaGlobals()
 
 // beforeEach
 rimraf.sync(path.resolve(fakebin, 'node'))
diff --git a/test/nyc.js b/test/nyc.js
deleted file mode 100644
index 11e1c0c..0000000
--- a/test/nyc.js
+++ /dev/null
@@ -1,48 +0,0 @@
-/* global describe, it */
-
-const NYC = require('../')
-const path = require('path')
-
-require('chai').should()
-
-describe('NYC', function () {
-  describe('_disableCachingTransform', function () {
-    it('is disabled if cache is "false"', function () {
-      const nyc = new NYC({ cache: false })
-      nyc._disableCachingTransform().should.equal(true)
-    })
-
-    it('is enabled if cache is "true" and isChildProcess is "true"', function () {
-      const nyc = new NYC({
-        cache: true,
-        isChildProcess: true
-      })
-      nyc._disableCachingTransform().should.equal(false)
-    })
-
-    it('is disabled if cache is "true" and isChildProcess is "false"', function () {
-      const nyc = new NYC({
-        cache: true,
-        isChildProcess: true
-      })
-      nyc._disableCachingTransform().should.equal(false)
-    })
-  })
-
-  describe('cacheDirectory', function () {
-    it('should resolve default cache folder to absolute path', function () {
-      const nyc = new NYC({
-        cache: true
-      })
-      path.isAbsolute(nyc.cacheDirectory).should.equal(true)
-    })
-
-    it('should resolve custom cache folder to absolute path', function () {
-      const nyc = new NYC({
-        cacheDir: '.nyc_cache',
-        cache: true
-      })
-      path.isAbsolute(nyc.cacheDirectory).should.equal(true)
-    })
-  })
-})
diff --git a/test/process-args.js b/test/process-args.js
index bab9806..4509bc5 100644
--- a/test/process-args.js
+++ b/test/process-args.js
@@ -1,6 +1,7 @@
 /* global describe, it */
 
 require('chai').should()
+require('tap').mochaGlobals()
 
 const processArgs = require('../lib/process-args')