From 039c4670862770d80045336e6d9ac9a75a991699 Mon Sep 17 00:00:00 2001
From: Chunpeng Huo <huocp@me.com>
Date: Mon, 20 Apr 2020 14:16:26 +1000
Subject: [PATCH] fix: support path input from Buffer on many fs APIs

There is missing support of "path <string> | <Buffer> | <URL>" on many fs APIs.

closes #292
---
 lib/binding.js                     |  28 +++++-
 test/helper.js                     |  23 ++++-
 test/lib/fs.access.spec.js         |   4 +
 test/lib/fs.chmod-fchmod.spec.js   |  11 +++
 test/lib/fs.chown-fchown.spec.js   |   4 +
 test/lib/fs.copyFile.spec.js       |  16 ++++
 test/lib/fs.link-symlink.spec.js   |  30 ++++++
 test/lib/fs.lstat.spec.js          |  12 +++
 test/lib/fs.mkdir.spec.js          |  11 +++
 test/lib/fs.open-close.spec.js     |  10 ++
 test/lib/fs.readdir.spec.js        |   9 ++
 test/lib/fs.readlink.spec.js       |  10 ++
 test/lib/fs.rename.spec.js         |  13 +++
 test/lib/fs.rmdir.spec.js          | 147 +++++++++++++++++++++++++----
 test/lib/fs.stat-fstat.spec.js     |  11 +++
 test/lib/fs.unlink.spec.js         |  10 ++
 test/lib/fs.utimes-futimes.spec.js |  14 +++
 17 files changed, 338 insertions(+), 25 deletions(-)

diff --git a/lib/binding.js b/lib/binding.js
index cd8010ee..3c74c0ad 100644
--- a/lib/binding.js
+++ b/lib/binding.js
@@ -164,6 +164,10 @@ function notImplemented() {
   throw new Error('Method not implemented');
 }
 
+function deBuffer(p) {
+  return Buffer.isBuffer(p) ? p.toString() : p;
+}
+
 /**
  * Create a new stats object.
  * @param {Object} config Stats properties.
@@ -342,9 +346,7 @@ Binding.prototype.realpath = function(filepath, encoding, callback, ctx) {
 
   return maybeCallback(normalizeCallback(callback), ctx, this, function() {
     let realPath;
-    if (Buffer.isBuffer(filepath)) {
-      filepath = filepath.toString();
-    }
+    filepath = deBuffer(filepath);
     const resolved = path.resolve(filepath);
     const parts = getPathParts(resolved);
     let item = _system.getRoot();
@@ -450,6 +452,7 @@ Binding.prototype.stat = function(filepath, options, callback, ctx) {
   markSyscall(ctx, 'stat');
 
   return maybeCallback(wrapStatsCallback(callback), ctx, this, function() {
+    filepath = deBuffer(filepath);
     let item = _system.getItem(filepath);
     if (item instanceof SymbolicLink) {
       item = _system.getItem(
@@ -539,6 +542,7 @@ Binding.prototype.open = function(pathname, flags, mode, callback, ctx) {
   markSyscall(ctx, 'open');
 
   return maybeCallback(normalizeCallback(callback), ctx, this, function() {
+    pathname = deBuffer(pathname);
     const descriptor = new FileDescriptor(flags);
     let item = _system.getItem(pathname);
     while (item instanceof SymbolicLink) {
@@ -674,6 +678,8 @@ Binding.prototype.copyFile = function(src, dest, flags, callback, ctx) {
   markSyscall(ctx, 'copyfile');
 
   return maybeCallback(normalizeCallback(callback), ctx, this, function() {
+    src = deBuffer(src);
+    dest = deBuffer(dest);
     const srcFd = this.open(src, constants.O_RDONLY);
 
     try {
@@ -873,6 +879,8 @@ Binding.prototype.rename = function(oldPath, newPath, callback, ctx) {
   markSyscall(ctx, 'rename');
 
   return maybeCallback(normalizeCallback(callback), ctx, this, function() {
+    oldPath = deBuffer(oldPath);
+    newPath = deBuffer(newPath);
     const oldItem = _system.getItem(oldPath);
     if (!oldItem) {
       throw new FSError('ENOENT', oldPath);
@@ -938,6 +946,7 @@ Binding.prototype.readdir = function(
   markSyscall(ctx, 'scandir');
 
   return maybeCallback(normalizeCallback(callback), ctx, this, function() {
+    dirpath = deBuffer(dirpath);
     let dpath = dirpath;
     let dir = _system.getItem(dirpath);
     while (dir instanceof SymbolicLink) {
@@ -990,6 +999,7 @@ Binding.prototype.mkdir = function(pathname, mode, recursive, callback, ctx) {
   markSyscall(ctx, 'mkdir');
 
   return maybeCallback(normalizeCallback(callback), ctx, this, function() {
+    pathname = deBuffer(pathname);
     const item = _system.getItem(pathname);
     if (item) {
       if (recursive && item instanceof Directory) {
@@ -1030,6 +1040,7 @@ Binding.prototype.rmdir = function(pathname, callback, ctx) {
   markSyscall(ctx, 'rmdir');
 
   return maybeCallback(normalizeCallback(callback), ctx, this, function() {
+    pathname = deBuffer(pathname);
     const item = _system.getItem(pathname);
     if (!item) {
       throw new FSError('ENOENT', pathname);
@@ -1158,6 +1169,7 @@ Binding.prototype.chown = function(pathname, uid, gid, callback, ctx) {
   markSyscall(ctx, 'chown');
 
   return maybeCallback(normalizeCallback(callback), ctx, this, function() {
+    pathname = deBuffer(pathname);
     const item = _system.getItem(pathname);
     if (!item) {
       throw new FSError('ENOENT', pathname);
@@ -1197,6 +1209,7 @@ Binding.prototype.chmod = function(pathname, mode, callback, ctx) {
   markSyscall(ctx, 'chmod');
 
   return maybeCallback(normalizeCallback(callback), ctx, this, function() {
+    pathname = deBuffer(pathname);
     const item = _system.getItem(pathname);
     if (!item) {
       throw new FSError('ENOENT', pathname);
@@ -1232,6 +1245,7 @@ Binding.prototype.unlink = function(pathname, callback, ctx) {
   markSyscall(ctx, 'unlink');
 
   return maybeCallback(normalizeCallback(callback), ctx, this, function() {
+    pathname = deBuffer(pathname);
     const item = _system.getItem(pathname);
     if (!item) {
       throw new FSError('ENOENT', pathname);
@@ -1256,6 +1270,7 @@ Binding.prototype.utimes = function(pathname, atime, mtime, callback, ctx) {
   markSyscall(ctx, 'utimes');
 
   return maybeCallback(normalizeCallback(callback), ctx, this, function() {
+    pathname = deBuffer(pathname);
     const item = _system.getItem(pathname);
     if (!item) {
       throw new FSError('ENOENT', pathname);
@@ -1323,6 +1338,8 @@ Binding.prototype.link = function(srcPath, destPath, callback, ctx) {
   markSyscall(ctx, 'link');
 
   return maybeCallback(normalizeCallback(callback), ctx, this, function() {
+    srcPath = deBuffer(srcPath);
+    destPath = deBuffer(destPath);
     const item = _system.getItem(srcPath);
     if (!item) {
       throw new FSError('ENOENT', srcPath);
@@ -1356,6 +1373,8 @@ Binding.prototype.symlink = function(srcPath, destPath, type, callback, ctx) {
   markSyscall(ctx, 'symlink');
 
   return maybeCallback(normalizeCallback(callback), ctx, this, function() {
+    srcPath = deBuffer(srcPath);
+    destPath = deBuffer(destPath);
     if (_system.getItem(destPath)) {
       throw new FSError('EEXIST', destPath);
     }
@@ -1390,6 +1409,7 @@ Binding.prototype.readlink = function(pathname, encoding, callback, ctx) {
   markSyscall(ctx, 'readlink');
 
   return maybeCallback(normalizeCallback(callback), ctx, this, function() {
+    pathname = deBuffer(pathname);
     const link = _system.getItem(pathname);
     if (!link) {
       throw new FSError('ENOENT', pathname);
@@ -1423,6 +1443,7 @@ Binding.prototype.lstat = function(filepath, options, callback, ctx) {
   markSyscall(ctx, 'lstat');
 
   return maybeCallback(wrapStatsCallback(callback), ctx, this, function() {
+    filepath = deBuffer(filepath);
     const item = _system.getItem(filepath);
     if (!item) {
       throw new FSError('ENOENT', filepath);
@@ -1455,6 +1476,7 @@ Binding.prototype.access = function(filepath, mode, callback, ctx) {
   markSyscall(ctx, 'access');
 
   return maybeCallback(normalizeCallback(callback), ctx, this, function() {
+    filepath = deBuffer(filepath);
     let item = _system.getItem(filepath);
     let links = 0;
     while (item instanceof SymbolicLink) {
diff --git a/test/helper.js b/test/helper.js
index 1516504e..a3e0ce34 100644
--- a/test/helper.js
+++ b/test/helper.js
@@ -15,8 +15,27 @@ chai.config.includeStack = true;
  */
 exports.assert = chai.assert;
 
-const TEST = {it: it, xit: xit, describe: describe, xdescribe: xdescribe};
-const NO_TEST = {it: xit, xit: xit, describe: xdescribe, xdescribe: xdescribe};
+function run(func) {
+  func();
+}
+
+function noRun() {}
+
+const TEST = {
+  it: it,
+  xit: xit,
+  describe: describe,
+  xdescribe: xdescribe,
+  run: run
+};
+
+const NO_TEST = {
+  it: xit,
+  xit: xit,
+  describe: xdescribe,
+  xdescribe: xdescribe,
+  run: noRun
+};
 
 exports.inVersion = function(range) {
   if (semver.satisfies(process.version, range)) {
diff --git a/test/lib/fs.access.spec.js b/test/lib/fs.access.spec.js
index 2454e847..9152e2ea 100644
--- a/test/lib/fs.access.spec.js
+++ b/test/lib/fs.access.spec.js
@@ -63,6 +63,10 @@ if (fs.access && fs.accessSync && process.getuid && process.getgid) {
       fs.access('path/to/accessible/file', done);
     });
 
+    it('supports Buffer input', function(done) {
+      fs.access(Buffer.from('path/to/accessible/file'), done);
+    });
+
     withPromise.it('promise works for an accessible file', function(done) {
       fs.promises.access('path/to/accessible/file').then(done, done);
     });
diff --git a/test/lib/fs.chmod-fchmod.spec.js b/test/lib/fs.chmod-fchmod.spec.js
index 2d5976e3..0af239b0 100644
--- a/test/lib/fs.chmod-fchmod.spec.js
+++ b/test/lib/fs.chmod-fchmod.spec.js
@@ -26,6 +26,17 @@ describe('fs.chmod(path, mode, callback)', function() {
     });
   });
 
+  it('supports Buffer input', function(done) {
+    fs.chmod(Buffer.from('file.txt'), parseInt('0664', 8), function(err) {
+      if (err) {
+        return done(err);
+      }
+      const stats = fs.statSync(Buffer.from('file.txt'));
+      assert.equal(stats.mode & parseInt('0777', 8), parseInt('0664', 8));
+      done();
+    });
+  });
+
   withPromise.it('promise changes permissions of a file', function(done) {
     fs.promises.chmod('file.txt', parseInt('0664', 8)).then(function() {
       const stats = fs.statSync('file.txt');
diff --git a/test/lib/fs.chown-fchown.spec.js b/test/lib/fs.chown-fchown.spec.js
index 35d0dfd8..755f4fcb 100644
--- a/test/lib/fs.chown-fchown.spec.js
+++ b/test/lib/fs.chown-fchown.spec.js
@@ -20,6 +20,10 @@ describe('fs.chown(path, uid, gid, callback)', function() {
     fs.chown('file.txt', 42, 43, done);
   });
 
+  it('supports Buffer input', function(done) {
+    fs.chown(Buffer.from('file.txt'), 42, 43, done);
+  });
+
   withPromise.it('promise changes ownership of a file', function(done) {
     fs.promises.chown('file.txt', 42, 43).then(done, done);
   });
diff --git a/test/lib/fs.copyFile.spec.js b/test/lib/fs.copyFile.spec.js
index 570605d3..575a3390 100644
--- a/test/lib/fs.copyFile.spec.js
+++ b/test/lib/fs.copyFile.spec.js
@@ -27,6 +27,22 @@ if (fs.copyFile && fs.copyFileSync) {
       });
     });
 
+    it('supports Buffer input', function(done) {
+      fs.copyFile(
+        Buffer.from('path/to/src.txt'),
+        Buffer.from('empty/dest.txt'),
+        function(err) {
+          assert.isTrue(!err);
+          assert.isTrue(fs.existsSync('empty/dest.txt'));
+          assert.equal(
+            String(fs.readFileSync('empty/dest.txt')),
+            'file content'
+          );
+          done();
+        }
+      );
+    });
+
     withPromise.it('promise copies a file to an empty directory', function(
       done
     ) {
diff --git a/test/lib/fs.link-symlink.spec.js b/test/lib/fs.link-symlink.spec.js
index a862751e..7ea607e8 100644
--- a/test/lib/fs.link-symlink.spec.js
+++ b/test/lib/fs.link-symlink.spec.js
@@ -31,6 +31,21 @@ describe('fs.link(srcpath, dstpath, callback)', function() {
     });
   });
 
+  it('supports Buffer input', function(done) {
+    assert.equal(fs.statSync('file.txt').nlink, 1);
+
+    fs.link(Buffer.from('file.txt'), Buffer.from('link.txt'), function(err) {
+      if (err) {
+        return done(err);
+      }
+      assert.isTrue(fs.statSync('link.txt').isFile());
+      assert.equal(fs.statSync('link.txt').nlink, 2);
+      assert.equal(fs.statSync('file.txt').nlink, 2);
+      assert.equal(String(fs.readFileSync('link.txt')), 'content');
+      done();
+    });
+  });
+
   withPromise.it('promise creates a link to a file', function(done) {
     assert.equal(fs.statSync('file.txt').nlink, 1);
 
@@ -173,6 +188,21 @@ describe('fs.symlink(srcpath, dstpath, [type], callback)', function() {
     });
   });
 
+  it('supports Buffer input', function(done) {
+    fs.symlink(
+      Buffer.from('../file.txt'),
+      Buffer.from('dir/link.txt'),
+      function(err) {
+        if (err) {
+          return done(err);
+        }
+        assert.isTrue(fs.statSync('dir/link.txt').isFile());
+        assert.equal(String(fs.readFileSync('dir/link.txt')), 'content');
+        done();
+      }
+    );
+  });
+
   withPromise.it('promise creates a symbolic link to a file', function(done) {
     fs.promises.symlink('../file.txt', 'dir/link.txt').then(function() {
       assert.isTrue(fs.statSync('dir/link.txt').isFile());
diff --git a/test/lib/fs.lstat.spec.js b/test/lib/fs.lstat.spec.js
index d4b53c67..02a6088f 100644
--- a/test/lib/fs.lstat.spec.js
+++ b/test/lib/fs.lstat.spec.js
@@ -34,6 +34,18 @@ describe('fs.lstat(path, callback)', function() {
     });
   });
 
+  it('suports Buffer input', function(done) {
+    fs.lstat(Buffer.from('link'), function(err, stats) {
+      if (err) {
+        return done(err);
+      }
+      assert.isTrue(stats.isSymbolicLink());
+      assert.isFalse(stats.isFile());
+      assert.equal(stats.mtime.getTime(), 2);
+      done();
+    });
+  });
+
   withPromise.it('promise stats a symbolic link', function(done) {
     fs.promises.lstat('link').then(function(stats) {
       assert.isTrue(stats.isSymbolicLink());
diff --git a/test/lib/fs.mkdir.spec.js b/test/lib/fs.mkdir.spec.js
index 1024d784..17433c04 100644
--- a/test/lib/fs.mkdir.spec.js
+++ b/test/lib/fs.mkdir.spec.js
@@ -35,6 +35,17 @@ describe('fs.mkdir(path, [mode], callback)', function() {
     });
   });
 
+  it('supports Buffer input', function(done) {
+    fs.mkdir(Buffer.from('parent/dir'), function(err) {
+      if (err) {
+        return done(err);
+      }
+      const stats = fs.statSync('parent/dir');
+      assert.isTrue(stats.isDirectory());
+      done();
+    });
+  });
+
   withPromise.it('promise creates a new directory', function(done) {
     fs.promises.mkdir('parent/dir').then(function() {
       const stats = fs.statSync('parent/dir');
diff --git a/test/lib/fs.open-close.spec.js b/test/lib/fs.open-close.spec.js
index 0e63beb3..bb889cd9 100644
--- a/test/lib/fs.open-close.spec.js
+++ b/test/lib/fs.open-close.spec.js
@@ -34,6 +34,16 @@ describe('fs.open(path, flags, [mode], callback)', function() {
     });
   });
 
+  it('supports Buffer input', function(done) {
+    fs.open(Buffer.from('nested/sub/dir/one.txt'), 'r', function(err, fd) {
+      if (err) {
+        return done(err);
+      }
+      assert.isNumber(fd);
+      done();
+    });
+  });
+
   withPromise.it('promise opens an existing file for reading (r)', function(
     done
   ) {
diff --git a/test/lib/fs.readdir.spec.js b/test/lib/fs.readdir.spec.js
index ed8c299f..6ec68f8b 100644
--- a/test/lib/fs.readdir.spec.js
+++ b/test/lib/fs.readdir.spec.js
@@ -35,6 +35,15 @@ describe('fs.readdir(path, callback)', function() {
     });
   });
 
+  it('supports Buffer input', function(done) {
+    fs.readdir(Buffer.from(path.join('path', 'to')), function(err, items) {
+      assert.isNull(err);
+      assert.isArray(items);
+      assert.deepEqual(items, ['file.txt']);
+      done();
+    });
+  });
+
   withPromise.it('promise lists directory contents', function(done) {
     fs.promises.readdir(path.join('path', 'to')).then(function(items) {
       assert.isArray(items);
diff --git a/test/lib/fs.readlink.spec.js b/test/lib/fs.readlink.spec.js
index 91535fb6..4b67e6d7 100644
--- a/test/lib/fs.readlink.spec.js
+++ b/test/lib/fs.readlink.spec.js
@@ -26,6 +26,16 @@ describe('fs.readlink(path, callback)', function() {
     });
   });
 
+  it('supports Buffer input', function(done) {
+    fs.readlink(Buffer.from('link'), function(err, srcPath) {
+      if (err) {
+        return done(err);
+      }
+      assert.equal(srcPath, './file.txt');
+      done();
+    });
+  });
+
   withPromise.it('promise reads a symbolic link', function(done) {
     fs.promises.readlink('link').then(function(srcPath) {
       assert.equal(srcPath, './file.txt');
diff --git a/test/lib/fs.rename.spec.js b/test/lib/fs.rename.spec.js
index 2a569b74..4b830e32 100644
--- a/test/lib/fs.rename.spec.js
+++ b/test/lib/fs.rename.spec.js
@@ -32,6 +32,19 @@ describe('fs.rename(oldPath, newPath, callback)', function() {
     });
   });
 
+  it('supports Buffer input', function(done) {
+    fs.rename(
+      Buffer.from('path/to/a.bin'),
+      Buffer.from('path/to/b.bin'),
+      function(err) {
+        assert.isTrue(!err);
+        assert.isFalse(fs.existsSync('path/to/a.bin'));
+        assert.isTrue(fs.existsSync('path/to/b.bin'));
+        done();
+      }
+    );
+  });
+
   withPromise.it('promise allows files to be renamed', function(done) {
     fs.promises.rename('path/to/a.bin', 'path/to/b.bin').then(function() {
       assert.isFalse(fs.existsSync('path/to/a.bin'));
diff --git a/test/lib/fs.rmdir.spec.js b/test/lib/fs.rmdir.spec.js
index 298635e2..5405fc67 100644
--- a/test/lib/fs.rmdir.spec.js
+++ b/test/lib/fs.rmdir.spec.js
@@ -5,21 +5,36 @@ const fs = require('fs');
 const mock = require('../../lib/index');
 
 const assert = helper.assert;
+const inVersion = helper.inVersion;
 const withPromise = helper.withPromise;
 
 const testParentPerms =
   fs.access && fs.accessSync && process.getuid && process.getgid;
 
-describe('fs.rmdir(path, callback)', function() {
-  beforeEach(function() {
-    mock({
-      'path/to/empty': {},
-      unwriteable: mock.directory({
-        mode: parseInt('0555', 8),
-        items: {child: {}}
-      })
-    });
+function setup() {
+  mock({
+    'path/to/empty': {},
+    'path2/to': {
+      empty: {
+        deep: {}
+      },
+      'non-empty': {
+        deep: {
+          'b.file': 'lorem'
+        },
+        'a.file': ''
+      }
+    },
+    'file.txt': 'content',
+    unwriteable: mock.directory({
+      mode: parseInt('0555', 8),
+      items: {child: {}}
+    })
   });
+}
+
+describe('fs.rmdir(path, callback)', function() {
+  beforeEach(setup);
   afterEach(mock.restore);
 
   it('removes an empty directory', function(done) {
@@ -35,6 +50,19 @@ describe('fs.rmdir(path, callback)', function() {
     });
   });
 
+  it('supports Buffer input', function(done) {
+    assert.equal(fs.statSync('path/to').nlink, 3);
+
+    fs.rmdir(Buffer.from('path/to/empty'), function(err) {
+      if (err) {
+        return done(err);
+      }
+      assert.isFalse(fs.existsSync('path/to/empty'));
+      assert.equal(fs.statSync('path/to').nlink, 2);
+      done();
+    });
+  });
+
   withPromise.it('promise removes an empty directory', function(done) {
     assert.equal(fs.statSync('path/to').nlink, 3);
 
@@ -67,6 +95,78 @@ describe('fs.rmdir(path, callback)', function() {
     );
   });
 
+  it('fails if file', function(done) {
+    fs.rmdir('file.txt', function(err) {
+      assert.instanceOf(err, Error);
+      assert.equal(err.code, 'ENOTDIR');
+      done();
+    });
+  });
+
+  withPromise.it('promise fails if file', function(done) {
+    fs.promises.rmdir('file.txt').then(
+      function() {
+        assert.fail('should not succeed.');
+        done();
+      },
+      function(err) {
+        assert.instanceOf(err, Error);
+        assert.equal(err.code, 'ENOTDIR');
+        done();
+      }
+    );
+  });
+
+  inVersion('>=12.10').run(function() {
+    it('recursively remove empty directory', function(done) {
+      assert.equal(fs.statSync('path2/to').nlink, 4);
+
+      fs.rmdir('path2/to/empty', {recursive: true}, function(err) {
+        if (err) {
+          return done(err);
+        }
+        assert.isFalse(fs.existsSync('path2/to/empty'));
+        assert.equal(fs.statSync('path2/to').nlink, 3);
+        done();
+      });
+    });
+
+    it('promise recursively remove empty directory', function(done) {
+      assert.equal(fs.statSync('path2/to').nlink, 4);
+
+      fs.promises.rmdir('path2/to/empty', {recursive: true}).then(function() {
+        assert.isFalse(fs.existsSync('path2/to/empty'));
+        assert.equal(fs.statSync('path2/to').nlink, 3);
+        done();
+      }, done);
+    });
+
+    it('recursively remove non-empty directory', function(done) {
+      assert.equal(fs.statSync('path2/to').nlink, 4);
+
+      fs.rmdir('path2/to/non-empty', {recursive: true}, function(err) {
+        if (err) {
+          return done(err);
+        }
+        assert.isFalse(fs.existsSync('path2/to/non-empty'));
+        assert.equal(fs.statSync('path2/to').nlink, 3);
+        done();
+      });
+    });
+
+    it('promise recursively remove non-empty directory', function(done) {
+      assert.equal(fs.statSync('path2/to').nlink, 4);
+
+      fs.promises
+        .rmdir('path2/to/non-empty', {recursive: true})
+        .then(function() {
+          assert.isFalse(fs.existsSync('path2/to/non-empty'));
+          assert.equal(fs.statSync('path2/to').nlink, 3);
+          done();
+        }, done);
+    });
+  });
+
   if (testParentPerms) {
     it('fails if parent is not writeable', function(done) {
       fs.rmdir('unwriteable/child', function(err) {
@@ -93,20 +193,11 @@ describe('fs.rmdir(path, callback)', function() {
 });
 
 describe('fs.rmdirSync(path)', function() {
-  beforeEach(function() {
-    mock({
-      'path/empty': {},
-      'file.txt': 'content',
-      unwriteable: mock.directory({
-        mode: parseInt('0555', 8),
-        items: {child: {}}
-      })
-    });
-  });
+  beforeEach(setup);
   afterEach(mock.restore);
 
   it('removes an empty directory', function() {
-    fs.rmdirSync('path/empty');
+    fs.rmdirSync('path/to/empty');
     assert.isFalse(fs.existsSync('path/empty'));
   });
 
@@ -128,6 +219,22 @@ describe('fs.rmdirSync(path)', function() {
     });
   });
 
+  inVersion('>=12.10').run(function() {
+    it('recursively remove empty directory', function() {
+      assert.equal(fs.statSync('path2/to').nlink, 4);
+      fs.rmdirSync('path2/to/empty', {recursive: true});
+      assert.isFalse(fs.existsSync('path2/to/empty'));
+      assert.equal(fs.statSync('path2/to').nlink, 3);
+    });
+
+    it('recursively remove non-empty directory', function() {
+      assert.equal(fs.statSync('path2/to').nlink, 4);
+      fs.rmdirSync('path2/to/non-empty', {recursive: true});
+      assert.isFalse(fs.existsSync('path2/to/non-empty'));
+      assert.equal(fs.statSync('path2/to').nlink, 3);
+    });
+  });
+
   if (testParentPerms) {
     it('fails if parent is not writeable', function() {
       assert.throws(function() {
diff --git a/test/lib/fs.stat-fstat.spec.js b/test/lib/fs.stat-fstat.spec.js
index d99b635d..38b5de28 100644
--- a/test/lib/fs.stat-fstat.spec.js
+++ b/test/lib/fs.stat-fstat.spec.js
@@ -52,6 +52,17 @@ describe('fs.stat(path, callback)', function() {
     });
   });
 
+  it('supports Buffer input', function(done) {
+    fs.stat(Buffer.from('/path/to/file.txt'), function(err, stats) {
+      if (err) {
+        return done(err);
+      }
+      assert.isTrue(stats.isFile());
+      assert.isFalse(stats.isDirectory());
+      done();
+    });
+  });
+
   withPromise.it('promise identifies files', function(done) {
     fs.promises.stat('/path/to/file.txt').then(function(stats) {
       assert.isTrue(stats.isFile());
diff --git a/test/lib/fs.unlink.spec.js b/test/lib/fs.unlink.spec.js
index 789ad2f4..60c887aa 100644
--- a/test/lib/fs.unlink.spec.js
+++ b/test/lib/fs.unlink.spec.js
@@ -31,6 +31,16 @@ describe('fs.unlink(path, callback)', function() {
     });
   });
 
+  it('supports Buffer input', function(done) {
+    fs.unlink(Buffer.from('file.txt'), function(err) {
+      if (err) {
+        return done(err);
+      }
+      assert.isFalse(fs.existsSync('file.txt'));
+      done();
+    });
+  });
+
   withPromise.it('promise deletes a file', function(done) {
     fs.promises.unlink('file.txt').then(function() {
       assert.isFalse(fs.existsSync('file.txt'));
diff --git a/test/lib/fs.utimes-futimes.spec.js b/test/lib/fs.utimes-futimes.spec.js
index a51c0ca6..efff3f39 100644
--- a/test/lib/fs.utimes-futimes.spec.js
+++ b/test/lib/fs.utimes-futimes.spec.js
@@ -28,6 +28,20 @@ describe('fs.utimes(path, atime, mtime, callback)', function() {
     });
   });
 
+  it('supports Buffer input', function(done) {
+    fs.utimes(Buffer.from('file.txt'), new Date(100), new Date(200), function(
+      err
+    ) {
+      if (err) {
+        return done(err);
+      }
+      const stats = fs.statSync('file.txt');
+      assert.equal(stats.atime.getTime(), 100);
+      assert.equal(stats.mtime.getTime(), 200);
+      done();
+    });
+  });
+
   withPromise.it('promise updates timestamps for a file', function(done) {
     fs.promises
       .utimes('file.txt', new Date(100), new Date(200))