11cb0ef41Sopenharmony_ci// Flags: --expose-internals
21cb0ef41Sopenharmony_ci'use strict';
31cb0ef41Sopenharmony_ciconst common = require('../common');
41cb0ef41Sopenharmony_ciconst tmpdir = require('../common/tmpdir');
51cb0ef41Sopenharmony_ciconst assert = require('assert');
61cb0ef41Sopenharmony_ciconst fs = require('fs');
71cb0ef41Sopenharmony_ciconst path = require('path');
81cb0ef41Sopenharmony_ciconst { validateRmdirOptions } = require('internal/fs/utils');
91cb0ef41Sopenharmony_ci
101cb0ef41Sopenharmony_cicommon.expectWarning(
111cb0ef41Sopenharmony_ci  'DeprecationWarning',
121cb0ef41Sopenharmony_ci  'In future versions of Node.js, fs.rmdir(path, { recursive: true }) ' +
131cb0ef41Sopenharmony_ci      'will be removed. Use fs.rm(path, { recursive: true }) instead',
141cb0ef41Sopenharmony_ci  'DEP0147'
151cb0ef41Sopenharmony_ci);
161cb0ef41Sopenharmony_ci
171cb0ef41Sopenharmony_citmpdir.refresh();
181cb0ef41Sopenharmony_ci
191cb0ef41Sopenharmony_cilet count = 0;
201cb0ef41Sopenharmony_ciconst nextDirPath = (name = 'rmdir-recursive') =>
211cb0ef41Sopenharmony_ci  path.join(tmpdir.path, `${name}-${count++}`);
221cb0ef41Sopenharmony_ci
231cb0ef41Sopenharmony_cifunction makeNonEmptyDirectory(depth, files, folders, dirname, createSymLinks) {
241cb0ef41Sopenharmony_ci  fs.mkdirSync(dirname, { recursive: true });
251cb0ef41Sopenharmony_ci  fs.writeFileSync(path.join(dirname, 'text.txt'), 'hello', 'utf8');
261cb0ef41Sopenharmony_ci
271cb0ef41Sopenharmony_ci  const options = { flag: 'wx' };
281cb0ef41Sopenharmony_ci
291cb0ef41Sopenharmony_ci  for (let f = files; f > 0; f--) {
301cb0ef41Sopenharmony_ci    fs.writeFileSync(path.join(dirname, `f-${depth}-${f}`), '', options);
311cb0ef41Sopenharmony_ci  }
321cb0ef41Sopenharmony_ci
331cb0ef41Sopenharmony_ci  if (createSymLinks) {
341cb0ef41Sopenharmony_ci    // Valid symlink
351cb0ef41Sopenharmony_ci    fs.symlinkSync(
361cb0ef41Sopenharmony_ci      `f-${depth}-1`,
371cb0ef41Sopenharmony_ci      path.join(dirname, `link-${depth}-good`),
381cb0ef41Sopenharmony_ci      'file'
391cb0ef41Sopenharmony_ci    );
401cb0ef41Sopenharmony_ci
411cb0ef41Sopenharmony_ci    // Invalid symlink
421cb0ef41Sopenharmony_ci    fs.symlinkSync(
431cb0ef41Sopenharmony_ci      'does-not-exist',
441cb0ef41Sopenharmony_ci      path.join(dirname, `link-${depth}-bad`),
451cb0ef41Sopenharmony_ci      'file'
461cb0ef41Sopenharmony_ci    );
471cb0ef41Sopenharmony_ci  }
481cb0ef41Sopenharmony_ci
491cb0ef41Sopenharmony_ci  // File with a name that looks like a glob
501cb0ef41Sopenharmony_ci  fs.writeFileSync(path.join(dirname, '[a-z0-9].txt'), '', options);
511cb0ef41Sopenharmony_ci
521cb0ef41Sopenharmony_ci  depth--;
531cb0ef41Sopenharmony_ci  if (depth <= 0) {
541cb0ef41Sopenharmony_ci    return;
551cb0ef41Sopenharmony_ci  }
561cb0ef41Sopenharmony_ci
571cb0ef41Sopenharmony_ci  for (let f = folders; f > 0; f--) {
581cb0ef41Sopenharmony_ci    fs.mkdirSync(
591cb0ef41Sopenharmony_ci      path.join(dirname, `folder-${depth}-${f}`),
601cb0ef41Sopenharmony_ci      { recursive: true }
611cb0ef41Sopenharmony_ci    );
621cb0ef41Sopenharmony_ci    makeNonEmptyDirectory(
631cb0ef41Sopenharmony_ci      depth,
641cb0ef41Sopenharmony_ci      files,
651cb0ef41Sopenharmony_ci      folders,
661cb0ef41Sopenharmony_ci      path.join(dirname, `d-${depth}-${f}`),
671cb0ef41Sopenharmony_ci      createSymLinks
681cb0ef41Sopenharmony_ci    );
691cb0ef41Sopenharmony_ci  }
701cb0ef41Sopenharmony_ci}
711cb0ef41Sopenharmony_ci
721cb0ef41Sopenharmony_cifunction removeAsync(dir) {
731cb0ef41Sopenharmony_ci  // Removal should fail without the recursive option.
741cb0ef41Sopenharmony_ci  fs.rmdir(dir, common.mustCall((err) => {
751cb0ef41Sopenharmony_ci    assert.strictEqual(err.syscall, 'rmdir');
761cb0ef41Sopenharmony_ci
771cb0ef41Sopenharmony_ci    // Removal should fail without the recursive option set to true.
781cb0ef41Sopenharmony_ci    fs.rmdir(dir, { recursive: false }, common.mustCall((err) => {
791cb0ef41Sopenharmony_ci      assert.strictEqual(err.syscall, 'rmdir');
801cb0ef41Sopenharmony_ci
811cb0ef41Sopenharmony_ci      // Recursive removal should succeed.
821cb0ef41Sopenharmony_ci      fs.rmdir(dir, { recursive: true }, common.mustSucceed(() => {
831cb0ef41Sopenharmony_ci        // An error should occur if recursive and the directory does not exist.
841cb0ef41Sopenharmony_ci        fs.rmdir(dir, { recursive: true }, common.mustCall((err) => {
851cb0ef41Sopenharmony_ci          assert.strictEqual(err.code, 'ENOENT');
861cb0ef41Sopenharmony_ci          // Attempted removal should fail now because the directory is gone.
871cb0ef41Sopenharmony_ci          fs.rmdir(dir, common.mustCall((err) => {
881cb0ef41Sopenharmony_ci            assert.strictEqual(err.syscall, 'rmdir');
891cb0ef41Sopenharmony_ci          }));
901cb0ef41Sopenharmony_ci        }));
911cb0ef41Sopenharmony_ci      }));
921cb0ef41Sopenharmony_ci    }));
931cb0ef41Sopenharmony_ci  }));
941cb0ef41Sopenharmony_ci}
951cb0ef41Sopenharmony_ci
961cb0ef41Sopenharmony_ci// Test the asynchronous version
971cb0ef41Sopenharmony_ci{
981cb0ef41Sopenharmony_ci  // Create a 4-level folder hierarchy including symlinks
991cb0ef41Sopenharmony_ci  let dir = nextDirPath();
1001cb0ef41Sopenharmony_ci  makeNonEmptyDirectory(4, 10, 2, dir, true);
1011cb0ef41Sopenharmony_ci  removeAsync(dir);
1021cb0ef41Sopenharmony_ci
1031cb0ef41Sopenharmony_ci  // Create a 2-level folder hierarchy without symlinks
1041cb0ef41Sopenharmony_ci  dir = nextDirPath();
1051cb0ef41Sopenharmony_ci  makeNonEmptyDirectory(2, 10, 2, dir, false);
1061cb0ef41Sopenharmony_ci  removeAsync(dir);
1071cb0ef41Sopenharmony_ci
1081cb0ef41Sopenharmony_ci  // Create a flat folder including symlinks
1091cb0ef41Sopenharmony_ci  dir = nextDirPath();
1101cb0ef41Sopenharmony_ci  makeNonEmptyDirectory(1, 10, 2, dir, true);
1111cb0ef41Sopenharmony_ci  removeAsync(dir);
1121cb0ef41Sopenharmony_ci}
1131cb0ef41Sopenharmony_ci
1141cb0ef41Sopenharmony_ci// Test the synchronous version.
1151cb0ef41Sopenharmony_ci{
1161cb0ef41Sopenharmony_ci  const dir = nextDirPath();
1171cb0ef41Sopenharmony_ci  makeNonEmptyDirectory(4, 10, 2, dir, true);
1181cb0ef41Sopenharmony_ci
1191cb0ef41Sopenharmony_ci  // Removal should fail without the recursive option set to true.
1201cb0ef41Sopenharmony_ci  assert.throws(() => {
1211cb0ef41Sopenharmony_ci    fs.rmdirSync(dir);
1221cb0ef41Sopenharmony_ci  }, { syscall: 'rmdir' });
1231cb0ef41Sopenharmony_ci  assert.throws(() => {
1241cb0ef41Sopenharmony_ci    fs.rmdirSync(dir, { recursive: false });
1251cb0ef41Sopenharmony_ci  }, { syscall: 'rmdir' });
1261cb0ef41Sopenharmony_ci
1271cb0ef41Sopenharmony_ci  // Recursive removal should succeed.
1281cb0ef41Sopenharmony_ci  fs.rmdirSync(dir, { recursive: true });
1291cb0ef41Sopenharmony_ci
1301cb0ef41Sopenharmony_ci  // An error should occur if recursive and the directory does not exist.
1311cb0ef41Sopenharmony_ci  assert.throws(() => fs.rmdirSync(dir, { recursive: true }),
1321cb0ef41Sopenharmony_ci                { code: 'ENOENT' });
1331cb0ef41Sopenharmony_ci
1341cb0ef41Sopenharmony_ci  // Attempted removal should fail now because the directory is gone.
1351cb0ef41Sopenharmony_ci  assert.throws(() => fs.rmdirSync(dir), { syscall: 'rmdir' });
1361cb0ef41Sopenharmony_ci}
1371cb0ef41Sopenharmony_ci
1381cb0ef41Sopenharmony_ci// Test the Promises based version.
1391cb0ef41Sopenharmony_ci(async () => {
1401cb0ef41Sopenharmony_ci  const dir = nextDirPath();
1411cb0ef41Sopenharmony_ci  makeNonEmptyDirectory(4, 10, 2, dir, true);
1421cb0ef41Sopenharmony_ci
1431cb0ef41Sopenharmony_ci  // Removal should fail without the recursive option set to true.
1441cb0ef41Sopenharmony_ci  await assert.rejects(fs.promises.rmdir(dir), { syscall: 'rmdir' });
1451cb0ef41Sopenharmony_ci  await assert.rejects(fs.promises.rmdir(dir, { recursive: false }), {
1461cb0ef41Sopenharmony_ci    syscall: 'rmdir'
1471cb0ef41Sopenharmony_ci  });
1481cb0ef41Sopenharmony_ci
1491cb0ef41Sopenharmony_ci  // Recursive removal should succeed.
1501cb0ef41Sopenharmony_ci  await fs.promises.rmdir(dir, { recursive: true });
1511cb0ef41Sopenharmony_ci
1521cb0ef41Sopenharmony_ci  // An error should occur if recursive and the directory does not exist.
1531cb0ef41Sopenharmony_ci  await assert.rejects(fs.promises.rmdir(dir, { recursive: true }),
1541cb0ef41Sopenharmony_ci                       { code: 'ENOENT' });
1551cb0ef41Sopenharmony_ci
1561cb0ef41Sopenharmony_ci  // Attempted removal should fail now because the directory is gone.
1571cb0ef41Sopenharmony_ci  await assert.rejects(fs.promises.rmdir(dir), { syscall: 'rmdir' });
1581cb0ef41Sopenharmony_ci})().then(common.mustCall());
1591cb0ef41Sopenharmony_ci
1601cb0ef41Sopenharmony_ci// Test input validation.
1611cb0ef41Sopenharmony_ci{
1621cb0ef41Sopenharmony_ci  const defaults = {
1631cb0ef41Sopenharmony_ci    retryDelay: 100,
1641cb0ef41Sopenharmony_ci    maxRetries: 0,
1651cb0ef41Sopenharmony_ci    recursive: false
1661cb0ef41Sopenharmony_ci  };
1671cb0ef41Sopenharmony_ci  const modified = {
1681cb0ef41Sopenharmony_ci    retryDelay: 953,
1691cb0ef41Sopenharmony_ci    maxRetries: 5,
1701cb0ef41Sopenharmony_ci    recursive: true
1711cb0ef41Sopenharmony_ci  };
1721cb0ef41Sopenharmony_ci
1731cb0ef41Sopenharmony_ci  assert.deepStrictEqual(validateRmdirOptions(), defaults);
1741cb0ef41Sopenharmony_ci  assert.deepStrictEqual(validateRmdirOptions({}), defaults);
1751cb0ef41Sopenharmony_ci  assert.deepStrictEqual(validateRmdirOptions(modified), modified);
1761cb0ef41Sopenharmony_ci  assert.deepStrictEqual(validateRmdirOptions({
1771cb0ef41Sopenharmony_ci    maxRetries: 99
1781cb0ef41Sopenharmony_ci  }), {
1791cb0ef41Sopenharmony_ci    retryDelay: 100,
1801cb0ef41Sopenharmony_ci    maxRetries: 99,
1811cb0ef41Sopenharmony_ci    recursive: false
1821cb0ef41Sopenharmony_ci  });
1831cb0ef41Sopenharmony_ci
1841cb0ef41Sopenharmony_ci  [null, 'foo', 5, NaN].forEach((bad) => {
1851cb0ef41Sopenharmony_ci    assert.throws(() => {
1861cb0ef41Sopenharmony_ci      validateRmdirOptions(bad);
1871cb0ef41Sopenharmony_ci    }, {
1881cb0ef41Sopenharmony_ci      code: 'ERR_INVALID_ARG_TYPE',
1891cb0ef41Sopenharmony_ci      name: 'TypeError',
1901cb0ef41Sopenharmony_ci      message: /^The "options" argument must be of type object\./
1911cb0ef41Sopenharmony_ci    });
1921cb0ef41Sopenharmony_ci  });
1931cb0ef41Sopenharmony_ci
1941cb0ef41Sopenharmony_ci  [undefined, null, 'foo', Infinity, function() {}].forEach((bad) => {
1951cb0ef41Sopenharmony_ci    assert.throws(() => {
1961cb0ef41Sopenharmony_ci      validateRmdirOptions({ recursive: bad });
1971cb0ef41Sopenharmony_ci    }, {
1981cb0ef41Sopenharmony_ci      code: 'ERR_INVALID_ARG_TYPE',
1991cb0ef41Sopenharmony_ci      name: 'TypeError',
2001cb0ef41Sopenharmony_ci      message: /^The "options\.recursive" property must be of type boolean\./
2011cb0ef41Sopenharmony_ci    });
2021cb0ef41Sopenharmony_ci  });
2031cb0ef41Sopenharmony_ci
2041cb0ef41Sopenharmony_ci  assert.throws(() => {
2051cb0ef41Sopenharmony_ci    validateRmdirOptions({ retryDelay: -1 });
2061cb0ef41Sopenharmony_ci  }, {
2071cb0ef41Sopenharmony_ci    code: 'ERR_OUT_OF_RANGE',
2081cb0ef41Sopenharmony_ci    name: 'RangeError',
2091cb0ef41Sopenharmony_ci    message: /^The value of "options\.retryDelay" is out of range\./
2101cb0ef41Sopenharmony_ci  });
2111cb0ef41Sopenharmony_ci
2121cb0ef41Sopenharmony_ci  assert.throws(() => {
2131cb0ef41Sopenharmony_ci    validateRmdirOptions({ maxRetries: -1 });
2141cb0ef41Sopenharmony_ci  }, {
2151cb0ef41Sopenharmony_ci    code: 'ERR_OUT_OF_RANGE',
2161cb0ef41Sopenharmony_ci    name: 'RangeError',
2171cb0ef41Sopenharmony_ci    message: /^The value of "options\.maxRetries" is out of range\./
2181cb0ef41Sopenharmony_ci  });
2191cb0ef41Sopenharmony_ci}
2201cb0ef41Sopenharmony_ci
2211cb0ef41Sopenharmony_ci// It should not pass recursive option to rmdirSync, when called from
2221cb0ef41Sopenharmony_ci// rimraf (see: #35566)
2231cb0ef41Sopenharmony_ci{
2241cb0ef41Sopenharmony_ci  // Make a non-empty directory:
2251cb0ef41Sopenharmony_ci  const original = fs.rmdirSync;
2261cb0ef41Sopenharmony_ci  const dir = `${nextDirPath()}/foo/bar`;
2271cb0ef41Sopenharmony_ci  fs.mkdirSync(dir, { recursive: true });
2281cb0ef41Sopenharmony_ci  fs.writeFileSync(`${dir}/foo.txt`, 'hello world', 'utf8');
2291cb0ef41Sopenharmony_ci
2301cb0ef41Sopenharmony_ci  // When called the second time from rimraf, the recursive option should
2311cb0ef41Sopenharmony_ci  // not be set for rmdirSync:
2321cb0ef41Sopenharmony_ci  let callCount = 0;
2331cb0ef41Sopenharmony_ci  let rmdirSyncOptionsFromRimraf;
2341cb0ef41Sopenharmony_ci  fs.rmdirSync = (path, options) => {
2351cb0ef41Sopenharmony_ci    if (callCount > 0) {
2361cb0ef41Sopenharmony_ci      rmdirSyncOptionsFromRimraf = { ...options };
2371cb0ef41Sopenharmony_ci    }
2381cb0ef41Sopenharmony_ci    callCount++;
2391cb0ef41Sopenharmony_ci    return original(path, options);
2401cb0ef41Sopenharmony_ci  };
2411cb0ef41Sopenharmony_ci  fs.rmdirSync(dir, { recursive: true });
2421cb0ef41Sopenharmony_ci  fs.rmdirSync = original;
2431cb0ef41Sopenharmony_ci  assert.strictEqual(rmdirSyncOptionsFromRimraf.recursive, undefined);
2441cb0ef41Sopenharmony_ci}
245