Skip to content

Commit 1610728

Browse files
iansuBethGriggs
authored andcommittedOct 13, 2020
fs: add rm method
This PR introduces a new method fs.rm that provides the behaviour of rimraf when used with the recursive: true and force: true options. PR-URL: #35494 Reviewed-By: Ben Coe <bencoe@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Ruy Adorno <ruyadorno@github.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Michael Dawson <midawson@redhat.com>
1 parent f552e5c commit 1610728

File tree

10 files changed

+518
-37
lines changed

10 files changed

+518
-37
lines changed
 

‎doc/api/errors.md

+5
Original file line numberDiff line numberDiff line change
@@ -928,6 +928,11 @@ added: v14.0.0
928928
Used when a feature that is not available
929929
to the current platform which is running Node.js is used.
930930

931+
<a id="ERR_FS_EISDIR"></a>
932+
### `ERR_FS_EISDIR`
933+
934+
Path is a directory.
935+
931936
<a id="ERR_FS_FILE_TOO_LARGE"></a>
932937
### `ERR_FS_FILE_TOO_LARGE`
933938

‎doc/api/fs.md

+78-9
Original file line numberDiff line numberDiff line change
@@ -3509,9 +3509,9 @@ changes:
35093509
* `options` {Object}
35103510
* `maxRetries` {integer} If an `EBUSY`, `EMFILE`, `ENFILE`, `ENOTEMPTY`, or
35113511
`EPERM` error is encountered, Node.js will retry the operation with a linear
3512-
backoff wait of `retryDelay` ms longer on each try. This option represents
3513-
the number of retries. This option is ignored if the `recursive` option is
3514-
not `true`. **Default:** `0`.
3512+
backoff wait of `retryDelay` milliseconds longer on each try. This option
3513+
represents the number of retries. This option is ignored if the `recursive`
3514+
option is not `true`. **Default:** `0`.
35153515
* `recursive` {boolean} If `true`, perform a recursive directory removal. In
35163516
recursive mode, errors are not reported if `path` does not exist, and
35173517
operations are retried on failure. **Default:** `false`.
@@ -3560,9 +3560,9 @@ changes:
35603560
* `options` {Object}
35613561
* `maxRetries` {integer} If an `EBUSY`, `EMFILE`, `ENFILE`, `ENOTEMPTY`, or
35623562
`EPERM` error is encountered, Node.js will retry the operation with a linear
3563-
backoff wait of `retryDelay` ms longer on each try. This option represents
3564-
the number of retries. This option is ignored if the `recursive` option is
3565-
not `true`. **Default:** `0`.
3563+
backoff wait of `retryDelay` milliseconds longer on each try. This option
3564+
represents the number of retries. This option is ignored if the `recursive`
3565+
option is not `true`. **Default:** `0`.
35663566
* `recursive` {boolean} If `true`, perform a recursive directory removal. In
35673567
recursive mode, errors are not reported if `path` does not exist, and
35683568
operations are retried on failure. **Default:** `false`.
@@ -3581,6 +3581,53 @@ that represent files will be deleted. The permissive behavior of the
35813581
`recursive` option is deprecated, `ENOTDIR` and `ENOENT` will be thrown in
35823582
the future.
35833583

3584+
## `fs.rm(path[, options], callback)`
3585+
<!-- YAML
3586+
added: REPLACEME
3587+
-->
3588+
3589+
* `path` {string|Buffer|URL}
3590+
* `options` {Object}
3591+
* `force` don't error on nonexistent path
3592+
* `maxRetries` {integer} If an `EBUSY`, `EMFILE`, `ENFILE`, `ENOTEMPTY`, or
3593+
`EPERM` error is encountered, Node.js will retry the operation with a linear
3594+
backoff wait of `retryDelay` milliseconds longer on each try. This option
3595+
represents the number of retries. This option is ignored if the `recursive`
3596+
option is not `true`. **Default:** `0`.
3597+
* `recursive` {boolean} If `true`, perform a recursive removal. In
3598+
recursive mode operations are retried on failure. **Default:** `false`.
3599+
* `retryDelay` {integer} The amount of time in milliseconds to wait between
3600+
retries. This option is ignored if the `recursive` option is not `true`.
3601+
**Default:** `100`.
3602+
* `callback` {Function}
3603+
* `err` {Error}
3604+
3605+
Asynchronously removes files and directories (modeled on the standard POSIX `rm`
3606+
utility). No arguments other than a possible exception are given to the
3607+
completion callback.
3608+
3609+
## `fs.rmSync(path[, options])`
3610+
<!-- YAML
3611+
added: REPLACEME
3612+
-->
3613+
3614+
* `path` {string|Buffer|URL}
3615+
* `options` {Object}
3616+
* `force` Ignore errors
3617+
* `maxRetries` {integer} If an `EBUSY`, `EMFILE`, `ENFILE`, `ENOTEMPTY`, or
3618+
`EPERM` error is encountered, Node.js will retry the operation with a linear
3619+
backoff wait of `retryDelay` milliseconds longer on each try. This option
3620+
represents the number of retries. This option is ignored if the `recursive`
3621+
option is not `true`. **Default:** `0`.
3622+
* `recursive` {boolean} If `true`, perform a recursive directory removal. In
3623+
recursive mode operations are retried on failure. **Default:** `false`.
3624+
* `retryDelay` {integer} The amount of time in milliseconds to wait between
3625+
retries. This option is ignored if the `recursive` option is not `true`.
3626+
**Default:** `100`.
3627+
3628+
Synchronously removes files and directories (modeled on the standard POSIX `rm`
3629+
utility). Returns `undefined`.
3630+
35843631
## `fs.stat(path[, options], callback)`
35853632
<!-- YAML
35863633
added: v0.0.2
@@ -5447,9 +5494,9 @@ changes:
54475494
* `options` {Object}
54485495
* `maxRetries` {integer} If an `EBUSY`, `EMFILE`, `ENFILE`, `ENOTEMPTY`, or
54495496
`EPERM` error is encountered, Node.js will retry the operation with a linear
5450-
backoff wait of `retryDelay` ms longer on each try. This option represents
5451-
the number of retries. This option is ignored if the `recursive` option is
5452-
not `true`. **Default:** `0`.
5497+
backoff wait of `retryDelay` milliseconds longer on each try. This option
5498+
represents the number of retries. This option is ignored if the `recursive`
5499+
option is not `true`. **Default:** `0`.
54535500
* `recursive` {boolean} If `true`, perform a recursive directory removal. In
54545501
recursive mode, errors are not reported if `path` does not exist, and
54555502
operations are retried on failure. **Default:** `false`.
@@ -5471,6 +5518,28 @@ that represent files will be deleted. The permissive behavior of the
54715518
`recursive` option is deprecated, `ENOTDIR` and `ENOENT` will be thrown in
54725519
the future.
54735520

5521+
## `fsPromises.rm(path[, options])`
5522+
<!-- YAML
5523+
added: REPLACEME
5524+
-->
5525+
5526+
* `path` {string|Buffer|URL}
5527+
* `options` {Object}
5528+
* `force` Ignore errors
5529+
* `maxRetries` {integer} If an `EBUSY`, `EMFILE`, `ENFILE`, `ENOTEMPTY`, or
5530+
`EPERM` error is encountered, Node.js will retry the operation with a linear
5531+
backoff wait of `retryDelay` milliseconds longer on each try. This option
5532+
represents the number of retries. This option is ignored if the `recursive`
5533+
option is not `true`. **Default:** `0`.
5534+
* `recursive` {boolean} If `true`, perform a recursive directory removal. In
5535+
recursive mode operations are retried on failure. **Default:** `false`.
5536+
* `retryDelay` {integer} The amount of time in milliseconds to wait between
5537+
retries. This option is ignored if the `recursive` option is not `true`.
5538+
**Default:** `100`.
5539+
5540+
Synchronously removes files and directories (modeled on the standard POSIX `rm`
5541+
utility). Resolves the `Promise` with no arguments on success.
5542+
54745543
### `fsPromises.stat(path[, options])`
54755544
<!-- YAML
54765545
added: v10.0.0

‎lib/fs.js

+48-11
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ const {
9797
validateOffsetLengthRead,
9898
validateOffsetLengthWrite,
9999
validatePath,
100+
validateRmOptions,
101+
validateRmOptionsSync,
100102
validateRmdirOptions,
101103
validateStringAfterArrayBufferView,
102104
warnOnNonPortableTemplate
@@ -855,30 +857,63 @@ function rmdir(path, options, callback) {
855857

856858
callback = makeCallback(callback);
857859
path = pathModule.toNamespacedPath(getValidatedPath(path));
858-
options = validateRmdirOptions(options);
859860

860-
if (options.recursive) {
861-
lazyLoadRimraf();
862-
return rimraf(path, options, callback);
863-
}
861+
if (options && options.recursive) {
862+
options = validateRmOptions(
863+
path,
864+
{ ...options, force: true },
865+
(err, options) => {
866+
if (err) {
867+
return callback(err);
868+
}
864869

865-
const req = new FSReqCallback();
866-
req.oncomplete = callback;
867-
binding.rmdir(path, req);
870+
lazyLoadRimraf();
871+
return rimraf(path, options, callback);
872+
});
873+
874+
} else {
875+
options = validateRmdirOptions(options);
876+
const req = new FSReqCallback();
877+
req.oncomplete = callback;
878+
return binding.rmdir(path, req);
879+
}
868880
}
869881

870882
function rmdirSync(path, options) {
871883
path = getValidatedPath(path);
872-
options = validateRmdirOptions(options);
873884

874-
if (options.recursive) {
885+
if (options && options.recursive) {
886+
options = validateRmOptionsSync(path, { ...options, force: true });
875887
lazyLoadRimraf();
876888
return rimrafSync(pathModule.toNamespacedPath(path), options);
877889
}
878890

891+
options = validateRmdirOptions(options);
879892
const ctx = { path };
880893
binding.rmdir(pathModule.toNamespacedPath(path), undefined, ctx);
881-
handleErrorFromBinding(ctx);
894+
return handleErrorFromBinding(ctx);
895+
}
896+
897+
function rm(path, options, callback) {
898+
if (typeof options === 'function') {
899+
callback = options;
900+
options = undefined;
901+
}
902+
903+
validateRmOptions(path, options, (err, options) => {
904+
if (err) {
905+
return callback(err);
906+
}
907+
lazyLoadRimraf();
908+
return rimraf(pathModule.toNamespacedPath(path), options, callback);
909+
});
910+
}
911+
912+
function rmSync(path, options) {
913+
options = validateRmOptionsSync(path, options);
914+
915+
lazyLoadRimraf();
916+
return rimrafSync(pathModule.toNamespacedPath(path), options);
882917
}
883918

884919
function fdatasync(fd, callback) {
@@ -2040,6 +2075,8 @@ module.exports = fs = {
20402075
realpathSync,
20412076
rename,
20422077
renameSync,
2078+
rm,
2079+
rmSync,
20432080
rmdir,
20442081
rmdirSync,
20452082
stat,

‎lib/internal/errors.js

+1
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,7 @@ E('ERR_FEATURE_UNAVAILABLE_ON_PLATFORM',
839839
'The feature %s is unavailable on the current platform' +
840840
', which is being used to run Node.js',
841841
TypeError);
842+
E('ERR_FS_EISDIR', 'Path is a directory', SystemError);
842843
E('ERR_FS_FILE_TOO_LARGE', 'File size (%s) is greater than 2 GB', RangeError);
843844
E('ERR_FS_INVALID_SYMLINK_TYPE',
844845
'Symlink type must be one of "dir", "file", or "junction". Received "%s"',

‎lib/internal/fs/promises.js

+9
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ const {
5252
validateBufferArray,
5353
validateOffsetLengthRead,
5454
validateOffsetLengthWrite,
55+
validateRmOptions,
5556
validateRmdirOptions,
5657
validateStringAfterArrayBufferView,
5758
warnOnNonPortableTemplate
@@ -79,6 +80,7 @@ const {
7980
} = require('internal/worker/js_transferable');
8081

8182
const getDirectoryEntriesPromise = promisify(getDirents);
83+
const validateRmOptionsPromise = promisify(validateRmOptions);
8284

8385
class FileHandle extends JSTransferable {
8486
constructor(filehandle) {
@@ -417,6 +419,12 @@ async function ftruncate(handle, len = 0) {
417419
return binding.ftruncate(handle.fd, len, kUsePromises);
418420
}
419421

422+
async function rm(path, options) {
423+
path = pathModule.toNamespacedPath(getValidatedPath(path));
424+
options = await validateRmOptionsPromise(path, options);
425+
return rimrafPromises(path, options);
426+
}
427+
420428
async function rmdir(path, options) {
421429
path = pathModule.toNamespacedPath(getValidatedPath(path));
422430
options = validateRmdirOptions(options);
@@ -635,6 +643,7 @@ module.exports = {
635643
opendir: promisify(opendir),
636644
rename,
637645
truncate,
646+
rm,
638647
rmdir,
639648
mkdir,
640649
readdir,

‎lib/internal/fs/utils.js

+87-10
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const {
1717
const { Buffer } = require('buffer');
1818
const {
1919
codes: {
20+
ERR_FS_EISDIR,
2021
ERR_FS_INVALID_SYMLINK_TYPE,
2122
ERR_INVALID_ARG_TYPE,
2223
ERR_INVALID_ARG_VALUE,
@@ -650,29 +651,103 @@ function warnOnNonPortableTemplate(template) {
650651
}
651652
}
652653

654+
const defaultRmOptions = {
655+
recursive: false,
656+
force: false,
657+
retryDelay: 100,
658+
maxRetries: 0
659+
};
660+
653661
const defaultRmdirOptions = {
654662
retryDelay: 100,
655663
maxRetries: 0,
656664
recursive: false,
657665
};
658666

659-
const validateRmdirOptions = hideStackFrames((options) => {
660-
if (options === undefined)
661-
return defaultRmdirOptions;
662-
if (options === null || typeof options !== 'object')
663-
throw new ERR_INVALID_ARG_TYPE('options', 'object', options);
667+
const validateRmOptions = hideStackFrames((path, options, callback) => {
668+
try {
669+
options = validateRmdirOptions(options, defaultRmOptions);
670+
} catch (err) {
671+
return callback(err);
672+
}
664673

665-
options = { ...defaultRmdirOptions, ...options };
674+
if (typeof options.force !== 'boolean')
675+
return callback(
676+
new ERR_INVALID_ARG_TYPE('force', 'boolean', options.force)
677+
);
666678

667-
if (typeof options.recursive !== 'boolean')
668-
throw new ERR_INVALID_ARG_TYPE('recursive', 'boolean', options.recursive);
679+
lazyLoadFs().stat(path, (err, stats) => {
680+
if (err && err.code === 'ENOENT') {
681+
if (options.force) {
682+
return callback(null, options);
683+
}
684+
return callback(err, options);
685+
}
686+
687+
if (err) {
688+
return callback(err);
689+
}
690+
691+
if (stats.isDirectory() && !options.recursive) {
692+
return callback(new ERR_FS_EISDIR({
693+
code: 'EISDIR',
694+
message: 'is a directory',
695+
path,
696+
syscall: 'rm',
697+
errno: -21
698+
}));
699+
}
700+
return callback(null, options);
701+
});
702+
});
669703

670-
validateInt32(options.retryDelay, 'retryDelay', 0);
671-
validateUint32(options.maxRetries, 'maxRetries');
704+
const validateRmOptionsSync = hideStackFrames((path, options) => {
705+
options = validateRmdirOptions(options, defaultRmOptions);
706+
707+
if (typeof options.force !== 'boolean')
708+
throw new ERR_INVALID_ARG_TYPE('force', 'boolean', options.force);
709+
710+
try {
711+
const stats = lazyLoadFs().statSync(path);
712+
713+
if (stats.isDirectory() && !options.recursive) {
714+
throw new ERR_FS_EISDIR({
715+
code: 'EISDIR',
716+
message: 'is a directory',
717+
path,
718+
syscall: 'rm',
719+
errno: -21
720+
});
721+
}
722+
} catch (err) {
723+
if (err.code !== 'ENOENT') {
724+
throw err;
725+
} else if (err.code === 'ENOENT' && !options.force) {
726+
throw err;
727+
}
728+
}
672729

673730
return options;
674731
});
675732

733+
const validateRmdirOptions = hideStackFrames(
734+
(options, defaults = defaultRmdirOptions) => {
735+
if (options === undefined)
736+
return defaults;
737+
if (options === null || typeof options !== 'object')
738+
throw new ERR_INVALID_ARG_TYPE('options', 'object', options);
739+
740+
options = { ...defaults, ...options };
741+
742+
if (typeof options.recursive !== 'boolean')
743+
throw new ERR_INVALID_ARG_TYPE('recursive', 'boolean', options.recursive);
744+
745+
validateInt32(options.retryDelay, 'retryDelay', 0);
746+
validateUint32(options.maxRetries, 'maxRetries');
747+
748+
return options;
749+
});
750+
676751
const getValidMode = hideStackFrames((mode, type) => {
677752
let min = kMinimumAccessMode;
678753
let max = kMaximumAccessMode;
@@ -741,6 +816,8 @@ module.exports = {
741816
validateOffsetLengthRead,
742817
validateOffsetLengthWrite,
743818
validatePath,
819+
validateRmOptions,
820+
validateRmOptionsSync,
744821
validateRmdirOptions,
745822
validateStringAfterArrayBufferView,
746823
warnOnNonPortableTemplate

‎test/common/tmpdir.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ const fs = require('fs');
55
const path = require('path');
66
const { isMainThread } = require('worker_threads');
77

8-
function rimrafSync(pathname) {
9-
fs.rmdirSync(pathname, { maxRetries: 3, recursive: true });
8+
function rmSync(pathname) {
9+
fs.rmSync(pathname, { maxRetries: 3, recursive: true, force: true });
1010
}
1111

1212
const testRoot = process.env.NODE_TEST_DIR ?
@@ -20,7 +20,7 @@ const tmpPath = path.join(testRoot, tmpdirName);
2020

2121
let firstRefresh = true;
2222
function refresh() {
23-
rimrafSync(this.path);
23+
rmSync(this.path);
2424
fs.mkdirSync(this.path);
2525

2626
if (firstRefresh) {
@@ -37,7 +37,7 @@ function onexit() {
3737
process.chdir(testRoot);
3838

3939
try {
40-
rimrafSync(tmpPath);
40+
rmSync(tmpPath);
4141
} catch (e) {
4242
console.error('Can\'t clean tmpdir:', tmpPath);
4343

‎test/parallel/test-fs-rm.js

+283
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
// Flags: --expose-internals
2+
'use strict';
3+
const common = require('../common');
4+
const tmpdir = require('../common/tmpdir');
5+
const assert = require('assert');
6+
const fs = require('fs');
7+
const path = require('path');
8+
const { validateRmOptionsSync } = require('internal/fs/utils');
9+
10+
tmpdir.refresh();
11+
12+
let count = 0;
13+
const nextDirPath = (name = 'rm') =>
14+
path.join(tmpdir.path, `${name}-${count++}`);
15+
16+
function makeNonEmptyDirectory(depth, files, folders, dirname, createSymLinks) {
17+
fs.mkdirSync(dirname, { recursive: true });
18+
fs.writeFileSync(path.join(dirname, 'text.txt'), 'hello', 'utf8');
19+
20+
const options = { flag: 'wx' };
21+
22+
for (let f = files; f > 0; f--) {
23+
fs.writeFileSync(path.join(dirname, `f-${depth}-${f}`), '', options);
24+
}
25+
26+
if (createSymLinks) {
27+
// Valid symlink
28+
fs.symlinkSync(
29+
`f-${depth}-1`,
30+
path.join(dirname, `link-${depth}-good`),
31+
'file'
32+
);
33+
34+
// Invalid symlink
35+
fs.symlinkSync(
36+
'does-not-exist',
37+
path.join(dirname, `link-${depth}-bad`),
38+
'file'
39+
);
40+
}
41+
42+
// File with a name that looks like a glob
43+
fs.writeFileSync(path.join(dirname, '[a-z0-9].txt'), '', options);
44+
45+
depth--;
46+
if (depth <= 0) {
47+
return;
48+
}
49+
50+
for (let f = folders; f > 0; f--) {
51+
fs.mkdirSync(
52+
path.join(dirname, `folder-${depth}-${f}`),
53+
{ recursive: true }
54+
);
55+
makeNonEmptyDirectory(
56+
depth,
57+
files,
58+
folders,
59+
path.join(dirname, `d-${depth}-${f}`),
60+
createSymLinks
61+
);
62+
}
63+
}
64+
65+
function removeAsync(dir) {
66+
// Removal should fail without the recursive option.
67+
fs.rm(dir, common.mustCall((err) => {
68+
assert.strictEqual(err.syscall, 'rm');
69+
70+
// Removal should fail without the recursive option set to true.
71+
fs.rm(dir, { recursive: false }, common.mustCall((err) => {
72+
assert.strictEqual(err.syscall, 'rm');
73+
74+
// Recursive removal should succeed.
75+
fs.rm(dir, { recursive: true }, common.mustCall((err) => {
76+
assert.ifError(err);
77+
78+
// Attempted removal should fail now because the directory is gone.
79+
fs.rm(dir, common.mustCall((err) => {
80+
assert.strictEqual(err.syscall, 'stat');
81+
}));
82+
}));
83+
}));
84+
}));
85+
}
86+
87+
// Test the asynchronous version
88+
{
89+
// Create a 4-level folder hierarchy including symlinks
90+
let dir = nextDirPath();
91+
makeNonEmptyDirectory(4, 10, 2, dir, true);
92+
removeAsync(dir);
93+
94+
// Create a 2-level folder hierarchy without symlinks
95+
dir = nextDirPath();
96+
makeNonEmptyDirectory(2, 10, 2, dir, false);
97+
removeAsync(dir);
98+
99+
// Create a flat folder including symlinks
100+
dir = nextDirPath();
101+
makeNonEmptyDirectory(1, 10, 2, dir, true);
102+
removeAsync(dir);
103+
104+
// Should fail if target does not exist
105+
fs.rm(
106+
path.join(tmpdir.path, 'noexist.txt'),
107+
{ recursive: true },
108+
common.mustCall((err) => {
109+
assert.strictEqual(err.code, 'ENOENT');
110+
})
111+
);
112+
113+
// Should delete a file
114+
const filePath = path.join(tmpdir.path, 'rm-async-file.txt');
115+
fs.writeFileSync(filePath, '');
116+
fs.rm(filePath, { recursive: true }, common.mustCall((err) => {
117+
try {
118+
assert.strictEqual(err, null);
119+
assert.strictEqual(fs.existsSync(filePath), false);
120+
} finally {
121+
fs.rmSync(filePath, { force: true });
122+
}
123+
}));
124+
}
125+
126+
// Test the synchronous version.
127+
{
128+
const dir = nextDirPath();
129+
makeNonEmptyDirectory(4, 10, 2, dir, true);
130+
131+
// Removal should fail without the recursive option set to true.
132+
assert.throws(() => {
133+
fs.rmSync(dir);
134+
}, { syscall: 'rm' });
135+
assert.throws(() => {
136+
fs.rmSync(dir, { recursive: false });
137+
}, { syscall: 'rm' });
138+
139+
// Should fail if target does not exist
140+
assert.throws(() => {
141+
fs.rmSync(path.join(tmpdir.path, 'noexist.txt'), { recursive: true });
142+
}, {
143+
code: 'ENOENT',
144+
name: 'Error',
145+
message: /^ENOENT: no such file or directory, stat/
146+
});
147+
148+
// Should delete a file
149+
const filePath = path.join(tmpdir.path, 'rm-file.txt');
150+
fs.writeFileSync(filePath, '');
151+
152+
try {
153+
fs.rmSync(filePath, { recursive: true });
154+
} finally {
155+
fs.rmSync(filePath, { force: true });
156+
}
157+
158+
// Recursive removal should succeed.
159+
fs.rmSync(dir, { recursive: true });
160+
161+
// Attempted removal should fail now because the directory is gone.
162+
assert.throws(() => fs.rmSync(dir), { syscall: 'stat' });
163+
}
164+
165+
// Test the Promises based version.
166+
(async () => {
167+
const dir = nextDirPath();
168+
makeNonEmptyDirectory(4, 10, 2, dir, true);
169+
170+
// Removal should fail without the recursive option set to true.
171+
assert.rejects(fs.promises.rm(dir), { syscall: 'rm' });
172+
assert.rejects(fs.promises.rm(dir, { recursive: false }), {
173+
syscall: 'rm'
174+
});
175+
176+
// Recursive removal should succeed.
177+
await fs.promises.rm(dir, { recursive: true });
178+
179+
// Attempted removal should fail now because the directory is gone.
180+
assert.rejects(fs.promises.rm(dir), { syscall: 'stat' });
181+
182+
// Should fail if target does not exist
183+
assert.rejects(fs.promises.rm(
184+
path.join(tmpdir.path, 'noexist.txt'),
185+
{ recursive: true }
186+
), {
187+
code: 'ENOENT',
188+
name: 'Error',
189+
message: /^ENOENT: no such file or directory, stat/
190+
});
191+
192+
// Should not fail if target does not exist and force option is true
193+
fs.promises.rm(path.join(tmpdir.path, 'noexist.txt'), { force: true });
194+
195+
// Should delete file
196+
const filePath = path.join(tmpdir.path, 'rm-promises-file.txt');
197+
fs.writeFileSync(filePath, '');
198+
199+
try {
200+
await fs.promises.rm(filePath, { recursive: true });
201+
} finally {
202+
fs.rmSync(filePath, { force: true });
203+
}
204+
})().then(common.mustCall());
205+
206+
// Test input validation.
207+
{
208+
const dir = nextDirPath();
209+
makeNonEmptyDirectory(4, 10, 2, dir, true);
210+
const filePath = (path.join(tmpdir.path, 'rm-args-file.txt'));
211+
fs.writeFileSync(filePath, '');
212+
213+
const defaults = {
214+
retryDelay: 100,
215+
maxRetries: 0,
216+
recursive: false,
217+
force: false
218+
};
219+
const modified = {
220+
retryDelay: 953,
221+
maxRetries: 5,
222+
recursive: true,
223+
force: false
224+
};
225+
226+
assert.deepStrictEqual(validateRmOptionsSync(filePath), defaults);
227+
assert.deepStrictEqual(validateRmOptionsSync(filePath, {}), defaults);
228+
assert.deepStrictEqual(validateRmOptionsSync(filePath, modified), modified);
229+
assert.deepStrictEqual(validateRmOptionsSync(filePath, {
230+
maxRetries: 99
231+
}), {
232+
retryDelay: 100,
233+
maxRetries: 99,
234+
recursive: false,
235+
force: false
236+
});
237+
238+
[null, 'foo', 5, NaN].forEach((bad) => {
239+
assert.throws(() => {
240+
validateRmOptionsSync(filePath, bad);
241+
}, {
242+
code: 'ERR_INVALID_ARG_TYPE',
243+
name: 'TypeError',
244+
message: /^The "options" argument must be of type object\./
245+
});
246+
});
247+
248+
[undefined, null, 'foo', Infinity, function() {}].forEach((bad) => {
249+
assert.throws(() => {
250+
validateRmOptionsSync(filePath, { recursive: bad });
251+
}, {
252+
code: 'ERR_INVALID_ARG_TYPE',
253+
name: 'TypeError',
254+
message: /^The "recursive" argument must be of type boolean\./
255+
});
256+
});
257+
258+
[undefined, null, 'foo', Infinity, function() {}].forEach((bad) => {
259+
assert.throws(() => {
260+
validateRmOptionsSync(filePath, { force: bad });
261+
}, {
262+
code: 'ERR_INVALID_ARG_TYPE',
263+
name: 'TypeError',
264+
message: /^The "force" argument must be of type boolean\./
265+
});
266+
});
267+
268+
assert.throws(() => {
269+
validateRmOptionsSync(filePath, { retryDelay: -1 });
270+
}, {
271+
code: 'ERR_OUT_OF_RANGE',
272+
name: 'RangeError',
273+
message: /^The value of "retryDelay" is out of range\./
274+
});
275+
276+
assert.throws(() => {
277+
validateRmOptionsSync(filePath, { maxRetries: -1 });
278+
}, {
279+
code: 'ERR_OUT_OF_RANGE',
280+
name: 'RangeError',
281+
message: /^The value of "maxRetries" is out of range\./
282+
});
283+
}

‎test/parallel/test-policy-parse-integrity.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ function hash(algo, body) {
2020
}
2121

2222
const tmpdirPath = path.join(tmpdir.path, 'test-policy-parse-integrity');
23-
fs.rmdirSync(tmpdirPath, { maxRetries: 3, recursive: true });
23+
fs.rmSync(tmpdirPath, { maxRetries: 3, recursive: true, force: true });
2424
fs.mkdirSync(tmpdirPath, { recursive: true });
2525

2626
const policyFilepath = path.join(tmpdirPath, 'policy');

‎test/pummel/test-policy-integrity.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ function drainQueue() {
109109
`deletable-policy-${testId}.json`
110110
);
111111
const cliPolicy = willDeletePolicy ? tmpPolicyPath : policyPath;
112-
fs.rmdirSync(configDirPath, { maxRetries: 3, recursive: true });
112+
fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true });
113113
fs.mkdirSync(configDirPath, { recursive: true });
114114
const manifest = {
115115
onerror: onError,
@@ -185,7 +185,7 @@ function drainQueue() {
185185
console.log(`stderr: ${Buffer.concat(stderr)}`);
186186
throw e;
187187
}
188-
fs.rmdirSync(configDirPath, { maxRetries: 3, recursive: true });
188+
fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true });
189189
drainQueue();
190190
});
191191
}

0 commit comments

Comments
 (0)
Please sign in to comment.