diff --git a/lib/binding.js b/lib/binding.js index cd8010ee..d9a09986 100644 --- a/lib/binding.js +++ b/lib/binding.js @@ -450,6 +450,9 @@ Binding.prototype.stat = function(filepath, options, callback, ctx) { markSyscall(ctx, 'stat'); return maybeCallback(wrapStatsCallback(callback), ctx, this, function() { + if (Buffer.isBuffer(filepath)) { + filepath = filepath.toString(); + } let item = _system.getItem(filepath); if (item instanceof SymbolicLink) { item = _system.getItem( @@ -539,6 +542,9 @@ Binding.prototype.open = function(pathname, flags, mode, callback, ctx) { markSyscall(ctx, 'open'); return maybeCallback(normalizeCallback(callback), ctx, this, function() { + if (Buffer.isBuffer(pathname)) { + pathname = pathname.toString(); + } const descriptor = new FileDescriptor(flags); let item = _system.getItem(pathname); while (item instanceof SymbolicLink) { @@ -674,6 +680,12 @@ Binding.prototype.copyFile = function(src, dest, flags, callback, ctx) { markSyscall(ctx, 'copyfile'); return maybeCallback(normalizeCallback(callback), ctx, this, function() { + if (Buffer.isBuffer(src)) { + src = src.toString(); + } + if (Buffer.isBuffer(dest)) { + dest = dest.toString(); + } const srcFd = this.open(src, constants.O_RDONLY); try { @@ -873,6 +885,12 @@ Binding.prototype.rename = function(oldPath, newPath, callback, ctx) { markSyscall(ctx, 'rename'); return maybeCallback(normalizeCallback(callback), ctx, this, function() { + if (Buffer.isBuffer(oldPath)) { + oldPath = oldPath.toString(); + } + if (Buffer.isBuffer(newPath)) { + newPath = newPath.toString(); + } const oldItem = _system.getItem(oldPath); if (!oldItem) { throw new FSError('ENOENT', oldPath); @@ -938,6 +956,9 @@ Binding.prototype.readdir = function( markSyscall(ctx, 'scandir'); return maybeCallback(normalizeCallback(callback), ctx, this, function() { + if (Buffer.isBuffer(dirpath)) { + dirpath = dirpath.toString(); + } let dpath = dirpath; let dir = _system.getItem(dirpath); while (dir instanceof SymbolicLink) { @@ -990,6 +1011,9 @@ Binding.prototype.mkdir = function(pathname, mode, recursive, callback, ctx) { markSyscall(ctx, 'mkdir'); return maybeCallback(normalizeCallback(callback), ctx, this, function() { + if (Buffer.isBuffer(pathname)) { + pathname = pathname.toString(); + } const item = _system.getItem(pathname); if (item) { if (recursive && item instanceof Directory) { @@ -1030,6 +1054,9 @@ Binding.prototype.rmdir = function(pathname, callback, ctx) { markSyscall(ctx, 'rmdir'); return maybeCallback(normalizeCallback(callback), ctx, this, function() { + if (Buffer.isBuffer(pathname)) { + pathname = pathname.toString(); + } const item = _system.getItem(pathname); if (!item) { throw new FSError('ENOENT', pathname); @@ -1158,6 +1185,9 @@ Binding.prototype.chown = function(pathname, uid, gid, callback, ctx) { markSyscall(ctx, 'chown'); return maybeCallback(normalizeCallback(callback), ctx, this, function() { + if (Buffer.isBuffer(pathname)) { + pathname = pathname.toString(); + } const item = _system.getItem(pathname); if (!item) { throw new FSError('ENOENT', pathname); @@ -1197,6 +1227,9 @@ Binding.prototype.chmod = function(pathname, mode, callback, ctx) { markSyscall(ctx, 'chmod'); return maybeCallback(normalizeCallback(callback), ctx, this, function() { + if (Buffer.isBuffer(pathname)) { + pathname = pathname.toString(); + } const item = _system.getItem(pathname); if (!item) { throw new FSError('ENOENT', pathname); @@ -1232,6 +1265,9 @@ Binding.prototype.unlink = function(pathname, callback, ctx) { markSyscall(ctx, 'unlink'); return maybeCallback(normalizeCallback(callback), ctx, this, function() { + if (Buffer.isBuffer(pathname)) { + pathname = pathname.toString(); + } const item = _system.getItem(pathname); if (!item) { throw new FSError('ENOENT', pathname); @@ -1256,6 +1292,9 @@ Binding.prototype.utimes = function(pathname, atime, mtime, callback, ctx) { markSyscall(ctx, 'utimes'); return maybeCallback(normalizeCallback(callback), ctx, this, function() { + if (Buffer.isBuffer(pathname)) { + pathname = pathname.toString(); + } const item = _system.getItem(pathname); if (!item) { throw new FSError('ENOENT', pathname); @@ -1323,6 +1362,12 @@ Binding.prototype.link = function(srcPath, destPath, callback, ctx) { markSyscall(ctx, 'link'); return maybeCallback(normalizeCallback(callback), ctx, this, function() { + if (Buffer.isBuffer(srcPath)) { + srcPath = srcPath.toString(); + } + if (Buffer.isBuffer(destPath)) { + destPath = destPath.toString(); + } const item = _system.getItem(srcPath); if (!item) { throw new FSError('ENOENT', srcPath); @@ -1356,6 +1401,12 @@ Binding.prototype.symlink = function(srcPath, destPath, type, callback, ctx) { markSyscall(ctx, 'symlink'); return maybeCallback(normalizeCallback(callback), ctx, this, function() { + if (Buffer.isBuffer(srcPath)) { + srcPath = srcPath.toString(); + } + if (Buffer.isBuffer(destPath)) { + destPath = destPath.toString(); + } if (_system.getItem(destPath)) { throw new FSError('EEXIST', destPath); } @@ -1390,6 +1441,9 @@ Binding.prototype.readlink = function(pathname, encoding, callback, ctx) { markSyscall(ctx, 'readlink'); return maybeCallback(normalizeCallback(callback), ctx, this, function() { + if (Buffer.isBuffer(pathname)) { + pathname = pathname.toString(); + } const link = _system.getItem(pathname); if (!link) { throw new FSError('ENOENT', pathname); @@ -1423,6 +1477,9 @@ Binding.prototype.lstat = function(filepath, options, callback, ctx) { markSyscall(ctx, 'lstat'); return maybeCallback(wrapStatsCallback(callback), ctx, this, function() { + if (Buffer.isBuffer(filepath)) { + filepath = filepath.toString(); + } const item = _system.getItem(filepath); if (!item) { throw new FSError('ENOENT', filepath); @@ -1455,6 +1512,9 @@ Binding.prototype.access = function(filepath, mode, callback, ctx) { markSyscall(ctx, 'access'); return maybeCallback(normalizeCallback(callback), ctx, this, function() { + if (Buffer.isBuffer(filepath)) { + filepath = filepath.toString(); + } 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))