fs: add recursive option to rmdir()
This commit adds a recursive option to fs.rmdir(), fs.rmdirSync(), and fs.promises.rmdir(). The implementation is a port of the npm module rimraf. PR-URL: https://github.com/nodejs/node/pull/29168 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Roman Reiss <me@silverwind.io> Reviewed-By: Ben Coe <bencoe@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Jiawen Geng <technicalcute@gmail.com>
This commit is contained in:
parent
2b1bcba385
commit
53816cce69
19
LICENSE
19
LICENSE
@ -1507,3 +1507,22 @@ The externally maintained libraries used by Node.js are:
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
"""
|
||||
|
||||
- rimraf, located at lib/internal/fs/rimraf.js, is licensed as follows:
|
||||
"""
|
||||
The ISC License
|
||||
|
||||
Copyright (c) Isaac Z. Schlueter and Contributors
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
"""
|
||||
|
@ -3017,10 +3017,14 @@ changes:
|
||||
|
||||
Synchronous rename(2). Returns `undefined`.
|
||||
|
||||
## fs.rmdir(path, callback)
|
||||
## fs.rmdir(path[, options], callback)
|
||||
<!-- YAML
|
||||
added: v0.0.2
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/29168
|
||||
description: The `recursive`, `maxBusyTries`, and `emfileWait` options are
|
||||
now supported.
|
||||
- version: v10.0.0
|
||||
pr-url: https://github.com/nodejs/node/pull/12562
|
||||
description: The `callback` parameter is no longer optional. Not passing
|
||||
@ -3035,7 +3039,21 @@ changes:
|
||||
it will emit a deprecation warning with id DEP0013.
|
||||
-->
|
||||
|
||||
> Stability: 1 - Recursive removal is experimental.
|
||||
|
||||
* `path` {string|Buffer|URL}
|
||||
* `options` {Object}
|
||||
* `emfileWait` {integer} If an `EMFILE` error is encountered, Node.js will
|
||||
retry the operation with a linear backoff of 1ms longer on each try until the
|
||||
timeout duration passes this limit. This option is ignored if the `recursive`
|
||||
option is not `true`. **Default:** `1000`.
|
||||
* `maxBusyTries` {integer} If an `EBUSY`, `ENOTEMPTY`, or `EPERM` error is
|
||||
encountered, Node.js will retry the operation with a linear backoff wait of
|
||||
100ms longer on each try. This option represents the number of retries. This
|
||||
option is ignored if the `recursive` option is not `true`. **Default:** `3`.
|
||||
* `recursive` {boolean} If `true`, perform a recursive directory removal. In
|
||||
recursive mode, errors are not reported if `path` does not exist, and
|
||||
operations are retried on failure. **Default:** `false`.
|
||||
* `callback` {Function}
|
||||
* `err` {Error}
|
||||
|
||||
@ -3045,17 +3063,27 @@ to the completion callback.
|
||||
Using `fs.rmdir()` on a file (not a directory) results in an `ENOENT` error on
|
||||
Windows and an `ENOTDIR` error on POSIX.
|
||||
|
||||
## fs.rmdirSync(path)
|
||||
## fs.rmdirSync(path[, options])
|
||||
<!-- YAML
|
||||
added: v0.1.21
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/29168
|
||||
description: The `recursive`, `maxBusyTries`, and `emfileWait` options are
|
||||
now supported.
|
||||
- version: v7.6.0
|
||||
pr-url: https://github.com/nodejs/node/pull/10739
|
||||
description: The `path` parameters can be a WHATWG `URL` object using
|
||||
`file:` protocol. Support is currently still *experimental*.
|
||||
-->
|
||||
|
||||
> Stability: 1 - Recursive removal is experimental.
|
||||
|
||||
* `path` {string|Buffer|URL}
|
||||
* `options` {Object}
|
||||
* `recursive` {boolean} If `true`, perform a recursive directory removal. In
|
||||
recursive mode, errors are not reported if `path` does not exist, and
|
||||
operations are retried on failure. **Default:** `false`.
|
||||
|
||||
Synchronous rmdir(2). Returns `undefined`.
|
||||
|
||||
@ -4694,12 +4722,31 @@ added: v10.0.0
|
||||
Renames `oldPath` to `newPath` and resolves the `Promise` with no arguments
|
||||
upon success.
|
||||
|
||||
### fsPromises.rmdir(path)
|
||||
### fsPromises.rmdir(path[, options])
|
||||
<!-- YAML
|
||||
added: v10.0.0
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/29168
|
||||
description: The `recursive`, `maxBusyTries`, and `emfileWait` options are
|
||||
now supported.
|
||||
-->
|
||||
|
||||
> Stability: 1 - Recursive removal is experimental.
|
||||
|
||||
* `path` {string|Buffer|URL}
|
||||
* `options` {Object}
|
||||
* `emfileWait` {integer} If an `EMFILE` error is encountered, Node.js will
|
||||
retry the operation with a linear backoff of 1ms longer on each try until the
|
||||
timeout duration passes this limit. This option is ignored if the `recursive`
|
||||
option is not `true`. **Default:** `1000`.
|
||||
* `maxBusyTries` {integer} If an `EBUSY`, `ENOTEMPTY`, or `EPERM` error is
|
||||
encountered, Node.js will retry the operation with a linear backoff wait of
|
||||
100ms longer on each try. This option represents the number of retries. This
|
||||
option is ignored if the `recursive` option is not `true`. **Default:** `3`.
|
||||
* `recursive` {boolean} If `true`, perform a recursive directory removal. In
|
||||
recursive mode, errors are not reported if `path` does not exist, and
|
||||
operations are retried on failure. **Default:** `false`.
|
||||
* Returns: {Promise}
|
||||
|
||||
Removes the directory identified by `path` then resolves the `Promise` with
|
||||
@ -5193,7 +5240,7 @@ the file contents.
|
||||
[`fs.readdir()`]: #fs_fs_readdir_path_options_callback
|
||||
[`fs.readdirSync()`]: #fs_fs_readdirsync_path_options
|
||||
[`fs.realpath()`]: #fs_fs_realpath_path_options_callback
|
||||
[`fs.rmdir()`]: #fs_fs_rmdir_path_callback
|
||||
[`fs.rmdir()`]: #fs_fs_rmdir_path_options_callback
|
||||
[`fs.stat()`]: #fs_fs_stat_path_options_callback
|
||||
[`fs.symlink()`]: #fs_fs_symlink_target_path_type_callback
|
||||
[`fs.utimes()`]: #fs_fs_utimes_path_atime_mtime_callback
|
||||
|
42
lib/fs.js
42
lib/fs.js
@ -76,6 +76,7 @@ const {
|
||||
validateOffsetLengthRead,
|
||||
validateOffsetLengthWrite,
|
||||
validatePath,
|
||||
validateRmdirOptions,
|
||||
warnOnNonPortableTemplate
|
||||
} = require('internal/fs/utils');
|
||||
const {
|
||||
@ -100,6 +101,8 @@ let watchers;
|
||||
let ReadFileContext;
|
||||
let ReadStream;
|
||||
let WriteStream;
|
||||
let rimraf;
|
||||
let rimrafSync;
|
||||
|
||||
// These have to be separate because of how graceful-fs happens to do it's
|
||||
// monkeypatching.
|
||||
@ -736,16 +739,41 @@ function ftruncateSync(fd, len = 0) {
|
||||
handleErrorFromBinding(ctx);
|
||||
}
|
||||
|
||||
function rmdir(path, callback) {
|
||||
callback = makeCallback(callback);
|
||||
path = getValidatedPath(path);
|
||||
const req = new FSReqCallback();
|
||||
req.oncomplete = callback;
|
||||
binding.rmdir(pathModule.toNamespacedPath(path), req);
|
||||
|
||||
function lazyLoadRimraf() {
|
||||
if (rimraf === undefined)
|
||||
({ rimraf, rimrafSync } = require('internal/fs/rimraf'));
|
||||
}
|
||||
|
||||
function rmdirSync(path) {
|
||||
function rmdir(path, options, callback) {
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = undefined;
|
||||
}
|
||||
|
||||
callback = makeCallback(callback);
|
||||
path = pathModule.toNamespacedPath(getValidatedPath(path));
|
||||
options = validateRmdirOptions(options);
|
||||
|
||||
if (options.recursive) {
|
||||
lazyLoadRimraf();
|
||||
return rimraf(path, options, callback);
|
||||
}
|
||||
|
||||
const req = new FSReqCallback();
|
||||
req.oncomplete = callback;
|
||||
binding.rmdir(path, req);
|
||||
}
|
||||
|
||||
function rmdirSync(path, options) {
|
||||
path = getValidatedPath(path);
|
||||
options = validateRmdirOptions(options);
|
||||
|
||||
if (options.recursive) {
|
||||
lazyLoadRimraf();
|
||||
return rimrafSync(pathModule.toNamespacedPath(path), options);
|
||||
}
|
||||
|
||||
const ctx = { path };
|
||||
binding.rmdir(pathModule.toNamespacedPath(path), undefined, ctx);
|
||||
handleErrorFromBinding(ctx);
|
||||
|
@ -18,6 +18,7 @@ const {
|
||||
ERR_METHOD_NOT_IMPLEMENTED
|
||||
} = require('internal/errors').codes;
|
||||
const { isUint8Array } = require('internal/util/types');
|
||||
const { rimrafPromises } = require('internal/fs/rimraf');
|
||||
const {
|
||||
copyObject,
|
||||
getDirents,
|
||||
@ -32,6 +33,7 @@ const {
|
||||
validateBufferArray,
|
||||
validateOffsetLengthRead,
|
||||
validateOffsetLengthWrite,
|
||||
validateRmdirOptions,
|
||||
warnOnNonPortableTemplate
|
||||
} = require('internal/fs/utils');
|
||||
const {
|
||||
@ -300,9 +302,15 @@ async function ftruncate(handle, len = 0) {
|
||||
return binding.ftruncate(handle.fd, len, kUsePromises);
|
||||
}
|
||||
|
||||
async function rmdir(path) {
|
||||
path = getValidatedPath(path);
|
||||
return binding.rmdir(pathModule.toNamespacedPath(path), kUsePromises);
|
||||
async function rmdir(path, options) {
|
||||
path = pathModule.toNamespacedPath(getValidatedPath(path));
|
||||
options = validateRmdirOptions(options);
|
||||
|
||||
if (options.recursive) {
|
||||
return rimrafPromises(path, options);
|
||||
}
|
||||
|
||||
return binding.rmdir(path, kUsePromises);
|
||||
}
|
||||
|
||||
async function fdatasync(handle) {
|
||||
|
252
lib/internal/fs/rimraf.js
Normal file
252
lib/internal/fs/rimraf.js
Normal file
@ -0,0 +1,252 @@
|
||||
// This file is a modified version of the rimraf module on npm. It has been
|
||||
// modified in the following ways:
|
||||
// - Use of the assert module has been replaced with core's error system.
|
||||
// - All code related to the glob dependency has been removed.
|
||||
// - Bring your own custom fs module is not currently supported.
|
||||
// - Some basic code cleanup.
|
||||
'use strict';
|
||||
const {
|
||||
chmod,
|
||||
chmodSync,
|
||||
lstat,
|
||||
lstatSync,
|
||||
readdir,
|
||||
readdirSync,
|
||||
rmdir,
|
||||
rmdirSync,
|
||||
stat,
|
||||
statSync,
|
||||
unlink,
|
||||
unlinkSync
|
||||
} = require('fs');
|
||||
const { join } = require('path');
|
||||
const { setTimeout } = require('timers');
|
||||
const notEmptyErrorCodes = new Set(['ENOTEMPTY', 'EEXIST', 'EPERM']);
|
||||
const isWindows = process.platform === 'win32';
|
||||
const epermHandler = isWindows ? fixWinEPERM : _rmdir;
|
||||
const epermHandlerSync = isWindows ? fixWinEPERMSync : _rmdirSync;
|
||||
const numRetries = isWindows ? 100 : 1;
|
||||
|
||||
|
||||
function rimraf(path, options, callback) {
|
||||
let timeout = 0; // For EMFILE handling.
|
||||
let busyTries = 0;
|
||||
|
||||
_rimraf(path, options, function CB(err) {
|
||||
if (err) {
|
||||
if ((err.code === 'EBUSY' || err.code === 'ENOTEMPTY' ||
|
||||
err.code === 'EPERM') && busyTries < options.maxBusyTries) {
|
||||
busyTries++;
|
||||
return setTimeout(_rimraf, busyTries * 100, path, options, CB);
|
||||
}
|
||||
|
||||
if (err.code === 'EMFILE' && timeout < options.emfileWait)
|
||||
return setTimeout(_rimraf, timeout++, path, options, CB);
|
||||
|
||||
// The file is already gone.
|
||||
if (err.code === 'ENOENT')
|
||||
err = null;
|
||||
}
|
||||
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function _rimraf(path, options, callback) {
|
||||
// SunOS lets the root user unlink directories. Use lstat here to make sure
|
||||
// it's not a directory.
|
||||
lstat(path, (err, stats) => {
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT')
|
||||
return callback(null);
|
||||
|
||||
// Windows can EPERM on stat.
|
||||
if (isWindows && err.code === 'EPERM')
|
||||
return fixWinEPERM(path, options, err, callback);
|
||||
} else if (stats.isDirectory()) {
|
||||
return _rmdir(path, options, err, callback);
|
||||
}
|
||||
|
||||
unlink(path, (err) => {
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT')
|
||||
return callback(null);
|
||||
if (err.code === 'EISDIR')
|
||||
return _rmdir(path, options, err, callback);
|
||||
if (err.code === 'EPERM') {
|
||||
return epermHandler(path, options, err, callback);
|
||||
}
|
||||
}
|
||||
|
||||
return callback(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function fixWinEPERM(path, options, originalErr, callback) {
|
||||
chmod(path, 0o666, (err) => {
|
||||
if (err)
|
||||
return callback(err.code === 'ENOENT' ? null : originalErr);
|
||||
|
||||
stat(path, (err, stats) => {
|
||||
if (err)
|
||||
return callback(err.code === 'ENOENT' ? null : originalErr);
|
||||
|
||||
if (stats.isDirectory())
|
||||
_rmdir(path, options, originalErr, callback);
|
||||
else
|
||||
unlink(path, callback);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function _rmdir(path, options, originalErr, callback) {
|
||||
rmdir(path, (err) => {
|
||||
if (err) {
|
||||
if (notEmptyErrorCodes.has(err.code))
|
||||
return _rmchildren(path, options, callback);
|
||||
if (err.code === 'ENOTDIR')
|
||||
return callback(originalErr);
|
||||
}
|
||||
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function _rmchildren(path, options, callback) {
|
||||
readdir(path, (err, files) => {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
let numFiles = files.length;
|
||||
|
||||
if (numFiles === 0)
|
||||
return rmdir(path, callback);
|
||||
|
||||
let done = false;
|
||||
|
||||
files.forEach((child) => {
|
||||
rimraf(join(path, child), options, (err) => {
|
||||
if (done)
|
||||
return;
|
||||
|
||||
if (err) {
|
||||
done = true;
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
numFiles--;
|
||||
if (numFiles === 0)
|
||||
rmdir(path, callback);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function rimrafPromises(path, options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
rimraf(path, options, (err) => {
|
||||
if (err)
|
||||
return reject(err);
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function rimrafSync(path, options) {
|
||||
let stats;
|
||||
|
||||
try {
|
||||
stats = lstatSync(path);
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT')
|
||||
return;
|
||||
|
||||
// Windows can EPERM on stat.
|
||||
if (isWindows && err.code === 'EPERM')
|
||||
fixWinEPERMSync(path, options, err);
|
||||
}
|
||||
|
||||
try {
|
||||
// SunOS lets the root user unlink directories.
|
||||
if (stats !== undefined && stats.isDirectory())
|
||||
_rmdirSync(path, options, null);
|
||||
else
|
||||
unlinkSync(path);
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT')
|
||||
return;
|
||||
if (err.code === 'EPERM')
|
||||
return epermHandlerSync(path, options, err);
|
||||
if (err.code !== 'EISDIR')
|
||||
throw err;
|
||||
|
||||
_rmdirSync(path, options, err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function _rmdirSync(path, options, originalErr) {
|
||||
try {
|
||||
rmdirSync(path);
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT')
|
||||
return;
|
||||
if (err.code === 'ENOTDIR')
|
||||
throw originalErr;
|
||||
|
||||
if (notEmptyErrorCodes.has(err.code)) {
|
||||
// Removing failed. Try removing all children and then retrying the
|
||||
// original removal. Windows has a habit of not closing handles promptly
|
||||
// when files are deleted, resulting in spurious ENOTEMPTY failures. Work
|
||||
// around that issue by retrying on Windows.
|
||||
readdirSync(path).forEach((child) => {
|
||||
rimrafSync(join(path, child), options);
|
||||
});
|
||||
|
||||
for (let i = 0; i < numRetries; i++) {
|
||||
try {
|
||||
return rmdirSync(path, options);
|
||||
} catch {} // Ignore errors.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function fixWinEPERMSync(path, options, originalErr) {
|
||||
try {
|
||||
chmodSync(path, 0o666);
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT')
|
||||
return;
|
||||
|
||||
throw originalErr;
|
||||
}
|
||||
|
||||
let stats;
|
||||
|
||||
try {
|
||||
stats = statSync(path);
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT')
|
||||
return;
|
||||
|
||||
throw originalErr;
|
||||
}
|
||||
|
||||
if (stats.isDirectory())
|
||||
_rmdirSync(path, options, originalErr);
|
||||
else
|
||||
unlinkSync(path);
|
||||
}
|
||||
|
||||
|
||||
module.exports = { rimraf, rimrafPromises, rimrafSync };
|
@ -22,6 +22,10 @@ const {
|
||||
} = require('internal/util/types');
|
||||
const { once } = require('internal/util');
|
||||
const { toPathIfFileURL } = require('internal/url');
|
||||
const {
|
||||
validateInt32,
|
||||
validateUint32
|
||||
} = require('internal/validators');
|
||||
const pathModule = require('path');
|
||||
const kType = Symbol('type');
|
||||
const kStats = Symbol('stats');
|
||||
@ -525,6 +529,30 @@ function warnOnNonPortableTemplate(template) {
|
||||
}
|
||||
}
|
||||
|
||||
const defaultRmdirOptions = {
|
||||
emfileWait: 1000,
|
||||
maxBusyTries: 3,
|
||||
recursive: false,
|
||||
};
|
||||
|
||||
const validateRmdirOptions = hideStackFrames((options) => {
|
||||
if (options === undefined)
|
||||
return defaultRmdirOptions;
|
||||
if (options === null || typeof options !== 'object')
|
||||
throw new ERR_INVALID_ARG_TYPE('options', 'object', options);
|
||||
|
||||
options = { ...defaultRmdirOptions, ...options };
|
||||
|
||||
if (typeof options.recursive !== 'boolean')
|
||||
throw new ERR_INVALID_ARG_TYPE('recursive', 'boolean', options.recursive);
|
||||
|
||||
validateInt32(options.emfileWait, 'emfileWait', 0);
|
||||
validateUint32(options.maxBusyTries, 'maxBusyTries');
|
||||
|
||||
return options;
|
||||
});
|
||||
|
||||
|
||||
module.exports = {
|
||||
assertEncoding,
|
||||
BigIntStats, // for testing
|
||||
@ -545,5 +573,6 @@ module.exports = {
|
||||
validateOffsetLengthRead,
|
||||
validateOffsetLengthWrite,
|
||||
validatePath,
|
||||
validateRmdirOptions,
|
||||
warnOnNonPortableTemplate
|
||||
};
|
||||
|
1
node.gyp
1
node.gyp
@ -124,6 +124,7 @@
|
||||
'lib/internal/freeze_intrinsics.js',
|
||||
'lib/internal/fs/promises.js',
|
||||
'lib/internal/fs/read_file_context.js',
|
||||
'lib/internal/fs/rimraf.js',
|
||||
'lib/internal/fs/streams.js',
|
||||
'lib/internal/fs/sync_write_stream.js',
|
||||
'lib/internal/fs/utils.js',
|
||||
|
152
test/parallel/test-fs-rmdir-recursive.js
Normal file
152
test/parallel/test-fs-rmdir-recursive.js
Normal file
@ -0,0 +1,152 @@
|
||||
// Flags: --expose-internals
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { validateRmdirOptions } = require('internal/fs/utils');
|
||||
let count = 0;
|
||||
|
||||
tmpdir.refresh();
|
||||
|
||||
function makeNonEmptyDirectory() {
|
||||
const dirname = `rmdir-recursive-${count}`;
|
||||
fs.mkdirSync(path.join(dirname, 'foo', 'bar', 'baz'), { recursive: true });
|
||||
fs.writeFileSync(path.join(dirname, 'text.txt'), 'hello', 'utf8');
|
||||
count++;
|
||||
return dirname;
|
||||
}
|
||||
|
||||
// Test the asynchronous version.
|
||||
{
|
||||
const dir = makeNonEmptyDirectory();
|
||||
|
||||
// Removal should fail without the recursive option.
|
||||
fs.rmdir(dir, common.mustCall((err) => {
|
||||
assert.strictEqual(err.syscall, 'rmdir');
|
||||
|
||||
// Removal should fail without the recursive option set to true.
|
||||
fs.rmdir(dir, { recursive: false }, common.mustCall((err) => {
|
||||
assert.strictEqual(err.syscall, 'rmdir');
|
||||
|
||||
// Recursive removal should succeed.
|
||||
fs.rmdir(dir, { recursive: true }, common.mustCall((err) => {
|
||||
assert.ifError(err);
|
||||
|
||||
// No error should occur if recursive and the directory does not exist.
|
||||
fs.rmdir(dir, { recursive: true }, common.mustCall((err) => {
|
||||
assert.ifError(err);
|
||||
|
||||
// Attempted removal should fail now because the directory is gone.
|
||||
fs.rmdir(dir, common.mustCall((err) => {
|
||||
assert.strictEqual(err.syscall, 'rmdir');
|
||||
}));
|
||||
}));
|
||||
}));
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
||||
// Test the synchronous version.
|
||||
{
|
||||
const dir = makeNonEmptyDirectory();
|
||||
|
||||
// Removal should fail without the recursive option set to true.
|
||||
common.expectsError(() => {
|
||||
fs.rmdirSync(dir);
|
||||
}, { syscall: 'rmdir' });
|
||||
common.expectsError(() => {
|
||||
fs.rmdirSync(dir, { recursive: false });
|
||||
}, { syscall: 'rmdir' });
|
||||
|
||||
// Recursive removal should succeed.
|
||||
fs.rmdirSync(dir, { recursive: true });
|
||||
|
||||
// No error should occur if recursive and the directory does not exist.
|
||||
fs.rmdirSync(dir, { recursive: true });
|
||||
|
||||
// Attempted removal should fail now because the directory is gone.
|
||||
common.expectsError(() => fs.rmdirSync(dir), { syscall: 'rmdir' });
|
||||
}
|
||||
|
||||
// Test the Promises based version.
|
||||
(async () => {
|
||||
const dir = makeNonEmptyDirectory();
|
||||
|
||||
// Removal should fail without the recursive option set to true.
|
||||
assert.rejects(fs.promises.rmdir(dir), { syscall: 'rmdir' });
|
||||
assert.rejects(fs.promises.rmdir(dir, { recursive: false }), {
|
||||
syscall: 'rmdir'
|
||||
});
|
||||
|
||||
// Recursive removal should succeed.
|
||||
await fs.promises.rmdir(dir, { recursive: true });
|
||||
|
||||
// No error should occur if recursive and the directory does not exist.
|
||||
await fs.promises.rmdir(dir, { recursive: true });
|
||||
|
||||
// Attempted removal should fail now because the directory is gone.
|
||||
assert.rejects(fs.promises.rmdir(dir), { syscall: 'rmdir' });
|
||||
})();
|
||||
|
||||
// Test input validation.
|
||||
{
|
||||
const defaults = {
|
||||
emfileWait: 1000,
|
||||
maxBusyTries: 3,
|
||||
recursive: false
|
||||
};
|
||||
const modified = {
|
||||
emfileWait: 953,
|
||||
maxBusyTries: 5,
|
||||
recursive: true
|
||||
};
|
||||
|
||||
assert.deepStrictEqual(validateRmdirOptions(), defaults);
|
||||
assert.deepStrictEqual(validateRmdirOptions({}), defaults);
|
||||
assert.deepStrictEqual(validateRmdirOptions(modified), modified);
|
||||
assert.deepStrictEqual(validateRmdirOptions({
|
||||
maxBusyTries: 99
|
||||
}), {
|
||||
emfileWait: 1000,
|
||||
maxBusyTries: 99,
|
||||
recursive: false
|
||||
});
|
||||
|
||||
[null, 'foo', 5, NaN].forEach((bad) => {
|
||||
common.expectsError(() => {
|
||||
validateRmdirOptions(bad);
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
type: TypeError,
|
||||
message: /^The "options" argument must be of type object\./
|
||||
});
|
||||
});
|
||||
|
||||
[undefined, null, 'foo', Infinity, function() {}].forEach((bad) => {
|
||||
common.expectsError(() => {
|
||||
validateRmdirOptions({ recursive: bad });
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
type: TypeError,
|
||||
message: /^The "recursive" argument must be of type boolean\./
|
||||
});
|
||||
});
|
||||
|
||||
common.expectsError(() => {
|
||||
validateRmdirOptions({ emfileWait: -1 });
|
||||
}, {
|
||||
code: 'ERR_OUT_OF_RANGE',
|
||||
type: RangeError,
|
||||
message: /^The value of "emfileWait" is out of range\./
|
||||
});
|
||||
|
||||
common.expectsError(() => {
|
||||
validateRmdirOptions({ maxBusyTries: -1 });
|
||||
}, {
|
||||
code: 'ERR_OUT_OF_RANGE',
|
||||
type: RangeError,
|
||||
message: /^The value of "maxBusyTries" is out of range\./
|
||||
});
|
||||
}
|
@ -106,4 +106,7 @@ addlicense "HdrHistogram" "deps/histogram" "$(cat ${rootdir}/deps/histogram/LICE
|
||||
addlicense "node-heapdump" "src/heap_utils.cc" \
|
||||
"$(curl -sL https://raw.githubusercontent.com/bnoordhuis/node-heapdump/0ca52441e46241ffbea56a389e2856ec01c48c97/LICENSE)"
|
||||
|
||||
addlicense "rimraf" "lib/internal/fs/rimraf.js" \
|
||||
"$(curl -sL https://raw.githubusercontent.com/isaacs/rimraf/0e365ac4e4d64a25aa2a3cc026348f13410210e1/LICENSE)"
|
||||
|
||||
mv $tmplicense $licensefile
|
||||
|
Loading…
x
Reference in New Issue
Block a user