Repository: jprichardson/node-fs-extra
Pull request #300 (2016-10-27 20:37:45)

Inline Rimraf
We aren't using rimraf's glob support, and glob is a rather large dependency.

Changes:

- Remove glob support from rimraf
- Remove move()'s dependency on rimraf; use fs-extra.remove instead
- Make rimraf a devDependency; it is used in the tests

---

**Original comment:**

@jprichardson This is my WIP of inlining rimraf. This needs rebased, **do not merge**.

rimraf has functionality built in to handle alternate `fs` modules. Currently, there is no way for the end-user to leverage that functionality. I could simplify the code by removing that, however, I don't want to have to add it back. Will we be adding custom `fs` module support or glob support to `fs-extra` first?

jprichardson: > Will we be adding custom fs module support 

This will come first.
RyanZim: OK, thanks.

Patch:
diff --git a/.travis.yml b/.travis.yml
index b8de422..da0d28c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,15 +1,15 @@
 sudo: false
 language: node_js
 node_js:
+  - "0.10"
   - "0.12"
   - "io.js"
   - "4"
   - "5"
   - "6"
-  - "7"
 matrix:
   include:
-    - node_js: "7"
+    - node_js: "4"
       env: TEST_SUITE=lint
 env:
   - TEST_SUITE=unit
diff --git a/README.md b/README.md
index eb1236b..06648b8 100644
--- a/README.md
+++ b/README.md
@@ -129,7 +129,7 @@ Options:
 - clobber (boolean): overwrite existing file or directory, default is `true`.
 - dereference (boolean): dereference symlinks, default is `false`.
 - preserveTimestamps (boolean): will set last modification and access times to the ones of the original source files, default is `false`.
-- filter: Function to filter copied files. Return `true` to include, `false` to exclude. This can also be a RegExp, however this is deprecated (See [issue #239](https://github.com/jprichardson/node-fs-extra/issues/239) for background).
+- filter: Function or RegExp to filter copied files. If function, return true to include, false to exclude. If RegExp, same as function, where `filter` is `filter.test`.
 
 Sync: `copySync()`
 
diff --git a/appveyor.yml b/appveyor.yml
index 591f933..08a2803 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -2,9 +2,10 @@
 environment:
   matrix:
     # node.js
+    - nodejs_version: "0.10"
     - nodejs_version: "0.12"
     - nodejs_version: "4"
-    - nodejs_version: "6"
+    - nodejs_version: "5"
 
 # Install scripts. (runs after repo cloning)
 install:
diff --git a/lib/copy-sync/copy-file-sync.js b/lib/copy-sync/copy-file-sync.js
index 9cac892..c6c5711 100644
--- a/lib/copy-sync/copy-file-sync.js
+++ b/lib/copy-sync/copy-file-sync.js
@@ -9,13 +9,10 @@ function copyFileSync (srcFile, destFile, options) {
 
   if (fs.existsSync(destFile)) {
     if (clobber) {
+      fs.chmodSync(destFile, parseInt('777', 8))
       fs.unlinkSync(destFile)
     } else {
-      var err = new Error('EEXIST: ' + destFile + ' already exists.')
-      err.code = 'EEXIST'
-      err.errno = -17
-      err.path = destFile
-      throw err
+      throw Error('EEXIST')
     }
   }
 
diff --git a/lib/copy-sync/copy-sync.js b/lib/copy-sync/copy-sync.js
index 38c8c43..8168dee 100644
--- a/lib/copy-sync/copy-sync.js
+++ b/lib/copy-sync/copy-sync.js
@@ -18,22 +18,14 @@ function copySync (src, dest, options) {
 
   options.filter = options.filter || function () { return true }
 
-  // Warn about using preserveTimestamps on 32-bit node:
-  if (options.preserveTimestamps && process.arch === 'ia32') {
-    console.warn('fs-extra: Using the preserveTimestamps option in 32-bit node is not recommended;\n' +
-    'see https://github.com/jprichardson/node-fs-extra/issues/269')
-  }
-
   var stats = (options.recursive && !options.dereference) ? fs.lstatSync(src) : fs.statSync(src)
   var destFolder = path.dirname(dest)
   var destFolderExists = fs.existsSync(destFolder)
   var performCopy = false
 
   if (stats.isFile()) {
-    if (options.filter instanceof RegExp) {
-      console.warn('Warning: fs-extra: Passing a RegExp filter is deprecated, use a function')
-      performCopy = options.filter.test(src)
-    } else if (typeof options.filter === 'function') performCopy = options.filter(src)
+    if (options.filter instanceof RegExp) performCopy = options.filter.test(src)
+    else if (typeof options.filter === 'function') performCopy = options.filter(src)
 
     if (performCopy) {
       if (!destFolderExists) mkdir.mkdirsSync(destFolder)
diff --git a/lib/copy/copy.js b/lib/copy/copy.js
index 6c5e224..d9d2912 100644
--- a/lib/copy/copy.js
+++ b/lib/copy/copy.js
@@ -13,12 +13,6 @@ function copy (src, dest, options, callback) {
   callback = callback || function () {}
   options = options || {}
 
-  // Warn about using preserveTimestamps on 32-bit node:
-  if (options.preserveTimestamps && process.arch === 'ia32') {
-    console.warn('fs-extra: Using the preserveTimestamps option in 32-bit node is not recommended;\n' +
-    'see https://github.com/jprichardson/node-fs-extra/issues/269')
-  }
-
   // don't allow src and dest to be the same
   var basePath = process.cwd()
   var currentPath = path.resolve(basePath, src)
diff --git a/lib/copy/ncp.js b/lib/copy/ncp.js
index a951004..4fded87 100644
--- a/lib/copy/ncp.js
+++ b/lib/copy/ncp.js
@@ -16,15 +16,18 @@ function ncp (source, dest, options, callback) {
 
   var filter = options.filter
   var transform = options.transform
-  var clobber = options.clobber !== false // default true
+  var clobber = options.clobber !== false
   var dereference = options.dereference
   var preserveTimestamps = options.preserveTimestamps === true
 
+  var errs = null
+
   var started = 0
   var finished = 0
   var running = 0
-
-  var errored = false
+  // this is pretty useless now that we're using graceful-fs
+  // consider removing
+  var limit = options.limit || 512
 
   startCopy(currentPath)
 
@@ -32,7 +35,6 @@ function ncp (source, dest, options, callback) {
     started++
     if (filter) {
       if (filter instanceof RegExp) {
-        console.warn('Warning: fs-extra: Passing a RegExp filter is deprecated, use a function')
         if (!filter.test(source)) {
           return doneOne(true)
         }
@@ -47,6 +49,11 @@ function ncp (source, dest, options, callback) {
 
   function getStats (source) {
     var stat = dereference ? fs.stat : fs.lstat
+    if (running >= limit) {
+      return setImmediate(function () {
+        getStats(source)
+      })
+    }
     running++
     stat(source, function (err, stats) {
       if (err) return onError(err)
@@ -82,11 +89,7 @@ function ncp (source, dest, options, callback) {
             copyFile(file, target)
           })
         } else {
-          var err = new Error('EEXIST: ' + target + ' already exists.')
-          err.code = 'EEXIST'
-          err.errno = -17
-          err.path = target
-          onError(err)
+          doneOne()
         }
       }
     })
@@ -130,7 +133,7 @@ function ncp (source, dest, options, callback) {
   }
 
   function onDir (dir) {
-    var target = dir.name.replace(currentPath, targetPath.replace('$', '$$$$')) // escapes '$' with '$$'
+    var target = dir.name.replace(currentPath, targetPath)
     isWritable(target, function (writable) {
       if (writable) {
         return mkDir(dir, target)
@@ -211,11 +214,19 @@ function ncp (source, dest, options, callback) {
   }
 
   function onError (err) {
-    // ensure callback is defined & called only once:
-    if (!errored && callback !== undefined) {
-      errored = true
+    if (options.stopOnError) {
       return callback(err)
+    } else if (!errs && options.errs) {
+      errs = fs.createWriteStream(options.errs)
+    } else if (!errs) {
+      errs = []
+    }
+    if (typeof errs.write === 'undefined') {
+      errs.push(err)
+    } else {
+      errs.write(err.stack + '\n\n')
     }
+    return doneOne()
   }
 
   function doneOne (skipped) {
@@ -223,7 +234,7 @@ function ncp (source, dest, options, callback) {
     finished++
     if ((started === finished) && (running === 0)) {
       if (callback !== undefined) {
-        return callback(null)
+        return errs ? callback(errs) : callback(null)
       }
     }
   }
diff --git a/lib/move/index.js b/lib/move/index.js
index f28152f..1eb2a14 100644
--- a/lib/move/index.js
+++ b/lib/move/index.js
@@ -7,7 +7,7 @@
 var fs = require('graceful-fs')
 var ncp = require('../copy/ncp')
 var path = require('path')
-var rimraf = require('rimraf')
+var remove = require('../remove').remove
 var mkdirp = require('../mkdirs').mkdirs
 
 function mv (source, dest, options, callback) {
@@ -40,7 +40,7 @@ function mv (source, dest, options, callback) {
         if (!err) return callback()
 
         if (err.code === 'ENOTEMPTY' || err.code === 'EEXIST') {
-          rimraf(dest, function (err) {
+          remove(dest, function (err) {
             if (err) return callback(err)
             options.clobber = false // just clobbered it, no need to do it again
             mv(source, dest, options, callback)
@@ -51,7 +51,7 @@ function mv (source, dest, options, callback) {
         // weird Windows shit
         if (err.code === 'EPERM') {
           setTimeout(function () {
-            rimraf(dest, function (err) {
+            remove(dest, function (err) {
               if (err) return callback(err)
               options.clobber = false
               mv(source, dest, options, callback)
@@ -142,12 +142,12 @@ function moveDirAcrossDevice (source, dest, clobber, limit, callback) {
   function startNcp () {
     ncp(source, dest, options, function (errList) {
       if (errList) return callback(errList[0])
-      rimraf(source, callback)
+      remove(source, callback)
     })
   }
 
   if (clobber) {
-    rimraf(dest, function (err) {
+    remove(dest, function (err) {
       if (err) return callback(err)
       startNcp()
     })
diff --git a/lib/remove/index.js b/lib/remove/index.js
index c01644d..566b866 100644
--- a/lib/remove/index.js
+++ b/lib/remove/index.js
@@ -1,4 +1,4 @@
-var rimraf = require('rimraf')
+var rimraf = require('./rimraf')
 
 function removeSync (dir) {
   return rimraf.sync(dir, {disableGlob: true})
diff --git a/lib/remove/rimraf.js b/lib/remove/rimraf.js
new file mode 100644
index 0000000..da55a62
--- /dev/null
+++ b/lib/remove/rimraf.js
@@ -0,0 +1,301 @@
+module.exports = rimraf
+rimraf.sync = rimrafSync
+
+var assert = require('assert')
+var path = require('path')
+var fs = require('graceful-fs')
+
+var isWindows = (process.platform === 'win32')
+
+function defaults (options) {
+  var methods = [
+    'unlink',
+    'chmod',
+    'stat',
+    'lstat',
+    'rmdir',
+    'readdir'
+  ]
+  methods.forEach(function (m) {
+    options[m] = options[m] || fs[m]
+    m = m + 'Sync'
+    options[m] = options[m] || fs[m]
+  })
+
+  options.maxBusyTries = options.maxBusyTries || 3
+}
+
+function rimraf (p, options, cb) {
+  if (typeof options === 'function') {
+    cb = options
+    options = {}
+  }
+
+  assert(p, 'rimraf: missing path')
+  assert.equal(typeof p, 'string', 'rimraf: path should be a string')
+  assert.equal(typeof cb, 'function', 'rimraf: callback function required')
+  assert(options, 'rimraf: invalid options argument provided')
+  assert.equal(typeof options, 'object', 'rimraf: options should be object')
+
+  defaults(options)
+
+  var busyTries = 0
+
+  rimraf_(p, options, function CB (er) {
+    if (er) {
+      if (isWindows && (er.code === 'EBUSY' || er.code === 'ENOTEMPTY' || er.code === 'EPERM') &&
+          busyTries < options.maxBusyTries) {
+        busyTries++
+        var time = busyTries * 100
+        // try again, with the same exact callback as this one.
+        return setTimeout(function () {
+          rimraf_(p, options, CB)
+        }, time)
+      }
+
+      // already gone
+      if (er.code === 'ENOENT') er = null
+    }
+
+    cb(er)
+  })
+}
+
+// Two possible strategies.
+// 1. Assume it's a file.  unlink it, then do the dir stuff on EPERM or EISDIR
+// 2. Assume it's a directory.  readdir, then do the file stuff on ENOTDIR
+//
+// Both result in an extra syscall when you guess wrong.  However, there
+// are likely far more normal files in the world than directories.  This
+// is based on the assumption that a the average number of files per
+// directory is >= 1.
+//
+// If anyone ever complains about this, then I guess the strategy could
+// be made configurable somehow.  But until then, YAGNI.
+function rimraf_ (p, options, cb) {
+  assert(p)
+  assert(options)
+  assert(typeof cb === 'function')
+
+  // sunos lets the root user unlink directories, which is... weird.
+  // so we have to lstat here and make sure it's not a dir.
+  options.lstat(p, function (er, st) {
+    if (er && er.code === 'ENOENT') {
+      return cb(null)
+    }
+
+    // Windows can EPERM on stat.  Life is suffering.
+    if (er && er.code === 'EPERM' && isWindows) {
+      fixWinEPERM(p, options, er, cb)
+    }
+
+    if (st && st.isDirectory()) {
+      return rmdir(p, options, er, cb)
+    }
+
+    options.unlink(p, function (er) {
+      if (er) {
+        if (er.code === 'ENOENT') {
+          return cb(null)
+        }
+        if (er.code === 'EPERM') {
+          return (isWindows)
+            ? fixWinEPERM(p, options, er, cb)
+            : rmdir(p, options, er, cb)
+        }
+        if (er.code === 'EISDIR') {
+          return rmdir(p, options, er, cb)
+        }
+      }
+      return cb(er)
+    })
+  })
+}
+
+function fixWinEPERM (p, options, er, cb) {
+  assert(p)
+  assert(options)
+  assert(typeof cb === 'function')
+  if (er) {
+    assert(er instanceof Error)
+  }
+
+  options.chmod(p, 666, function (er2) {
+    if (er2) {
+      cb(er2.code === 'ENOENT' ? null : er)
+    } else {
+      options.stat(p, function (er3, stats) {
+        if (er3) {
+          cb(er3.code === 'ENOENT' ? null : er)
+        } else if (stats.isDirectory()) {
+          rmdir(p, options, er, cb)
+        } else {
+          options.unlink(p, cb)
+        }
+      })
+    }
+  })
+}
+
+function fixWinEPERMSync (p, options, er) {
+  assert(p)
+  assert(options)
+  if (er) {
+    assert(er instanceof Error)
+  }
+
+  try {
+    options.chmodSync(p, 666)
+  } catch (er2) {
+    if (er2.code === 'ENOENT') {
+      return
+    } else {
+      throw er
+    }
+  }
+
+  try {
+    var stats = options.statSync(p)
+  } catch (er3) {
+    if (er3.code === 'ENOENT') {
+      return
+    } else {
+      throw er
+    }
+  }
+
+  if (stats.isDirectory()) {
+    rmdirSync(p, options, er)
+  } else {
+    options.unlinkSync(p)
+  }
+}
+
+function rmdir (p, options, originalEr, cb) {
+  assert(p)
+  assert(options)
+  if (originalEr) {
+    assert(originalEr instanceof Error)
+  }
+  assert(typeof cb === 'function')
+
+  // try to rmdir first, and only readdir on ENOTEMPTY or EEXIST (SunOS)
+  // if we guessed wrong, and it's not a directory, then
+  // raise the original error.
+  options.rmdir(p, function (er) {
+    if (er && (er.code === 'ENOTEMPTY' || er.code === 'EEXIST' || er.code === 'EPERM')) {
+      rmkids(p, options, cb)
+    } else if (er && er.code === 'ENOTDIR') {
+      cb(originalEr)
+    } else {
+      cb(er)
+    }
+  })
+}
+
+function rmkids (p, options, cb) {
+  assert(p)
+  assert(options)
+  assert(typeof cb === 'function')
+
+  options.readdir(p, function (er, files) {
+    if (er) {
+      return cb(er)
+    }
+    var n = files.length
+    if (n === 0) {
+      return options.rmdir(p, cb)
+    }
+    var errState
+    files.forEach(function (f) {
+      rimraf(path.join(p, f), options, function (er) {
+        if (errState) {
+          return
+        }
+        if (er) {
+          return cb(errState = er)
+        }
+        if (--n === 0) {
+          options.rmdir(p, cb)
+        }
+      })
+    })
+  })
+}
+
+// this looks simpler, and is strictly *faster*, but will
+// tie up the JavaScript thread and fail on excessively
+// deep directory trees.
+function rimrafSync (p, options) {
+  options = options || {}
+  defaults(options)
+
+  assert(p, 'rimraf: missing path')
+  assert.equal(typeof p, 'string', 'rimraf: path should be a string')
+  assert(options, 'rimraf: missing options')
+  assert.equal(typeof options, 'object', 'rimraf: options should be object')
+
+  try {
+    var st = options.lstatSync(p)
+  } catch (er) {
+    if (er.code === 'ENOENT') {
+      return
+    }
+
+    // Windows can EPERM on stat.  Life is suffering.
+    if (er.code === 'EPERM' && isWindows) {
+      fixWinEPERMSync(p, options, er)
+    }
+  }
+
+  try {
+    // sunos lets the root user unlink directories, which is... weird.
+    if (st && st.isDirectory()) {
+      rmdirSync(p, options, null)
+    } else {
+      options.unlinkSync(p)
+    }
+  } catch (er) {
+    if (er.code === 'ENOENT') {
+      return
+    }
+    if (er.code === 'EPERM') {
+      return isWindows ? fixWinEPERMSync(p, options, er) : rmdirSync(p, options, er)
+    }
+    if (er.code !== 'EISDIR') {
+      throw er
+    }
+    rmdirSync(p, options, er)
+  }
+}
+
+function rmdirSync (p, options, originalEr) {
+  assert(p)
+  assert(options)
+  if (originalEr) {
+    assert(originalEr instanceof Error)
+  }
+
+  try {
+    options.rmdirSync(p)
+  } catch (er) {
+    if (er.code === 'ENOENT') {
+      return
+    }
+    if (er.code === 'ENOTDIR') {
+      throw originalEr
+    }
+    if (er.code === 'ENOTEMPTY' || er.code === 'EEXIST' || er.code === 'EPERM') {
+      rmkidsSync(p, options)
+    }
+  }
+}
+
+function rmkidsSync (p, options) {
+  assert(p)
+  assert(options)
+  options.readdirSync(p).forEach(function (f) {
+    rimrafSync(path.join(p, f), options)
+  })
+  options.rmdirSync(p, options)
+}
diff --git a/package.json b/package.json
index 2b4db7f..31d296a 100644
--- a/package.json
+++ b/package.json
@@ -36,8 +36,7 @@
     "graceful-fs": "^4.1.2",
     "jsonfile": "^2.1.0",
     "klaw": "^1.0.0",
-    "path-is-absolute": "^1.0.0",
-    "rimraf": "^2.2.8"
+    "path-is-absolute": "^1.0.0"
   },
   "devDependencies": {
     "coveralls": "^2.11.2",
@@ -46,6 +45,7 @@
     "mocha": "^2.1.0",
     "proxyquire": "^1.7.10",
     "read-dir-files": "^0.1.1",
+    "rimraf": "^2.2.8",
     "secure-random": "^1.1.1",
     "semver": "^4.3.6",
     "standard": "^7.0.0-beta.0"


Test patch:
diff --git a/lib/copy-sync/__tests__/copy-sync-dir.test.js b/lib/copy-sync/__tests__/copy-sync-dir.test.js
index f29d49b..9e6e53c 100644
--- a/lib/copy-sync/__tests__/copy-sync-dir.test.js
+++ b/lib/copy-sync/__tests__/copy-sync-dir.test.js
@@ -64,9 +64,7 @@ describe('+ copySync()', function () {
 
     it('should should apply filter recursively', function () {
       var FILES = 2
-      var filter = function (s) {
-        return /0$/i.test(s)
-      }
+      var filter = /0$/i
 
       fs.mkdirsSync(src)
 
diff --git a/lib/copy-sync/__tests__/copy-sync-file.test.js b/lib/copy-sync/__tests__/copy-sync-file.test.js
index 3442359..b6c869b 100644
--- a/lib/copy-sync/__tests__/copy-sync-file.test.js
+++ b/lib/copy-sync/__tests__/copy-sync-file.test.js
@@ -65,6 +65,29 @@ describe('+ copySync()', function () {
       assert.strictEqual(statSrc.mode, statDest.mode)
     })
 
+    it('should only copy files allowed by filter regex', function () {
+      var srcFile1 = path.join(TEST_DIR, '1.html')
+      var srcFile2 = path.join(TEST_DIR, '2.css')
+      var srcFile3 = path.join(TEST_DIR, '3.jade')
+
+      fs.writeFileSync(srcFile1, '')
+      fs.writeFileSync(srcFile2, '')
+      fs.writeFileSync(srcFile3, '')
+
+      var destFile1 = path.join(TEST_DIR, 'dest1.html')
+      var destFile2 = path.join(TEST_DIR, 'dest2.css')
+      var destFile3 = path.join(TEST_DIR, 'dest3.jade')
+      var filter = /.html$|.css$/i
+
+      fs.copySync(srcFile1, destFile1, {filter: filter})
+      fs.copySync(srcFile2, destFile2, {filter: filter})
+      fs.copySync(srcFile3, destFile3, {filter: filter})
+
+      assert(fs.existsSync(destFile1))
+      assert(fs.existsSync(destFile2))
+      assert(!fs.existsSync(destFile3))
+    })
+
     it('should only copy files allowed by filter fn', function () {
       var srcFile1 = path.join(TEST_DIR, '1.html')
       var srcFile2 = path.join(TEST_DIR, '2.css')
diff --git a/lib/copy/__tests__/copy.test.js b/lib/copy/__tests__/copy.test.js
index 5c1efbc..1476a7a 100644
--- a/lib/copy/__tests__/copy.test.js
+++ b/lib/copy/__tests__/copy.test.js
@@ -56,6 +56,17 @@ describe('fs-extra', function () {
         })
       })
 
+      it('should only copy files allowed by filter regex', function (done) {
+        var srcFile1 = path.join(TEST_DIR, '1.jade')
+        fs.writeFileSync(srcFile1, '')
+        var destFile1 = path.join(TEST_DIR, 'dest1.jade')
+        var filter = /.html$|.css$/i
+        fse.copy(srcFile1, destFile1, filter, function () {
+          assert(!fs.existsSync(destFile1))
+          done()
+        })
+      })
+
       it('should only copy files allowed by filter fn', function (done) {
         var srcFile1 = path.join(TEST_DIR, '1.css')
         fs.writeFileSync(srcFile1, '')
@@ -71,7 +82,7 @@ describe('fs-extra', function () {
         var srcFile1 = path.join(TEST_DIR, '1.jade')
         fs.writeFileSync(srcFile1, '')
         var destFile1 = path.join(TEST_DIR, 'dest1.jade')
-        var options = {filter: function (s) { return /.html$|.css$/i.test(s) }}
+        var options = {filter: /.html$|.css$/i}
         fse.copy(srcFile1, destFile1, options, function () {
           assert(!fs.existsSync(destFile1))
           done()
diff --git a/lib/copy/__tests__/ncp/broken-symlink.test.js b/lib/copy/__tests__/ncp/broken-symlink.test.js
index 33b0d43..3ab067e 100644
--- a/lib/copy/__tests__/ncp/broken-symlink.test.js
+++ b/lib/copy/__tests__/ncp/broken-symlink.test.js
@@ -33,7 +33,8 @@ describe('ncp broken symlink', function () {
 
   it('should return an error when dereference=true', function (done) {
     ncp(src, out, {dereference: true}, function (err) {
-      assert.equal(err.code, 'ENOENT')
+      assert.equal(err.length, 1)
+      assert.equal(err[0].code, 'ENOENT')
       done()
     })
   })
diff --git a/lib/copy/__tests__/ncp/ncp-error-perm.test.js b/lib/copy/__tests__/ncp/ncp-error-perm.test.js
index 5038959..5aaec3d 100644
--- a/lib/copy/__tests__/ncp/ncp-error-perm.test.js
+++ b/lib/copy/__tests__/ncp/ncp-error-perm.test.js
@@ -43,7 +43,7 @@ describe('ncp / error / dest-permission', function () {
 
     ncp(src, subdest, function (err) {
       assert(err)
-      assert.equal(err.code, 'EACCES')
+      assert.equal(err[0].code, 'EACCES')
       done()
     })
   })
diff --git a/lib/copy/__tests__/ncp/ncp.test.js b/lib/copy/__tests__/ncp/ncp.test.js
index e091306..73c5b81 100644
--- a/lib/copy/__tests__/ncp/ncp.test.js
+++ b/lib/copy/__tests__/ncp/ncp.test.js
@@ -5,7 +5,7 @@ var rimraf = require('rimraf')
 var readDirFiles = require('read-dir-files').read // temporary, will remove
 var ncp = require('../../ncp')
 
-/* eslint-env mocha */
+/* global after, before, describe, it */
 
 var fixturesDir = path.join(__dirname, 'fixtures')
 
@@ -88,19 +88,10 @@ describe('ncp', function () {
     })
 
     describe('when using clobber=false', function () {
-      beforeEach(function (done) {
-        rimraf(out, done)
-      })
-      it('works', function (cb) {
-        ncp(src, out, {clobber: false}, function (err) {
-          assert.ifError(err)
-          cb()
-        })
-      })
-      it('errors if files exist', function (cb) {
+      it('the copy is completed successfully', function (cb) {
         ncp(src, out, function () {
           ncp(src, out, {clobber: false}, function (err) {
-            assert(err)
+            assert.ifError(err)
             cb()
           })
         })