• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Flags: --expose-internals
2'use strict';
3const common = require('../common');
4const tmpdir = require('../common/tmpdir');
5const assert = require('assert');
6const fs = require('fs');
7const path = require('path');
8const { validateRmdirOptions } = require('internal/fs/utils');
9
10tmpdir.refresh();
11
12let count = 0;
13const nextDirPath = (name = 'rmdir-recursive') =>
14  path.join(tmpdir.path, `${name}-${count++}`);
15
16function 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
65function removeAsync(dir) {
66  // Removal should fail without the recursive option.
67  fs.rmdir(dir, common.mustCall((err) => {
68    assert.strictEqual(err.syscall, 'rmdir');
69
70    // Removal should fail without the recursive option set to true.
71    fs.rmdir(dir, { recursive: false }, common.mustCall((err) => {
72      assert.strictEqual(err.syscall, 'rmdir');
73
74      // Recursive removal should succeed.
75      fs.rmdir(dir, { recursive: true }, common.mustCall((err) => {
76        assert.ifError(err);
77
78        // No error should occur if recursive and the directory does not exist.
79        fs.rmdir(dir, { recursive: true }, common.mustCall((err) => {
80          assert.ifError(err);
81
82          // Attempted removal should fail now because the directory is gone.
83          fs.rmdir(dir, common.mustCall((err) => {
84            assert.strictEqual(err.syscall, 'rmdir');
85          }));
86        }));
87      }));
88    }));
89  }));
90}
91
92// Test the asynchronous version
93{
94  // Create a 4-level folder hierarchy including symlinks
95  let dir = nextDirPath();
96  makeNonEmptyDirectory(4, 10, 2, dir, true);
97  removeAsync(dir);
98
99  // Create a 2-level folder hierarchy without symlinks
100  dir = nextDirPath();
101  makeNonEmptyDirectory(2, 10, 2, dir, false);
102  removeAsync(dir);
103
104  // Create a flat folder including symlinks
105  dir = nextDirPath();
106  makeNonEmptyDirectory(1, 10, 2, dir, true);
107  removeAsync(dir);
108}
109
110// Test the synchronous version.
111{
112  const dir = nextDirPath();
113  makeNonEmptyDirectory(4, 10, 2, dir, true);
114
115  // Removal should fail without the recursive option set to true.
116  assert.throws(() => {
117    fs.rmdirSync(dir);
118  }, { syscall: 'rmdir' });
119  assert.throws(() => {
120    fs.rmdirSync(dir, { recursive: false });
121  }, { syscall: 'rmdir' });
122
123  // Recursive removal should succeed.
124  fs.rmdirSync(dir, { recursive: true });
125
126  // No error should occur if recursive and the directory does not exist.
127  fs.rmdirSync(dir, { recursive: true });
128
129  // Attempted removal should fail now because the directory is gone.
130  assert.throws(() => fs.rmdirSync(dir), { syscall: 'rmdir' });
131}
132
133// Test the Promises based version.
134(async () => {
135  const dir = nextDirPath();
136  makeNonEmptyDirectory(4, 10, 2, dir, true);
137
138  // Removal should fail without the recursive option set to true.
139  assert.rejects(fs.promises.rmdir(dir), { syscall: 'rmdir' });
140  assert.rejects(fs.promises.rmdir(dir, { recursive: false }), {
141    syscall: 'rmdir'
142  });
143
144  // Recursive removal should succeed.
145  await fs.promises.rmdir(dir, { recursive: true });
146
147  // No error should occur if recursive and the directory does not exist.
148  await fs.promises.rmdir(dir, { recursive: true });
149
150  // Attempted removal should fail now because the directory is gone.
151  assert.rejects(fs.promises.rmdir(dir), { syscall: 'rmdir' });
152})();
153
154// Test input validation.
155{
156  const defaults = {
157    retryDelay: 100,
158    maxRetries: 0,
159    recursive: false
160  };
161  const modified = {
162    retryDelay: 953,
163    maxRetries: 5,
164    recursive: true
165  };
166
167  assert.deepStrictEqual(validateRmdirOptions(), defaults);
168  assert.deepStrictEqual(validateRmdirOptions({}), defaults);
169  assert.deepStrictEqual(validateRmdirOptions(modified), modified);
170  assert.deepStrictEqual(validateRmdirOptions({
171    maxRetries: 99
172  }), {
173    retryDelay: 100,
174    maxRetries: 99,
175    recursive: false
176  });
177
178  [null, 'foo', 5, NaN].forEach((bad) => {
179    assert.throws(() => {
180      validateRmdirOptions(bad);
181    }, {
182      code: 'ERR_INVALID_ARG_TYPE',
183      name: 'TypeError',
184      message: /^The "options" argument must be of type object\./
185    });
186  });
187
188  [undefined, null, 'foo', Infinity, function() {}].forEach((bad) => {
189    assert.throws(() => {
190      validateRmdirOptions({ recursive: bad });
191    }, {
192      code: 'ERR_INVALID_ARG_TYPE',
193      name: 'TypeError',
194      message: /^The "recursive" argument must be of type boolean\./
195    });
196  });
197
198  assert.throws(() => {
199    validateRmdirOptions({ retryDelay: -1 });
200  }, {
201    code: 'ERR_OUT_OF_RANGE',
202    name: 'RangeError',
203    message: /^The value of "retryDelay" is out of range\./
204  });
205
206  assert.throws(() => {
207    validateRmdirOptions({ maxRetries: -1 });
208  }, {
209    code: 'ERR_OUT_OF_RANGE',
210    name: 'RangeError',
211    message: /^The value of "maxRetries" is out of range\./
212  });
213}
214