[Pkg-javascript-commits] [node-graceful-fs] 04/08: New upstream version 4.1.9

Julien Puydt julien.puydt at laposte.net
Sun Oct 30 07:27:00 UTC 2016


This is an automated email from the git hooks/post-receive script.

jpuydt-guest pushed a commit to branch master
in repository node-graceful-fs.

commit 4667eb53d7ab68160c22da1b13c35c93c08de805
Author: Julien Puydt <julien.puydt at laposte.net>
Date:   Sun Oct 16 11:08:43 2016 +0200

    New upstream version 4.1.9
---
 .gitignore                |   2 +
 .travis.yml               |   7 +
 LICENSE                   |  36 ++---
 README.md                 | 103 +++++++++++++-
 fs.js                     |  32 +++--
 graceful-fs.js            | 334 ++++++++++++++++++++++++++++++----------------
 legacy-streams.js         | 118 ++++++++++++++++
 package.json              |  20 ++-
 polyfills.js              | 333 ++++++++++++++++++++++++++-------------------
 test.js                   |  24 ++++
 test/chown-er-ok.js       |  53 ++++++++
 test/close.js             |  10 ++
 test/enoent.js            |  46 +++++++
 test/max-open.js          |  68 ++++++++++
 test/open.js              |   7 +-
 test/read-write-stream.js |  51 +++++++
 test/readdir-options.js   |  61 +++++++++
 test/readdir-sort.js      |   3 +-
 test/readfile.js          |  47 +++++++
 test/stats-uid-gid.js     |  44 ++++++
 test/stats.js             |  12 ++
 test/write-then-read.js   |  43 ++++++
 22 files changed, 1149 insertions(+), 305 deletions(-)

diff --git a/.gitignore b/.gitignore
index c2658d7..2f24c57 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
 node_modules/
+coverage/
+.nyc_output/
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..c070d37
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,7 @@
+sudo: false
+language: node_js
+node_js:
+  - '0.10'
+  - '4'
+  - '5'
+  - '6'
diff --git a/LICENSE b/LICENSE
index 0c44ae7..9d2c803 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,27 +1,15 @@
-Copyright (c) Isaac Z. Schlueter ("Author")
-All rights reserved.
+The ISC License
 
-The BSD License
+Copyright (c) Isaac Z. Schlueter, Ben Noordhuis, and Contributors
 
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions
-are met:
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
 
-1. Redistributions of source code must retain the above copyright
-   notice, this list of conditions and the following disclaimer.
-
-2. Redistributions in binary form must reproduce the above copyright
-   notice, this list of conditions and the following disclaimer in the
-   documentation and/or other materials provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
-BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
-BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
-WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
-OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
-IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/README.md b/README.md
index 13a2e86..5273a50 100644
--- a/README.md
+++ b/README.md
@@ -7,9 +7,7 @@ The improvements are meant to normalize behavior across different
 platforms and environments, and to make filesystem access more
 resilient to errors.
 
-## Improvements over [fs module](http://api.nodejs.org/fs.html)
-
-graceful-fs:
+## Improvements over [fs module](https://nodejs.org/api/fs.html)
 
 * Queues up `open` and `readdir` calls, and retries them once
   something closes if there is an EMFILE error from too many file
@@ -34,3 +32,102 @@ var fs = require('graceful-fs')
 // now go and do stuff with it...
 fs.readFileSync('some-file-or-whatever')
 ```
+
+## Global Patching
+
+If you want to patch the global fs module (or any other fs-like
+module) you can do this:
+
+```javascript
+// Make sure to read the caveat below.
+var realFs = require('fs')
+var gracefulFs = require('graceful-fs')
+gracefulFs.gracefulify(realFs)
+```
+
+This should only ever be done at the top-level application layer, in
+order to delay on EMFILE errors from any fs-using dependencies.  You
+should **not** do this in a library, because it can cause unexpected
+delays in other parts of the program.
+
+## Changes
+
+This module is fairly stable at this point, and used by a lot of
+things.  That being said, because it implements a subtle behavior
+change in a core part of the node API, even modest changes can be
+extremely breaking, and the versioning is thus biased towards
+bumping the major when in doubt.
+
+The main change between major versions has been switching between
+providing a fully-patched `fs` module vs monkey-patching the node core
+builtin, and the approach by which a non-monkey-patched `fs` was
+created.
+
+The goal is to trade `EMFILE` errors for slower fs operations.  So, if
+you try to open a zillion files, rather than crashing, `open`
+operations will be queued up and wait for something else to `close`.
+
+There are advantages to each approach.  Monkey-patching the fs means
+that no `EMFILE` errors can possibly occur anywhere in your
+application, because everything is using the same core `fs` module,
+which is patched.  However, it can also obviously cause undesirable
+side-effects, especially if the module is loaded multiple times.
+
+Implementing a separate-but-identical patched `fs` module is more
+surgical (and doesn't run the risk of patching multiple times), but
+also imposes the challenge of keeping in sync with the core module.
+
+The current approach loads the `fs` module, and then creates a
+lookalike object that has all the same methods, except a few that are
+patched.  It is safe to use in all versions of Node from 0.8 through
+7.0.
+
+### v4
+
+* Do not monkey-patch the fs module.  This module may now be used as a
+  drop-in dep, and users can opt into monkey-patching the fs builtin
+  if their app requires it.
+
+### v3
+
+* Monkey-patch fs, because the eval approach no longer works on recent
+  node.
+* fixed possible type-error throw if rename fails on windows
+* verify that we *never* get EMFILE errors
+* Ignore ENOSYS from chmod/chown
+* clarify that graceful-fs must be used as a drop-in
+
+### v2.1.0
+
+* Use eval rather than monkey-patching fs.
+* readdir: Always sort the results
+* win32: requeue a file if error has an OK status
+
+### v2.0
+
+* A return to monkey patching
+* wrap process.cwd
+
+### v1.1
+
+* wrap readFile
+* Wrap fs.writeFile.
+* readdir protection
+* Don't clobber the fs builtin
+* Handle fs.read EAGAIN errors by trying again
+* Expose the curOpen counter
+* No-op lchown/lchmod if not implemented
+* fs.rename patch only for win32
+* Patch fs.rename to handle AV software on Windows
+* Close #4 Chown should not fail on einval or eperm if non-root
+* Fix isaacs/fstream#1 Only wrap fs one time
+* Fix #3 Start at 1024 max files, then back off on EMFILE
+* lutimes that doens't blow up on Linux
+* A full on-rewrite using a queue instead of just swallowing the EMFILE error
+* Wrap Read/Write streams as well
+
+### 1.0
+
+* Update engines for node 0.6
+* Be lstat-graceful on Windows
+* first
diff --git a/fs.js b/fs.js
index ae9fd6f..8ad4a38 100644
--- a/fs.js
+++ b/fs.js
@@ -1,11 +1,21 @@
-// eeeeeevvvvviiiiiiillllll
-// more evil than monkey-patching the native builtin?
-// Not sure.
-
-var mod = require("module")
-var pre = '(function (exports, require, module, __filename, __dirname) { '
-var post = '});'
-var src = pre + process.binding('natives').fs + post
-var vm = require('vm')
-var fn = vm.runInThisContext(src)
-return fn(exports, require, module, __filename, __dirname)
+'use strict'
+
+var fs = require('fs')
+
+module.exports = clone(fs)
+
+function clone (obj) {
+  if (obj === null || typeof obj !== 'object')
+    return obj
+
+  if (obj instanceof Object)
+    var copy = { __proto__: obj.__proto__ }
+  else
+    var copy = Object.create(null)
+
+  Object.getOwnPropertyNames(obj).forEach(function (key) {
+    Object.defineProperty(copy, key, Object.getOwnPropertyDescriptor(obj, key))
+  })
+
+  return copy
+}
diff --git a/graceful-fs.js b/graceful-fs.js
index 77fc702..33b30d2 100644
--- a/graceful-fs.js
+++ b/graceful-fs.js
@@ -1,11 +1,7 @@
-// Monkey-patching the fs module.
-// It's ugly, but there is simply no other way to do this.
-var fs = module.exports = require('./fs.js')
-
-var assert = require('assert')
-
-// fix up some busted stuff, mostly on windows and old nodes
-require('./polyfills.js')
+var fs = require('fs')
+var polyfills = require('./polyfills.js')
+var legacy = require('./legacy-streams.js')
+var queue = []
 
 var util = require('util')
 
@@ -13,146 +9,254 @@ function noop () {}
 
 var debug = noop
 if (util.debuglog)
-  debug = util.debuglog('gfs')
-else if (/\bgfs\b/i.test(process.env.NODE_DEBUG || ''))
+  debug = util.debuglog('gfs4')
+else if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || ''))
   debug = function() {
     var m = util.format.apply(util, arguments)
-    m = 'GFS: ' + m.split(/\n/).join('\nGFS: ')
+    m = 'GFS4: ' + m.split(/\n/).join('\nGFS4: ')
     console.error(m)
   }
 
-if (/\bgfs\b/i.test(process.env.NODE_DEBUG || '')) {
+if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) {
   process.on('exit', function() {
-    debug('fds', fds)
     debug(queue)
-    assert.equal(queue.length, 0)
+    require('assert').equal(queue.length, 0)
   })
 }
 
-
-var originalOpen = fs.open
-fs.open = open
-
-function open(path, flags, mode, cb) {
-  if (typeof mode === "function") cb = mode, mode = null
-  if (typeof cb !== "function") cb = noop
-  new OpenReq(path, flags, mode, cb)
-}
-
-function OpenReq(path, flags, mode, cb) {
-  this.path = path
-  this.flags = flags
-  this.mode = mode
-  this.cb = cb
-  Req.call(this)
-}
-
-util.inherits(OpenReq, Req)
-
-OpenReq.prototype.process = function() {
-  originalOpen.call(fs, this.path, this.flags, this.mode, this.done)
-}
-
-var fds = {}
-OpenReq.prototype.done = function(er, fd) {
-  debug('open done', er, fd)
-  if (fd)
-    fds['fd' + fd] = this.path
-  Req.prototype.done.call(this, er, fd)
+module.exports = patch(require('./fs.js'))
+if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH) {
+  module.exports = patch(fs)
 }
 
+// Always patch fs.close/closeSync, because we want to
+// retry() whenever a close happens *anywhere* in the program.
+// This is essential when multiple graceful-fs instances are
+// in play at the same time.
+module.exports.close =
+fs.close = (function (fs$close) { return function (fd, cb) {
+  return fs$close.call(fs, fd, function (err) {
+    if (!err)
+      retry()
+
+    if (typeof cb === 'function')
+      cb.apply(this, arguments)
+  })
+}})(fs.close)
+
+module.exports.closeSync =
+fs.closeSync = (function (fs$closeSync) { return function (fd) {
+  // Note that graceful-fs also retries when fs.closeSync() fails.
+  // Looks like a bug to me, although it's probably a harmless one.
+  var rval = fs$closeSync.apply(fs, arguments)
+  retry()
+  return rval
+}})(fs.closeSync)
+
+function patch (fs) {
+  // Everything that references the open() function needs to be in here
+  polyfills(fs)
+  fs.gracefulify = patch
+  fs.FileReadStream = ReadStream;  // Legacy name.
+  fs.FileWriteStream = WriteStream;  // Legacy name.
+  fs.createReadStream = createReadStream
+  fs.createWriteStream = createWriteStream
+  var fs$readFile = fs.readFile
+  fs.readFile = readFile
+  function readFile (path, options, cb) {
+    if (typeof options === 'function')
+      cb = options, options = null
+
+    return go$readFile(path, options, cb)
+
+    function go$readFile (path, options, cb) {
+      return fs$readFile(path, options, function (err) {
+        if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
+          enqueue([go$readFile, [path, options, cb]])
+        else {
+          if (typeof cb === 'function')
+            cb.apply(this, arguments)
+          retry()
+        }
+      })
+    }
+  }
 
-var originalReaddir = fs.readdir
-fs.readdir = readdir
-
-function readdir(path, cb) {
-  if (typeof cb !== "function") cb = noop
-  new ReaddirReq(path, cb)
-}
+  var fs$writeFile = fs.writeFile
+  fs.writeFile = writeFile
+  function writeFile (path, data, options, cb) {
+    if (typeof options === 'function')
+      cb = options, options = null
+
+    return go$writeFile(path, data, options, cb)
+
+    function go$writeFile (path, data, options, cb) {
+      return fs$writeFile(path, data, options, function (err) {
+        if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
+          enqueue([go$writeFile, [path, data, options, cb]])
+        else {
+          if (typeof cb === 'function')
+            cb.apply(this, arguments)
+          retry()
+        }
+      })
+    }
+  }
 
-function ReaddirReq(path, cb) {
-  this.path = path
-  this.cb = cb
-  Req.call(this)
-}
+  var fs$appendFile = fs.appendFile
+  if (fs$appendFile)
+    fs.appendFile = appendFile
+  function appendFile (path, data, options, cb) {
+    if (typeof options === 'function')
+      cb = options, options = null
+
+    return go$appendFile(path, data, options, cb)
+
+    function go$appendFile (path, data, options, cb) {
+      return fs$appendFile(path, data, options, function (err) {
+        if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
+          enqueue([go$appendFile, [path, data, options, cb]])
+        else {
+          if (typeof cb === 'function')
+            cb.apply(this, arguments)
+          retry()
+        }
+      })
+    }
+  }
 
-util.inherits(ReaddirReq, Req)
+  var fs$readdir = fs.readdir
+  fs.readdir = readdir
+  function readdir (path, options, cb) {
+    var args = [path]
+    if (typeof options !== 'function') {
+      args.push(options)
+    } else {
+      cb = options
+    }
+    args.push(go$readdir$cb)
+
+    return go$readdir(args)
+
+    function go$readdir$cb (err, files) {
+      if (files && files.sort)
+        files.sort()
+
+      if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
+        enqueue([go$readdir, [args]])
+      else {
+        if (typeof cb === 'function')
+          cb.apply(this, arguments)
+        retry()
+      }
+    }
+  }
 
-ReaddirReq.prototype.process = function() {
-  originalReaddir.call(fs, this.path, this.done)
-}
+  function go$readdir (args) {
+    return fs$readdir.apply(fs, args)
+  }
 
-ReaddirReq.prototype.done = function(er, files) {
-  if (files && files.sort)
-    files = files.sort()
-  Req.prototype.done.call(this, er, files)
-  onclose()
-}
+  if (process.version.substr(0, 4) === 'v0.8') {
+    var legStreams = legacy(fs)
+    ReadStream = legStreams.ReadStream
+    WriteStream = legStreams.WriteStream
+  }
 
+  var fs$ReadStream = fs.ReadStream
+  ReadStream.prototype = Object.create(fs$ReadStream.prototype)
+  ReadStream.prototype.open = ReadStream$open
 
-var originalClose = fs.close
-fs.close = close
+  var fs$WriteStream = fs.WriteStream
+  WriteStream.prototype = Object.create(fs$WriteStream.prototype)
+  WriteStream.prototype.open = WriteStream$open
 
-function close (fd, cb) {
-  debug('close', fd)
-  if (typeof cb !== "function") cb = noop
-  delete fds['fd' + fd]
-  originalClose.call(fs, fd, function(er) {
-    onclose()
-    cb(er)
-  })
-}
+  fs.ReadStream = ReadStream
+  fs.WriteStream = WriteStream
 
+  function ReadStream (path, options) {
+    if (this instanceof ReadStream)
+      return fs$ReadStream.apply(this, arguments), this
+    else
+      return ReadStream.apply(Object.create(ReadStream.prototype), arguments)
+  }
 
-var originalCloseSync = fs.closeSync
-fs.closeSync = closeSync
+  function ReadStream$open () {
+    var that = this
+    open(that.path, that.flags, that.mode, function (err, fd) {
+      if (err) {
+        if (that.autoClose)
+          that.destroy()
+
+        that.emit('error', err)
+      } else {
+        that.fd = fd
+        that.emit('open', fd)
+        that.read()
+      }
+    })
+  }
 
-function closeSync (fd) {
-  try {
-    return originalCloseSync(fd)
-  } finally {
-    onclose()
+  function WriteStream (path, options) {
+    if (this instanceof WriteStream)
+      return fs$WriteStream.apply(this, arguments), this
+    else
+      return WriteStream.apply(Object.create(WriteStream.prototype), arguments)
   }
-}
 
+  function WriteStream$open () {
+    var that = this
+    open(that.path, that.flags, that.mode, function (err, fd) {
+      if (err) {
+        that.destroy()
+        that.emit('error', err)
+      } else {
+        that.fd = fd
+        that.emit('open', fd)
+      }
+    })
+  }
 
-// Req class
-function Req () {
-  // start processing
-  this.done = this.done.bind(this)
-  this.failures = 0
-  this.process()
-}
+  function createReadStream (path, options) {
+    return new ReadStream(path, options)
+  }
 
-Req.prototype.done = function (er, result) {
-  var tryAgain = false
-  if (er) {
-    var code = er.code
-    var tryAgain = code === "EMFILE"
-    if (process.platform === "win32")
-      tryAgain = tryAgain || code === "OK"
+  function createWriteStream (path, options) {
+    return new WriteStream(path, options)
   }
 
-  if (tryAgain) {
-    this.failures ++
-    enqueue(this)
-  } else {
-    var cb = this.cb
-    cb(er, result)
+  var fs$open = fs.open
+  fs.open = open
+  function open (path, flags, mode, cb) {
+    if (typeof mode === 'function')
+      cb = mode, mode = null
+
+    return go$open(path, flags, mode, cb)
+
+    function go$open (path, flags, mode, cb) {
+      return fs$open(path, flags, mode, function (err, fd) {
+        if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
+          enqueue([go$open, [path, flags, mode, cb]])
+        else {
+          if (typeof cb === 'function')
+            cb.apply(this, arguments)
+          retry()
+        }
+      })
+    }
   }
-}
 
-var queue = []
+  return fs
+}
 
-function enqueue(req) {
-  queue.push(req)
-  debug('enqueue %d %s', queue.length, req.constructor.name, req)
+function enqueue (elem) {
+  debug('ENQUEUE', elem[0].name, elem[1])
+  queue.push(elem)
 }
 
-function onclose() {
-  var req = queue.shift()
-  if (req) {
-    debug('process', req.constructor.name, req)
-    req.process()
+function retry () {
+  var elem = queue.shift()
+  if (elem) {
+    debug('RETRY', elem[0].name, elem[1])
+    elem[0].apply(null, elem[1])
   }
 }
diff --git a/legacy-streams.js b/legacy-streams.js
new file mode 100644
index 0000000..d617b50
--- /dev/null
+++ b/legacy-streams.js
@@ -0,0 +1,118 @@
+var Stream = require('stream').Stream
+
+module.exports = legacy
+
+function legacy (fs) {
+  return {
+    ReadStream: ReadStream,
+    WriteStream: WriteStream
+  }
+
+  function ReadStream (path, options) {
+    if (!(this instanceof ReadStream)) return new ReadStream(path, options);
+
+    Stream.call(this);
+
+    var self = this;
+
+    this.path = path;
+    this.fd = null;
+    this.readable = true;
+    this.paused = false;
+
+    this.flags = 'r';
+    this.mode = 438; /*=0666*/
+    this.bufferSize = 64 * 1024;
+
+    options = options || {};
+
+    // Mixin options into this
+    var keys = Object.keys(options);
+    for (var index = 0, length = keys.length; index < length; index++) {
+      var key = keys[index];
+      this[key] = options[key];
+    }
+
+    if (this.encoding) this.setEncoding(this.encoding);
+
+    if (this.start !== undefined) {
+      if ('number' !== typeof this.start) {
+        throw TypeError('start must be a Number');
+      }
+      if (this.end === undefined) {
+        this.end = Infinity;
+      } else if ('number' !== typeof this.end) {
+        throw TypeError('end must be a Number');
+      }
+
+      if (this.start > this.end) {
+        throw new Error('start must be <= end');
+      }
+
+      this.pos = this.start;
+    }
+
+    if (this.fd !== null) {
+      process.nextTick(function() {
+        self._read();
+      });
+      return;
+    }
+
+    fs.open(this.path, this.flags, this.mode, function (err, fd) {
+      if (err) {
+        self.emit('error', err);
+        self.readable = false;
+        return;
+      }
+
+      self.fd = fd;
+      self.emit('open', fd);
+      self._read();
+    })
+  }
+
+  function WriteStream (path, options) {
+    if (!(this instanceof WriteStream)) return new WriteStream(path, options);
+
+    Stream.call(this);
+
+    this.path = path;
+    this.fd = null;
+    this.writable = true;
+
+    this.flags = 'w';
+    this.encoding = 'binary';
+    this.mode = 438; /*=0666*/
+    this.bytesWritten = 0;
+
+    options = options || {};
+
+    // Mixin options into this
+    var keys = Object.keys(options);
+    for (var index = 0, length = keys.length; index < length; index++) {
+      var key = keys[index];
+      this[key] = options[key];
+    }
+
+    if (this.start !== undefined) {
+      if ('number' !== typeof this.start) {
+        throw TypeError('start must be a Number');
+      }
+      if (this.start < 0) {
+        throw new Error('start must be >= zero');
+      }
+
+      this.pos = this.start;
+    }
+
+    this.busy = false;
+    this._queue = [];
+
+    if (this.fd === null) {
+      this._open = fs.open;
+      this._queue.push([this._open, this.path, this.flags, this.mode, undefined]);
+      this.flush();
+    }
+  }
+}
diff --git a/package.json b/package.json
index b15f91f..d8e11a5 100644
--- a/package.json
+++ b/package.json
@@ -1,11 +1,10 @@
 {
-  "author": "Isaac Z. Schlueter <i at izs.me> (http://blog.izs.me)",
   "name": "graceful-fs",
   "description": "A drop-in replacement for fs, making various improvements.",
-  "version": "3.0.2",
+  "version": "4.1.9",
   "repository": {
     "type": "git",
-    "url": "git://github.com/isaacs/node-graceful-fs.git"
+    "url": "https://github.com/isaacs/node-graceful-fs"
   },
   "main": "graceful-fs.js",
   "engines": {
@@ -15,7 +14,7 @@
     "test": "test"
   },
   "scripts": {
-    "test": "tap test/*.js"
+    "test": "node test.js | tap -"
   },
   "keywords": [
     "fs",
@@ -33,5 +32,16 @@
     "EPERM",
     "EACCESS"
   ],
-  "license": "BSD"
+  "license": "ISC",
+  "devDependencies": {
+    "mkdirp": "^0.5.0",
+    "rimraf": "^2.2.8",
+    "tap": "^5.4.2"
+  },
+  "files": [
+    "fs.js",
+    "graceful-fs.js",
+    "legacy-streams.js",
+    "polyfills.js"
+  ]
 }
diff --git a/polyfills.js b/polyfills.js
index 9d62af5..cf474df 100644
--- a/polyfills.js
+++ b/polyfills.js
@@ -8,33 +8,140 @@ process.cwd = function() {
     cwd = origCwd.call(process)
   return cwd
 }
+try {
+  process.cwd()
+} catch (er) {}
+
 var chdir = process.chdir
 process.chdir = function(d) {
   cwd = null
   chdir.call(process, d)
 }
 
-// (re-)implement some things that are known busted or missing.
+module.exports = patch
+
+function patch (fs) {
+  // (re-)implement some things that are known busted or missing.
+
+  // lchmod, broken prior to 0.6.2
+  // back-port the fix here.
+  if (constants.hasOwnProperty('O_SYMLINK') &&
+      process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
+    patchLchmod(fs)
+  }
+
+  // lutimes implementation, or no-op
+  if (!fs.lutimes) {
+    patchLutimes(fs)
+  }
+
+  // https://github.com/isaacs/node-graceful-fs/issues/4
+  // Chown should not fail on einval or eperm if non-root.
+  // It should not fail on enosys ever, as this just indicates
+  // that a fs doesn't support the intended operation.
+
+  fs.chown = chownFix(fs.chown)
+  fs.fchown = chownFix(fs.fchown)
+  fs.lchown = chownFix(fs.lchown)
+
+  fs.chmod = chmodFix(fs.chmod)
+  fs.fchmod = chmodFix(fs.fchmod)
+  fs.lchmod = chmodFix(fs.lchmod)
+
+  fs.chownSync = chownFixSync(fs.chownSync)
+  fs.fchownSync = chownFixSync(fs.fchownSync)
+  fs.lchownSync = chownFixSync(fs.lchownSync)
+
+  fs.chmodSync = chmodFixSync(fs.chmodSync)
+  fs.fchmodSync = chmodFixSync(fs.fchmodSync)
+  fs.lchmodSync = chmodFixSync(fs.lchmodSync)
+
+  fs.stat = statFix(fs.stat)
+  fs.fstat = statFix(fs.fstat)
+  fs.lstat = statFix(fs.lstat)
+
+  fs.statSync = statFixSync(fs.statSync)
+  fs.fstatSync = statFixSync(fs.fstatSync)
+  fs.lstatSync = statFixSync(fs.lstatSync)
+
+  // if lchmod/lchown do not exist, then make them no-ops
+  if (!fs.lchmod) {
+    fs.lchmod = function (path, mode, cb) {
+      if (cb) process.nextTick(cb)
+    }
+    fs.lchmodSync = function () {}
+  }
+  if (!fs.lchown) {
+    fs.lchown = function (path, uid, gid, cb) {
+      if (cb) process.nextTick(cb)
+    }
+    fs.lchownSync = function () {}
+  }
+
+  // on Windows, A/V software can lock the directory, causing this
+  // to fail with an EACCES or EPERM if the directory contains newly
+  // created files.  Try again on failure, for up to 1 second.
+  if (process.platform === "win32") {
+    fs.rename = (function (fs$rename) { return function (from, to, cb) {
+      var start = Date.now()
+      fs$rename(from, to, function CB (er) {
+        if (er
+            && (er.code === "EACCES" || er.code === "EPERM")
+            && Date.now() - start < 1000) {
+          return fs$rename(from, to, CB)
+        }
+        if (cb) cb(er)
+      })
+    }})(fs.rename)
+  }
+
+  // if read() returns EAGAIN, then just try it again.
+  fs.read = (function (fs$read) { return function (fd, buffer, offset, length, position, callback_) {
+    var callback
+    if (callback_ && typeof callback_ === 'function') {
+      var eagCounter = 0
+      callback = function (er, _, __) {
+        if (er && er.code === 'EAGAIN' && eagCounter < 10) {
+          eagCounter ++
+          return fs$read.call(fs, fd, buffer, offset, length, position, callback)
+        }
+        callback_.apply(this, arguments)
+      }
+    }
+    return fs$read.call(fs, fd, buffer, offset, length, position, callback)
+  }})(fs.read)
+
+  fs.readSync = (function (fs$readSync) { return function (fd, buffer, offset, length, position) {
+    var eagCounter = 0
+    while (true) {
+      try {
+        return fs$readSync.call(fs, fd, buffer, offset, length, position)
+      } catch (er) {
+        if (er.code === 'EAGAIN' && eagCounter < 10) {
+          eagCounter ++
+          continue
+        }
+        throw er
+      }
+    }
+  }})(fs.readSync)
+}
 
-// lchmod, broken prior to 0.6.2
-// back-port the fix here.
-if (constants.hasOwnProperty('O_SYMLINK') &&
-    process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
+function patchLchmod (fs) {
   fs.lchmod = function (path, mode, callback) {
-    callback = callback || noop
     fs.open( path
            , constants.O_WRONLY | constants.O_SYMLINK
            , mode
            , function (err, fd) {
       if (err) {
-        callback(err)
+        if (callback) callback(err)
         return
       }
       // prefer to return the chmod error, if one occurs,
       // but still try to close, and report closing errors if they occur.
       fs.fchmod(fd, mode, function (err) {
         fs.close(fd, function(err2) {
-          callback(err || err2)
+          if (callback) callback(err || err2)
         })
       })
     })
@@ -45,33 +152,35 @@ if (constants.hasOwnProperty('O_SYMLINK') &&
 
     // prefer to return the chmod error, if one occurs,
     // but still try to close, and report closing errors if they occur.
-    var err, err2
+    var threw = true
+    var ret
     try {
-      var ret = fs.fchmodSync(fd, mode)
-    } catch (er) {
-      err = er
-    }
-    try {
-      fs.closeSync(fd)
-    } catch (er) {
-      err2 = er
+      ret = fs.fchmodSync(fd, mode)
+      threw = false
+    } finally {
+      if (threw) {
+        try {
+          fs.closeSync(fd)
+        } catch (er) {}
+      } else {
+        fs.closeSync(fd)
+      }
     }
-    if (err || err2) throw (err || err2)
     return ret
   }
 }
 
-
-// lutimes implementation, or no-op
-if (!fs.lutimes) {
+function patchLutimes (fs) {
   if (constants.hasOwnProperty("O_SYMLINK")) {
     fs.lutimes = function (path, at, mt, cb) {
       fs.open(path, constants.O_SYMLINK, function (er, fd) {
-        cb = cb || noop
-        if (er) return cb(er)
+        if (er) {
+          if (cb) cb(er)
+          return
+        }
         fs.futimes(fd, at, mt, function (er) {
           fs.close(fd, function (er2) {
-            return cb(er || er2)
+            if (cb) cb(er || er2)
           })
         })
       })
@@ -79,68 +188,57 @@ if (!fs.lutimes) {
 
     fs.lutimesSync = function (path, at, mt) {
       var fd = fs.openSync(path, constants.O_SYMLINK)
-        , err
-        , err2
-        , ret
-
+      var ret
+      var threw = true
       try {
-        var ret = fs.futimesSync(fd, at, mt)
-      } catch (er) {
-        err = er
+        ret = fs.futimesSync(fd, at, mt)
+        threw = false
+      } finally {
+        if (threw) {
+          try {
+            fs.closeSync(fd)
+          } catch (er) {}
+        } else {
+          fs.closeSync(fd)
+        }
       }
-      try {
-        fs.closeSync(fd)
-      } catch (er) {
-        err2 = er
-      }
-      if (err || err2) throw (err || err2)
       return ret
     }
 
-  } else if (fs.utimensat && constants.hasOwnProperty("AT_SYMLINK_NOFOLLOW")) {
-    // maybe utimensat will be bound soonish?
-    fs.lutimes = function (path, at, mt, cb) {
-      fs.utimensat(path, at, mt, constants.AT_SYMLINK_NOFOLLOW, cb)
-    }
-
-    fs.lutimesSync = function (path, at, mt) {
-      return fs.utimensatSync(path, at, mt, constants.AT_SYMLINK_NOFOLLOW)
-    }
-
   } else {
-    fs.lutimes = function (_a, _b, _c, cb) { process.nextTick(cb) }
+    fs.lutimes = function (_a, _b, _c, cb) { if (cb) process.nextTick(cb) }
     fs.lutimesSync = function () {}
   }
 }
 
+function chmodFix (orig) {
+  if (!orig) return orig
+  return function (target, mode, cb) {
+    return orig.call(fs, target, mode, function (er) {
+      if (chownErOk(er)) er = null
+      if (cb) cb.apply(this, arguments)
+    })
+  }
+}
 
-// https://github.com/isaacs/node-graceful-fs/issues/4
-// Chown should not fail on einval or eperm if non-root.
-// It should not fail on enosys ever, as this just indicates
-// that a fs doesn't support the intended operation.
-
-fs.chown = chownFix(fs.chown)
-fs.fchown = chownFix(fs.fchown)
-fs.lchown = chownFix(fs.lchown)
-
-fs.chmod = chownFix(fs.chmod)
-fs.fchmod = chownFix(fs.fchmod)
-fs.lchmod = chownFix(fs.lchmod)
-
-fs.chownSync = chownFixSync(fs.chownSync)
-fs.fchownSync = chownFixSync(fs.fchownSync)
-fs.lchownSync = chownFixSync(fs.lchownSync)
+function chmodFixSync (orig) {
+  if (!orig) return orig
+  return function (target, mode) {
+    try {
+      return orig.call(fs, target, mode)
+    } catch (er) {
+      if (!chownErOk(er)) throw er
+    }
+  }
+}
 
-fs.chmodSync = chownFix(fs.chmodSync)
-fs.fchmodSync = chownFix(fs.fchmodSync)
-fs.lchmodSync = chownFix(fs.lchmodSync)
 
 function chownFix (orig) {
   if (!orig) return orig
   return function (target, uid, gid, cb) {
-    return orig.call(fs, target, uid, gid, function (er, res) {
+    return orig.call(fs, target, uid, gid, function (er) {
       if (chownErOk(er)) er = null
-      cb(er, res)
+      if (cb) cb.apply(this, arguments)
     })
   }
 }
@@ -156,6 +254,33 @@ function chownFixSync (orig) {
   }
 }
 
+
+function statFix (orig) {
+  if (!orig) return orig
+  // Older versions of Node erroneously returned signed integers for
+  // uid + gid.
+  return function (target, cb) {
+    return orig.call(fs, target, function (er, stats) {
+      if (!stats) return cb.apply(this, arguments)
+      if (stats.uid < 0) stats.uid += 0x100000000
+      if (stats.gid < 0) stats.gid += 0x100000000
+      if (cb) cb.apply(this, arguments)
+    })
+  }
+}
+
+function statFixSync (orig) {
+  if (!orig) return orig
+  // Older versions of Node erroneously returned signed integers for
+  // uid + gid.
+  return function (target) {
+    var stats = orig.call(fs, target)
+    if (stats.uid < 0) stats.uid += 0x100000000
+    if (stats.gid < 0) stats.gid += 0x100000000
+    return stats;
+  }
+}
+
 // ENOSYS means that the fs doesn't support the op. Just ignore
 // that, because it doesn't matter.
 //
@@ -183,73 +308,3 @@ function chownErOk (er) {
 
   return false
 }
-
-
-// if lchmod/lchown do not exist, then make them no-ops
-if (!fs.lchmod) {
-  fs.lchmod = function (path, mode, cb) {
-    process.nextTick(cb)
-  }
-  fs.lchmodSync = function () {}
-}
-if (!fs.lchown) {
-  fs.lchown = function (path, uid, gid, cb) {
-    process.nextTick(cb)
-  }
-  fs.lchownSync = function () {}
-}
-
-
-
-// on Windows, A/V software can lock the directory, causing this
-// to fail with an EACCES or EPERM if the directory contains newly
-// created files.  Try again on failure, for up to 1 second.
-if (process.platform === "win32") {
-  var rename_ = fs.rename
-  fs.rename = function rename (from, to, cb) {
-    var start = Date.now()
-    rename_(from, to, function CB (er) {
-      if (er
-          && (er.code === "EACCES" || er.code === "EPERM")
-          && Date.now() - start < 1000) {
-        return rename_(from, to, CB)
-      }
-      cb(er)
-    })
-  }
-}
-
-
-// if read() returns EAGAIN, then just try it again.
-var read = fs.read
-fs.read = function (fd, buffer, offset, length, position, callback_) {
-  var callback
-  if (callback_ && typeof callback_ === 'function') {
-    var eagCounter = 0
-    callback = function (er, _, __) {
-      if (er && er.code === 'EAGAIN' && eagCounter < 10) {
-        eagCounter ++
-        return read.call(fs, fd, buffer, offset, length, position, callback)
-      }
-      callback_.apply(this, arguments)
-    }
-  }
-  return read.call(fs, fd, buffer, offset, length, position, callback)
-}
-
-var readSync = fs.readSync
-fs.readSync = function (fd, buffer, offset, length, position) {
-  var eagCounter = 0
-  while (true) {
-    try {
-      return readSync.call(fs, fd, buffer, offset, length, position)
-    } catch (er) {
-      if (er.code === 'EAGAIN' && eagCounter < 10) {
-        eagCounter ++
-        continue
-      }
-      throw er
-    }
-  }
-}
-
diff --git a/test.js b/test.js
new file mode 100644
index 0000000..59e8fe8
--- /dev/null
+++ b/test.js
@@ -0,0 +1,24 @@
+var spawn = require('child_process').spawn
+var fs = require('fs')
+var tap = require('tap')
+var dir = __dirname + '/test'
+var node = process.execPath
+
+var files = fs.readdirSync(dir)
+var env = Object.keys(process.env).reduce(function (env, k) {
+  env[k] = process.env[k]
+  return env
+}, {
+  TEST_GRACEFUL_FS_GLOBAL_PATCH: 1
+})
+
+files.filter(function (f) {
+  if (/\.js$/.test(f) && fs.statSync(dir + '/' + f).isFile()) {
+    tap.spawn(node, ['test/' + f])
+    return true
+  }
+}).forEach(function (f) {
+  tap.spawn(node, ['test/' + f], {
+    env: env
+  }, '🐵  test/' + f)
+})
diff --git a/test/chown-er-ok.js b/test/chown-er-ok.js
new file mode 100644
index 0000000..3096652
--- /dev/null
+++ b/test/chown-er-ok.js
@@ -0,0 +1,53 @@
+var t = require('tap')
+var realFs = require('fs')
+
+var methods = ['chown', 'chownSync', 'chmod', 'chmodSync']
+methods.forEach(function (method) {
+  causeErr(method, realFs[method])
+})
+
+function causeErr (method, original) {
+  realFs[method] = function (path) {
+    var err = makeErr(path, method)
+    if (!/Sync$/.test(method)) {
+      var cb = arguments[arguments.length - 1]
+      process.nextTick(cb.bind(null, err))
+    } else {
+      throw err
+    }
+  }
+}
+
+function makeErr (path, method) {
+  var err = new Error('this is fine')
+  err.syscall = method.replace(/Sync$/, '')
+  err.code = path.toUpperCase()
+  return err
+}
+
+var fs = require('../')
+
+var errs = ['ENOSYS', 'EINVAL', 'EPERM']
+t.plan(errs.length * methods.length)
+
+errs.forEach(function (err) {
+  methods.forEach(function (method) {
+    var args = [err]
+    if (/chmod/.test(method)) {
+      args.push('some mode')
+    } else {
+      args.push('some uid', 'some gid')
+    }
+
+    if (method.match(/Sync$/)) {
+      t.doesNotThrow(function () {
+        fs[method].apply(fs, args)
+      })
+    } else {
+      args.push(function (err) {
+        t.notOk(err)
+      })
+      fs[method].apply(fs, args)
+    }
+  })
+})
diff --git a/test/close.js b/test/close.js
new file mode 100644
index 0000000..848e2c4
--- /dev/null
+++ b/test/close.js
@@ -0,0 +1,10 @@
+var test = require('tap').test
+var fs$close = require('fs').close;
+var fs$closeSync = require('fs').closeSync;
+var fs = require('../');
+
+test('`close` is patched correctly', function(t) {
+  t.notEqual(fs.close, fs$close, 'patch close');
+  t.notEqual(fs.closeSync, fs$closeSync, 'patch closeSync');
+  t.end();
+})
diff --git a/test/enoent.js b/test/enoent.js
new file mode 100644
index 0000000..10ba417
--- /dev/null
+++ b/test/enoent.js
@@ -0,0 +1,46 @@
+// this test makes sure that various things get enoent, instead of
+// some other kind of throw.
+
+var t = require('tap')
+var g = require('../')
+var file = 'this file does not exist even a little bit'
+var methods = [
+  ['open', 'r'],
+  ['readFile'],
+  ['stat'],
+  ['lstat'],
+  ['utimes', new Date(), new Date()],
+  ['readdir']
+]
+
+// any version > v6 can do readdir(path, options, cb)
+if (process.version.match(/^v([6-9]|[1-9][0-9])\./)) {
+  methods.push(['readdir', {}])
+}
+
+t.plan(methods.length)
+methods.forEach(function (method) {
+  t.test(method[0], runTest(method))
+})
+
+function runTest (args) { return function (t) {
+  var method = args.shift()
+  args.unshift(file)
+  var methodSync = method + 'Sync'
+  t.isa(g[methodSync], 'function')
+  t.throws(function () {
+    g[methodSync].apply(g, args)
+  }, { code: 'ENOENT' })
+  // add the callback
+  args.push(verify(t))
+  t.isa(g[method], 'function')
+  t.doesNotThrow(function () {
+    g[method].apply(g, args)
+  })
+}}
+
+function verify (t) { return function (er) {
+  t.isa(er, Error)
+  t.equal(er.code, 'ENOENT')
+  t.end()
+}}
diff --git a/test/max-open.js b/test/max-open.js
new file mode 100644
index 0000000..5c3babc
--- /dev/null
+++ b/test/max-open.js
@@ -0,0 +1,68 @@
+var test = require('tap').test
+var fs = require('../')
+
+test('open lots of stuff', function (t) {
+  // Get around EBADF from libuv by making sure that stderr is opened
+  // Otherwise Darwin will refuse to give us a FD for stderr!
+  process.stderr.write('')
+
+  // How many parallel open()'s to do
+  var n = 1024
+  var opens = 0
+  var fds = []
+  var going = true
+  var closing = false
+  var doneCalled = 0
+
+  for (var i = 0; i < n; i++) {
+    go()
+  }
+
+  function go() {
+    opens++
+    fs.open(__filename, 'r', function (er, fd) {
+      if (er) throw er
+      fds.push(fd)
+      if (going) go()
+    })
+  }
+
+  // should hit ulimit pretty fast
+  setTimeout(function () {
+    going = false
+    t.equal(opens - fds.length, n)
+    done()
+  }, 100)
+
+
+  function done () {
+    if (closing) return
+    doneCalled++
+
+    if (fds.length === 0) {
+      // First because of the timeout
+      // Then to close the fd's opened afterwards
+      // Then this time, to complete.
+      // Might take multiple passes, depending on CPU speed
+      // and ulimit, but at least 3 in every case.
+      t.ok(doneCalled >= 2)
+      return t.end()
+    }
+
+    closing = true
+    setTimeout(function () {
+      // console.error('do closing again')
+      closing = false
+      done()
+    }, 100)
+
+    // console.error('closing time')
+    var closes = fds.slice(0)
+    fds.length = 0
+    closes.forEach(function (fd) {
+      fs.close(fd, function (er) {
+        if (er) throw er
+      })
+    })
+  }
+})
diff --git a/test/open.js b/test/open.js
index 85732f2..b76014d 100644
--- a/test/open.js
+++ b/test/open.js
@@ -1,10 +1,5 @@
 var test = require('tap').test
-var fs = require('../graceful-fs.js')
-
-test('graceful fs is monkeypatched fs', function (t) {
-  t.equal(fs, require('../fs.js'))
-  t.end()
-})
+var fs = require('../')
 
 test('open an existing file works', function (t) {
   var fd = fs.openSync(__filename, 'r')
diff --git a/test/read-write-stream.js b/test/read-write-stream.js
new file mode 100644
index 0000000..c6511cb
--- /dev/null
+++ b/test/read-write-stream.js
@@ -0,0 +1,51 @@
+'use strict'
+
+var fs = require('../')
+var rimraf = require('rimraf')
+var mkdirp = require('mkdirp')
+var test = require('tap').test
+var p = require('path').resolve(__dirname, 'files')
+
+process.chdir(__dirname)
+
+// Make sure to reserve the stderr fd
+process.stderr.write('')
+
+var num = 4097
+var paths = new Array(num)
+
+test('write files', function (t) {
+  rimraf.sync(p)
+  mkdirp.sync(p)
+
+  t.plan(num)
+  for (var i = 0; i < num; ++i) {
+    paths[i] = 'files/file-' + i
+    var stream = fs.createWriteStream(paths[i])
+    stream.on('finish', function () {
+      t.pass('success')
+    })
+    stream.write('content')
+    stream.end()
+  }
+})
+
+test('read files', function (t) {
+  // now read them
+  t.plan(num)
+  for (var i = 0; i < num; ++i) (function (i) {
+    var stream = fs.createReadStream(paths[i])
+    var data = ''
+    stream.on('data', function (c) {
+      data += c
+    })
+    stream.on('end', function () {
+      t.equal(data, 'content')
+    })
+  })(i)
+})
+
+test('cleanup', function (t) {
+  rimraf.sync(p)
+  t.end()
+})
diff --git a/test/readdir-options.js b/test/readdir-options.js
new file mode 100644
index 0000000..b937901
--- /dev/null
+++ b/test/readdir-options.js
@@ -0,0 +1,61 @@
+var t = require("tap")
+var fs = require("fs")
+
+var currentTest
+
+var strings = ['b', 'z', 'a']
+var buffs = strings.map(function (s) { return new Buffer(s) })
+var hexes = buffs.map(function (b) { return b.toString('hex') })
+
+function getRet (encoding) {
+  switch (encoding) {
+    case 'hex':
+      return hexes
+    case 'buffer':
+      return buffs
+    default:
+      return strings
+  }
+}
+
+var readdir = fs.readdir
+var failed = false
+fs.readdir = function(path, options, cb) {
+  if (!failed) {
+    // simulate an EMFILE and then open and close a thing to retry
+    failed = true
+    process.nextTick(function () {
+      var er = new Error('synthetic emfile')
+      er.code = 'EMFILE'
+      cb(er)
+      process.nextTick(function () {
+        fs.closeSync(fs.openSync(__filename, 'r'))
+      })
+    })
+    return
+  }
+
+  failed = false
+  currentTest.isa(cb, 'function')
+  currentTest.isa(options, 'object')
+  currentTest.ok(options)
+  process.nextTick(function() {
+    var ret = getRet(options.encoding)
+    cb(null, ret)
+  })
+}
+
+var g = require("../")
+
+var encodings = ['buffer', 'hex', 'utf8', null]
+encodings.forEach(function (enc) {
+  t.test('encoding=' + enc, function (t) {
+    currentTest = t
+    g.readdir("whatevers", { encoding: enc }, function (er, files) {
+      if (er)
+        throw er
+      t.same(files, getRet(enc).sort())
+      t.end()
+    })
+  })
+})
diff --git a/test/readdir-sort.js b/test/readdir-sort.js
index fe005aa..89d3e77 100644
--- a/test/readdir-sort.js
+++ b/test/readdir-sort.js
@@ -1,5 +1,5 @@
 var test = require("tap").test
-var fs = require("../fs.js")
+var fs = require("fs")
 
 var readdir = fs.readdir
 fs.readdir = function(path, cb) {
@@ -14,7 +14,6 @@ test("readdir reorder", function (t) {
   g.readdir("whatevers", function (er, files) {
     if (er)
       throw er
-    console.error(files)
     t.same(files, [ "a", "b", "z" ])
     t.end()
   })
diff --git a/test/readfile.js b/test/readfile.js
new file mode 100644
index 0000000..ce4f04f
--- /dev/null
+++ b/test/readfile.js
@@ -0,0 +1,47 @@
+'use strict'
+
+var fs = require('../')
+var rimraf = require('rimraf')
+var mkdirp = require('mkdirp')
+var test = require('tap').test
+var p = require('path').resolve(__dirname, 'files')
+
+process.chdir(__dirname)
+
+// Make sure to reserve the stderr fd
+process.stderr.write('')
+
+var num = 4097
+var paths = new Array(num)
+
+test('write files', function (t) {
+  rimraf.sync(p)
+  mkdirp.sync(p)
+
+  t.plan(num)
+  for (var i = 0; i < num; ++i) {
+    paths[i] = 'files/file-' + i
+    fs.writeFile(paths[i], 'content', 'ascii', function (er) {
+      if (er)
+        throw er
+      t.pass('written')
+    })
+  }
+})
+
+test('read files', function (t) {
+  // now read them
+  t.plan(num)
+  for (var i = 0; i < num; ++i) {
+    fs.readFile(paths[i], 'ascii', function (er, data) {
+      if (er)
+        throw er
+      t.equal(data, 'content')
+    })
+  }
+})
+
+test('cleanup', function (t) {
+  rimraf.sync(p)
+  t.end()
+})
diff --git a/test/stats-uid-gid.js b/test/stats-uid-gid.js
new file mode 100644
index 0000000..58ce661
--- /dev/null
+++ b/test/stats-uid-gid.js
@@ -0,0 +1,44 @@
+'use strict';
+var test = require('tap').test
+var util = require('util')
+var fs = require('fs')
+
+// mock fs.statSync to return signed uids/gids
+var realStatSync = fs.statSync
+fs.statSync = function(path) {
+  var stats = realStatSync.call(fs, path)
+  stats.uid = -2
+  stats.gid = -2
+  return stats
+}
+
+var gfs = require('../graceful-fs.js')
+
+test('graceful fs uses same stats constructor as fs', function (t) {
+  t.equal(gfs.Stats, fs.Stats, 'should reference the same constructor')
+
+  if (!process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH) {
+    t.equal(fs.statSync(__filename).uid, -2)
+    t.equal(fs.statSync(__filename).gid, -2)
+  }
+
+  t.equal(gfs.statSync(__filename).uid, 0xfffffffe)
+  t.equal(gfs.statSync(__filename).gid, 0xfffffffe)
+
+  t.end()
+})
+
+test('does not throw when async stat fails', function (t) {
+  gfs.stat(__filename + ' this does not exist', function (er, stats) {
+    t.ok(er)
+    t.notOk(stats)
+    t.end()
+  })
+})
+
+test('throws ENOENT when sync stat fails', function (t) {
+  t.throws(function() {
+    gfs.statSync(__filename + ' this does not exist')
+  }, /ENOENT/)
+  t.end()
+})
diff --git a/test/stats.js b/test/stats.js
new file mode 100644
index 0000000..4519012
--- /dev/null
+++ b/test/stats.js
@@ -0,0 +1,12 @@
+var test = require('tap').test
+var fs = require('fs')
+var gfs = require('../graceful-fs.js')
+
+test('graceful fs uses same stats constructor as fs', function (t) {
+  t.equal(gfs.Stats, fs.Stats, 'should reference the same constructor')
+  t.ok(fs.statSync(__filename) instanceof fs.Stats,
+    'should be instance of fs.Stats')
+  t.ok(gfs.statSync(__filename) instanceof fs.Stats,
+    'should be instance of fs.Stats')
+  t.end()
+})
diff --git a/test/write-then-read.js b/test/write-then-read.js
new file mode 100644
index 0000000..3a66df3
--- /dev/null
+++ b/test/write-then-read.js
@@ -0,0 +1,43 @@
+var fs = require('../');
+var rimraf = require('rimraf');
+var mkdirp = require('mkdirp');
+var test = require('tap').test;
+var p = require('path').resolve(__dirname, 'files');
+
+process.chdir(__dirname)
+
+// Make sure to reserve the stderr fd
+process.stderr.write('');
+
+var num = 4097;
+var paths = new Array(num);
+
+test('make files', function (t) {
+  rimraf.sync(p);
+  mkdirp.sync(p);
+
+  for (var i = 0; i < num; ++i) {
+    paths[i] = 'files/file-' + i;
+    fs.writeFileSync(paths[i], 'content');
+  }
+
+  t.end();
+})
+
+test('read files', function (t) {
+  // now read them
+  t.plan(num)
+  for (var i = 0; i < num; ++i) {
+    fs.readFile(paths[i], 'ascii', function(err, data) {
+      if (err)
+        throw err;
+
+      t.equal(data, 'content')
+    });
+  }
+});
+
+test('cleanup', function (t) {
+  rimraf.sync(p);
+  t.end();
+});

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-graceful-fs.git



More information about the Pkg-javascript-commits mailing list