From aac7925801edfc8dd1de051a29aac85332e7d200 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Sun, 10 Aug 2025 21:35:42 +0200 Subject: [PATCH] test: split test-fs-cp.js This test previously squeezed 70+ test cases into one single file and has been constantly crashing on Windows with exit code 3221226505 and no stack trace. As it is already marked as flaky there is no way to understand which test case is failing and the Windows CI was constantly orange. This patch splits the test cases into different files so it's easier to find out which case is exactly failing and to be skipped. PR-URL: https://github.com/nodejs/node/pull/59408 Refs: https://github.com/nodejs/node/issues/56794 Reviewed-By: Luigi Pinca Reviewed-By: Zeyu "Alex" Yang Reviewed-By: Daeyeon Jeong Reviewed-By: Filip Skokan --- test/common/fs.js | 49 + test/parallel/parallel.status | 10 +- ...test-fs-cp-async-async-filter-function.mjs | 32 + ...fs-cp-async-copy-non-directory-symlink.mjs | 22 + ...nc-dereference-force-false-silent-fail.mjs | 25 + .../test-fs-cp-async-dereference-symlink.mjs | 27 + ...async-dest-symlink-points-to-src-error.mjs | 21 + .../parallel/test-fs-cp-async-dir-to-file.mjs | 17 + .../test-fs-cp-async-error-on-exist.mjs | 22 + .../parallel/test-fs-cp-async-file-to-dir.mjs | 17 + .../test-fs-cp-async-file-to-file.mjs | 19 + test/parallel/test-fs-cp-async-file-url.mjs | 18 + .../test-fs-cp-async-filter-child-folder.mjs | 26 + .../test-fs-cp-async-filter-function.mjs | 31 + .../test-fs-cp-async-identical-src-dest.mjs | 14 + .../test-fs-cp-async-invalid-mode-range.mjs | 13 + .../test-fs-cp-async-invalid-options-type.mjs | 13 + .../test-fs-cp-async-nested-files-folders.mjs | 17 + ...test-fs-cp-async-no-errors-force-false.mjs | 28 + .../test-fs-cp-async-no-recursive.mjs | 16 + ...test-fs-cp-async-overwrites-force-true.mjs | 23 + ...sync-preserve-timestamps-readonly-file.mjs | 26 + .../test-fs-cp-async-preserve-timestamps.mjs | 24 + .../test-fs-cp-async-same-dir-twice.mjs | 25 + ...cp-async-skip-validation-when-filtered.mjs | 29 + test/parallel/test-fs-cp-async-socket.mjs | 34 + .../test-fs-cp-async-subdirectory-of-self.mjs | 12 + ...fs-cp-async-symlink-dest-points-to-src.mjs | 21 + .../test-fs-cp-async-symlink-over-file.mjs | 21 + ...est-fs-cp-async-symlink-points-to-dest.mjs | 20 + .../test-fs-cp-async-with-mode-flags.mjs | 31 + .../test-fs-cp-promises-async-error.mjs | 23 + .../parallel/test-fs-cp-promises-file-url.mjs | 21 + .../test-fs-cp-promises-invalid-mode.mjs | 15 + .../test-fs-cp-promises-mode-flags.mjs | 36 + ...fs-cp-promises-nested-folder-recursive.mjs | 16 + ...test-fs-cp-promises-options-validation.mjs | 13 + .../test-fs-cp-sync-apply-filter-function.mjs | 28 + .../test-fs-cp-sync-async-filter-error.mjs | 24 + ...s-cp-sync-copy-directory-to-file-error.mjs | 24 + ...copy-directory-without-recursive-error.mjs | 16 + ...s-cp-sync-copy-file-to-directory-error.mjs | 22 + ...test-fs-cp-sync-copy-file-to-file-path.mjs | 13 + .../test-fs-cp-sync-copy-socket-error.mjs | 34 + ...nc-copy-symlink-not-pointing-to-folder.mjs | 19 + ...s-cp-sync-copy-symlink-over-file-error.mjs | 21 + ...ync-copy-symlinks-to-existing-symlinks.mjs | 17 + ...-fs-cp-sync-copy-to-subdirectory-error.mjs | 14 + .../test-fs-cp-sync-dereference-directory.mjs | 23 + .../test-fs-cp-sync-dereference-file.mjs | 23 + .../test-fs-cp-sync-dereference-twice.mjs | 20 + ...test-fs-cp-sync-dest-name-prefix-match.mjs | 14 + ...-cp-sync-dest-parent-name-prefix-match.mjs | 16 + ...t-fs-cp-sync-directory-not-exist-error.mjs | 15 + .../test-fs-cp-sync-error-on-exist.mjs | 22 + test/parallel/test-fs-cp-sync-file-url.mjs | 14 + ...est-fs-cp-sync-filename-too-long-error.mjs | 17 + ...-fs-cp-sync-incompatible-options-error.mjs | 14 + test/parallel/test-fs-cp-sync-mode-flags.mjs | 30 + .../parallel/test-fs-cp-sync-mode-invalid.mjs | 12 + .../test-fs-cp-sync-nested-files-folders.mjs | 13 + ...st-fs-cp-sync-no-overwrite-force-false.mjs | 21 + ...-fs-cp-sync-options-invalid-type-error.mjs | 12 + .../test-fs-cp-sync-overwrite-force-true.mjs | 19 + ...arent-symlink-dest-points-to-src-error.mjs | 26 + ...s-cp-sync-preserve-timestamps-readonly.mjs | 24 + .../test-fs-cp-sync-preserve-timestamps.mjs | 18 + ...sync-resolve-relative-symlinks-default.mjs | 21 + ...p-sync-resolve-relative-symlinks-false.mjs | 21 + ...st-fs-cp-sync-src-dest-identical-error.mjs | 14 + ...st-fs-cp-sync-src-parent-of-dest-error.mjs | 25 + ...-sync-symlink-dest-points-to-src-error.mjs | 21 + ...s-cp-sync-symlink-points-to-dest-error.mjs | 22 + .../test-fs-cp-sync-unicode-folder-names.mjs | 13 + ...t-fs-cp-sync-verbatim-symlinks-invalid.mjs | 17 + ...test-fs-cp-sync-verbatim-symlinks-true.mjs | 21 + test/parallel/test-fs-cp.mjs | 1098 ----------------- 77 files changed, 1585 insertions(+), 1100 deletions(-) create mode 100644 test/common/fs.js create mode 100644 test/parallel/test-fs-cp-async-async-filter-function.mjs create mode 100644 test/parallel/test-fs-cp-async-copy-non-directory-symlink.mjs create mode 100644 test/parallel/test-fs-cp-async-dereference-force-false-silent-fail.mjs create mode 100644 test/parallel/test-fs-cp-async-dereference-symlink.mjs create mode 100644 test/parallel/test-fs-cp-async-dest-symlink-points-to-src-error.mjs create mode 100644 test/parallel/test-fs-cp-async-dir-to-file.mjs create mode 100644 test/parallel/test-fs-cp-async-error-on-exist.mjs create mode 100644 test/parallel/test-fs-cp-async-file-to-dir.mjs create mode 100644 test/parallel/test-fs-cp-async-file-to-file.mjs create mode 100644 test/parallel/test-fs-cp-async-file-url.mjs create mode 100644 test/parallel/test-fs-cp-async-filter-child-folder.mjs create mode 100644 test/parallel/test-fs-cp-async-filter-function.mjs create mode 100644 test/parallel/test-fs-cp-async-identical-src-dest.mjs create mode 100644 test/parallel/test-fs-cp-async-invalid-mode-range.mjs create mode 100644 test/parallel/test-fs-cp-async-invalid-options-type.mjs create mode 100644 test/parallel/test-fs-cp-async-nested-files-folders.mjs create mode 100644 test/parallel/test-fs-cp-async-no-errors-force-false.mjs create mode 100644 test/parallel/test-fs-cp-async-no-recursive.mjs create mode 100644 test/parallel/test-fs-cp-async-overwrites-force-true.mjs create mode 100644 test/parallel/test-fs-cp-async-preserve-timestamps-readonly-file.mjs create mode 100644 test/parallel/test-fs-cp-async-preserve-timestamps.mjs create mode 100644 test/parallel/test-fs-cp-async-same-dir-twice.mjs create mode 100644 test/parallel/test-fs-cp-async-skip-validation-when-filtered.mjs create mode 100644 test/parallel/test-fs-cp-async-socket.mjs create mode 100644 test/parallel/test-fs-cp-async-subdirectory-of-self.mjs create mode 100644 test/parallel/test-fs-cp-async-symlink-dest-points-to-src.mjs create mode 100644 test/parallel/test-fs-cp-async-symlink-over-file.mjs create mode 100644 test/parallel/test-fs-cp-async-symlink-points-to-dest.mjs create mode 100644 test/parallel/test-fs-cp-async-with-mode-flags.mjs create mode 100644 test/parallel/test-fs-cp-promises-async-error.mjs create mode 100644 test/parallel/test-fs-cp-promises-file-url.mjs create mode 100644 test/parallel/test-fs-cp-promises-invalid-mode.mjs create mode 100644 test/parallel/test-fs-cp-promises-mode-flags.mjs create mode 100644 test/parallel/test-fs-cp-promises-nested-folder-recursive.mjs create mode 100644 test/parallel/test-fs-cp-promises-options-validation.mjs create mode 100644 test/parallel/test-fs-cp-sync-apply-filter-function.mjs create mode 100644 test/parallel/test-fs-cp-sync-async-filter-error.mjs create mode 100644 test/parallel/test-fs-cp-sync-copy-directory-to-file-error.mjs create mode 100644 test/parallel/test-fs-cp-sync-copy-directory-without-recursive-error.mjs create mode 100644 test/parallel/test-fs-cp-sync-copy-file-to-directory-error.mjs create mode 100644 test/parallel/test-fs-cp-sync-copy-file-to-file-path.mjs create mode 100644 test/parallel/test-fs-cp-sync-copy-socket-error.mjs create mode 100644 test/parallel/test-fs-cp-sync-copy-symlink-not-pointing-to-folder.mjs create mode 100644 test/parallel/test-fs-cp-sync-copy-symlink-over-file-error.mjs create mode 100644 test/parallel/test-fs-cp-sync-copy-symlinks-to-existing-symlinks.mjs create mode 100644 test/parallel/test-fs-cp-sync-copy-to-subdirectory-error.mjs create mode 100644 test/parallel/test-fs-cp-sync-dereference-directory.mjs create mode 100644 test/parallel/test-fs-cp-sync-dereference-file.mjs create mode 100644 test/parallel/test-fs-cp-sync-dereference-twice.mjs create mode 100644 test/parallel/test-fs-cp-sync-dest-name-prefix-match.mjs create mode 100644 test/parallel/test-fs-cp-sync-dest-parent-name-prefix-match.mjs create mode 100644 test/parallel/test-fs-cp-sync-directory-not-exist-error.mjs create mode 100644 test/parallel/test-fs-cp-sync-error-on-exist.mjs create mode 100644 test/parallel/test-fs-cp-sync-file-url.mjs create mode 100644 test/parallel/test-fs-cp-sync-filename-too-long-error.mjs create mode 100644 test/parallel/test-fs-cp-sync-incompatible-options-error.mjs create mode 100644 test/parallel/test-fs-cp-sync-mode-flags.mjs create mode 100644 test/parallel/test-fs-cp-sync-mode-invalid.mjs create mode 100644 test/parallel/test-fs-cp-sync-nested-files-folders.mjs create mode 100644 test/parallel/test-fs-cp-sync-no-overwrite-force-false.mjs create mode 100644 test/parallel/test-fs-cp-sync-options-invalid-type-error.mjs create mode 100644 test/parallel/test-fs-cp-sync-overwrite-force-true.mjs create mode 100644 test/parallel/test-fs-cp-sync-parent-symlink-dest-points-to-src-error.mjs create mode 100644 test/parallel/test-fs-cp-sync-preserve-timestamps-readonly.mjs create mode 100644 test/parallel/test-fs-cp-sync-preserve-timestamps.mjs create mode 100644 test/parallel/test-fs-cp-sync-resolve-relative-symlinks-default.mjs create mode 100644 test/parallel/test-fs-cp-sync-resolve-relative-symlinks-false.mjs create mode 100644 test/parallel/test-fs-cp-sync-src-dest-identical-error.mjs create mode 100644 test/parallel/test-fs-cp-sync-src-parent-of-dest-error.mjs create mode 100644 test/parallel/test-fs-cp-sync-symlink-dest-points-to-src-error.mjs create mode 100644 test/parallel/test-fs-cp-sync-symlink-points-to-dest-error.mjs create mode 100644 test/parallel/test-fs-cp-sync-unicode-folder-names.mjs create mode 100644 test/parallel/test-fs-cp-sync-verbatim-symlinks-invalid.mjs create mode 100644 test/parallel/test-fs-cp-sync-verbatim-symlinks-true.mjs delete mode 100644 test/parallel/test-fs-cp.mjs diff --git a/test/common/fs.js b/test/common/fs.js new file mode 100644 index 00000000000..fdf7cbece5c --- /dev/null +++ b/test/common/fs.js @@ -0,0 +1,49 @@ +'use strict'; + +const { mustNotMutateObjectDeep } = require('.'); +const { readdirSync } = require('node:fs'); +const { join } = require('node:path'); +const assert = require('node:assert'); +const tmpdir = require('./tmpdir.js'); + +let dirc = 0; +function nextdir(dirname) { + return tmpdir.resolve(dirname || `copy_%${++dirc}`); +} + +function assertDirEquivalent(dir1, dir2) { + const dir1Entries = []; + collectEntries(dir1, dir1Entries); + const dir2Entries = []; + collectEntries(dir2, dir2Entries); + assert.strictEqual(dir1Entries.length, dir2Entries.length); + for (const entry1 of dir1Entries) { + const entry2 = dir2Entries.find((entry) => { + return entry.name === entry1.name; + }); + assert(entry2, `entry ${entry2.name} not copied`); + if (entry1.isFile()) { + assert(entry2.isFile(), `${entry2.name} was not file`); + } else if (entry1.isDirectory()) { + assert(entry2.isDirectory(), `${entry2.name} was not directory`); + } else if (entry1.isSymbolicLink()) { + assert(entry2.isSymbolicLink(), `${entry2.name} was not symlink`); + } + } +} + +function collectEntries(dir, dirEntries) { + const newEntries = readdirSync(dir, mustNotMutateObjectDeep({ withFileTypes: true })); + for (const entry of newEntries) { + if (entry.isDirectory()) { + collectEntries(join(dir, entry.name), dirEntries); + } + } + dirEntries.push(...newEntries); +} + +module.exports = { + nextdir, + assertDirEquivalent, + collectEntries, +}; diff --git a/test/parallel/parallel.status b/test/parallel/parallel.status index 77682bc8498..76e46b80719 100644 --- a/test/parallel/parallel.status +++ b/test/parallel/parallel.status @@ -24,8 +24,14 @@ test-snapshot-incompatible: SKIP test-async-context-frame: PASS, FLAKY # https://github.com/nodejs/node/issues/54534 test-runner-run-watch: PASS, FLAKY -# https://github.com/nodejs/node/issues/56794 -test-fs-cp: PASS, FLAKY +# https://github.com/nodejs/node/pull/59408#issuecomment-3170650933 +test-fs-cp-sync-error-on-exist: PASS, FLAKY +test-fs-cp-sync-copy-symlink-not-pointing-to-folder: PASS, FLAKY +test-fs-cp-sync-symlink-points-to-dest-error: PASS, FLAKY +test-fs-cp-sync-resolve-relative-symlinks-false: PASS, FLAKY +test-fs-cp-async-symlink-points-to-dest: PASS, FLAKY +test-fs-cp-sync-unicode-folder-names: PASS, FLAKY +test-fs-cp-sync-resolve-relative-symlinks-default: PASS, FLAKY # https://github.com/nodejs/node/issues/56751 test-without-async-context-frame: PASS, FLAKY diff --git a/test/parallel/test-fs-cp-async-async-filter-function.mjs b/test/parallel/test-fs-cp-async-async-filter-function.mjs new file mode 100644 index 00000000000..91b17a0f882 --- /dev/null +++ b/test/parallel/test-fs-cp-async-async-filter-function.mjs @@ -0,0 +1,32 @@ +// This tests that cp() supports async filter function. + +import { mustCall } from '../common/index.mjs'; +import { nextdir, collectEntries } from '../common/fs.js'; +import assert from 'node:assert'; +import { cp, statSync } from 'node:fs'; +import { setTimeout } from 'node:timers/promises'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink'); +const dest = nextdir(); +cp(src, dest, { + filter: async (path) => { + await setTimeout(5, 'done'); + const pathStat = statSync(path); + return pathStat.isDirectory() || path.endsWith('.js'); + }, + dereference: true, + recursive: true, +}, mustCall((err) => { + assert.strictEqual(err, null); + const destEntries = []; + collectEntries(dest, destEntries); + for (const entry of destEntries) { + assert.strictEqual( + entry.isDirectory() || entry.name.endsWith('.js'), + true + ); + } +})); diff --git a/test/parallel/test-fs-cp-async-copy-non-directory-symlink.mjs b/test/parallel/test-fs-cp-async-copy-non-directory-symlink.mjs new file mode 100644 index 00000000000..a5064a9a71a --- /dev/null +++ b/test/parallel/test-fs-cp-async-copy-non-directory-symlink.mjs @@ -0,0 +1,22 @@ +// This tests that cp() copies link if it does not point to folder in src. + +import { mustCall, mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cp, mkdirSync, readlinkSync, symlinkSync } from 'node:fs'; +import { join } from 'node:path'; +import tmpdir from '../common/tmpdir.js'; + +tmpdir.refresh(); + +const src = nextdir(); +mkdirSync(join(src, 'a', 'b'), mustNotMutateObjectDeep({ recursive: true })); +symlinkSync(src, join(src, 'a', 'c')); +const dest = nextdir(); +mkdirSync(join(dest, 'a'), mustNotMutateObjectDeep({ recursive: true })); +symlinkSync(dest, join(dest, 'a', 'c')); +cp(src, dest, mustNotMutateObjectDeep({ recursive: true }), mustCall((err) => { + assert.strictEqual(err, null); + const link = readlinkSync(join(dest, 'a', 'c')); + assert.strictEqual(link, src); +})); diff --git a/test/parallel/test-fs-cp-async-dereference-force-false-silent-fail.mjs b/test/parallel/test-fs-cp-async-dereference-force-false-silent-fail.mjs new file mode 100644 index 00000000000..0c92b3eb627 --- /dev/null +++ b/test/parallel/test-fs-cp-async-dereference-force-false-silent-fail.mjs @@ -0,0 +1,25 @@ +// This tests that it does not fail if the same directory is copied to dest +// twice, when dereference is true, and force is false (fails silently). + +import { mustCall, mustNotMutateObjectDeep } from '../common/index.mjs'; +import assert from 'node:assert'; +import { cp, cpSync, lstatSync } from 'node:fs'; +import { join } from 'node:path'; +import { nextdir } from '../common/fs.js'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink'); +const dest = nextdir(); +const destFile = join(dest, 'a/b/README2.md'); +cpSync(src, dest, mustNotMutateObjectDeep({ dereference: true, recursive: true })); +cp(src, dest, { + dereference: true, + recursive: true +}, mustCall((err) => { + assert.strictEqual(err, null); + const stat = lstatSync(destFile); + assert(stat.isFile()); +})); diff --git a/test/parallel/test-fs-cp-async-dereference-symlink.mjs b/test/parallel/test-fs-cp-async-dereference-symlink.mjs new file mode 100644 index 00000000000..7edc8b8c66b --- /dev/null +++ b/test/parallel/test-fs-cp-async-dereference-symlink.mjs @@ -0,0 +1,27 @@ +// This tests that cp() copies file itself, rather than symlink, when dereference is true. + +import { mustCall, mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cp, lstatSync, mkdirSync, symlinkSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; +import tmpdir from '../common/tmpdir.js'; + +tmpdir.refresh(); + +const src = nextdir(); +mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); +writeFileSync(join(src, 'foo.js'), 'foo', 'utf8'); +symlinkSync(join(src, 'foo.js'), join(src, 'bar.js')); + +const dest = nextdir(); +mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); +const destFile = join(dest, 'foo.js'); + +cp(join(src, 'bar.js'), destFile, mustNotMutateObjectDeep({ dereference: true }), + mustCall((err) => { + assert.strictEqual(err, null); + const stat = lstatSync(destFile); + assert(stat.isFile()); + }) +); diff --git a/test/parallel/test-fs-cp-async-dest-symlink-points-to-src-error.mjs b/test/parallel/test-fs-cp-async-dest-symlink-points-to-src-error.mjs new file mode 100644 index 00000000000..da8d606963b --- /dev/null +++ b/test/parallel/test-fs-cp-async-dest-symlink-points-to-src-error.mjs @@ -0,0 +1,21 @@ +// This tests that cp() returns error if parent directory of symlink in dest points to src. + +import { mustCall, mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cp, mkdirSync, symlinkSync } from 'node:fs'; +import { join } from 'node:path'; +import tmpdir from '../common/tmpdir.js'; + +tmpdir.refresh(); + +const src = nextdir(); +mkdirSync(join(src, 'a'), mustNotMutateObjectDeep({ recursive: true })); +const dest = nextdir(); +// Create symlink in dest pointing to src. +const destLink = join(dest, 'b'); +mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); +symlinkSync(src, destLink); +cp(src, join(dest, 'b', 'c'), mustCall((err) => { + assert.strictEqual(err.code, 'ERR_FS_CP_EINVAL'); +})); diff --git a/test/parallel/test-fs-cp-async-dir-to-file.mjs b/test/parallel/test-fs-cp-async-dir-to-file.mjs new file mode 100644 index 00000000000..16d9ce6f0c2 --- /dev/null +++ b/test/parallel/test-fs-cp-async-dir-to-file.mjs @@ -0,0 +1,17 @@ +// This tests that cp() returns error if attempt is made to copy directory to file. + +import { mustCall, mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cp, mkdirSync } from 'node:fs'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = nextdir(); +mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); +const dest = fixtures.path('copy/kitchen-sink/README.md'); +cp(src, dest, mustCall((err) => { + assert.strictEqual(err.code, 'ERR_FS_CP_DIR_TO_NON_DIR'); +})); diff --git a/test/parallel/test-fs-cp-async-error-on-exist.mjs b/test/parallel/test-fs-cp-async-error-on-exist.mjs new file mode 100644 index 00000000000..c6df45db7f3 --- /dev/null +++ b/test/parallel/test-fs-cp-async-error-on-exist.mjs @@ -0,0 +1,22 @@ +// This tests that cp() returns error if errorOnExist is true, force is false, and file or folder copied over. + +import { mustCall, mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cp, cpSync } from 'node:fs'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink'); +const dest = nextdir(); +cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); +cp(src, dest, { + dereference: true, + errorOnExist: true, + force: false, + recursive: true, +}, mustCall((err) => { + assert.strictEqual(err.code, 'ERR_FS_CP_EEXIST'); +})); diff --git a/test/parallel/test-fs-cp-async-file-to-dir.mjs b/test/parallel/test-fs-cp-async-file-to-dir.mjs new file mode 100644 index 00000000000..94dc193f5c5 --- /dev/null +++ b/test/parallel/test-fs-cp-async-file-to-dir.mjs @@ -0,0 +1,17 @@ +// This tests that cp() returns error if attempt is made to copy file to directory. + +import { mustCall, mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cp, mkdirSync } from 'node:fs'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink/README.md'); +const dest = nextdir(); +mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); +cp(src, dest, mustCall((err) => { + assert.strictEqual(err.code, 'ERR_FS_CP_NON_DIR_TO_DIR'); +})); diff --git a/test/parallel/test-fs-cp-async-file-to-file.mjs b/test/parallel/test-fs-cp-async-file-to-file.mjs new file mode 100644 index 00000000000..faff91e6a2b --- /dev/null +++ b/test/parallel/test-fs-cp-async-file-to-file.mjs @@ -0,0 +1,19 @@ +// This tests that cp() allows file to be copied to a file path. + +import { mustCall, mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cp, lstatSync } from 'node:fs'; +import { join } from 'node:path'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const srcFile = fixtures.path('copy/kitchen-sink/README.md'); +const destFile = join(nextdir(), 'index.js'); +cp(srcFile, destFile, mustNotMutateObjectDeep({ dereference: true }), mustCall((err) => { + assert.strictEqual(err, null); + const stat = lstatSync(destFile); + assert(stat.isFile()); +})); diff --git a/test/parallel/test-fs-cp-async-file-url.mjs b/test/parallel/test-fs-cp-async-file-url.mjs new file mode 100644 index 00000000000..4c1ea1fdd56 --- /dev/null +++ b/test/parallel/test-fs-cp-async-file-url.mjs @@ -0,0 +1,18 @@ +// This tests that it accepts file URL as src and dest. + +import { mustCall, mustNotMutateObjectDeep } from '../common/index.mjs'; +import assert from 'node:assert'; +import { cp } from 'node:fs'; +import { pathToFileURL } from 'node:url'; +import tmpdir from '../common/tmpdir.js'; +import { assertDirEquivalent, nextdir } from '../common/fs.js'; + +tmpdir.refresh(); + +const src = './test/fixtures/copy/kitchen-sink'; +const dest = nextdir(); +cp(pathToFileURL(src), pathToFileURL(dest), mustNotMutateObjectDeep({ recursive: true }), + mustCall((err) => { + assert.strictEqual(err, null); + assertDirEquivalent(src, dest); + })); diff --git a/test/parallel/test-fs-cp-async-filter-child-folder.mjs b/test/parallel/test-fs-cp-async-filter-child-folder.mjs new file mode 100644 index 00000000000..b1ebeca514e --- /dev/null +++ b/test/parallel/test-fs-cp-async-filter-child-folder.mjs @@ -0,0 +1,26 @@ +// This tests that cp() should not throw exception if child folder is filtered out. + +import { mustCall, mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cp, cpSync, mkdirSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; +import tmpdir from '../common/tmpdir.js'; + +tmpdir.refresh(); + +const src = nextdir(); +mkdirSync(join(src, 'test-cp'), mustNotMutateObjectDeep({ recursive: true })); + +const dest = nextdir(); +mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); +writeFileSync(join(dest, 'test-cp'), 'test-content', mustNotMutateObjectDeep({ mode: 0o444 })); + +const opts = { + filter: (path) => !path.includes('test-cp'), + recursive: true, +}; +cp(src, dest, opts, mustCall((err) => { + assert.strictEqual(err, null); +})); +cpSync(src, dest, opts); diff --git a/test/parallel/test-fs-cp-async-filter-function.mjs b/test/parallel/test-fs-cp-async-filter-function.mjs new file mode 100644 index 00000000000..bb8145b035b --- /dev/null +++ b/test/parallel/test-fs-cp-async-filter-function.mjs @@ -0,0 +1,31 @@ +// This tests that cp() applies filter function. + +import { mustCall } from '../common/index.mjs'; +import { nextdir, collectEntries } from '../common/fs.js'; +import assert from 'node:assert'; +import { cp, statSync } from 'node:fs'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink'); +const dest = nextdir(); +cp(src, dest, { + filter: (path) => { + const pathStat = statSync(path); + return pathStat.isDirectory() || path.endsWith('.js'); + }, + dereference: true, + recursive: true, +}, mustCall((err) => { + assert.strictEqual(err, null); + const destEntries = []; + collectEntries(dest, destEntries); + for (const entry of destEntries) { + assert.strictEqual( + entry.isDirectory() || entry.name.endsWith('.js'), + true + ); + } +})); diff --git a/test/parallel/test-fs-cp-async-identical-src-dest.mjs b/test/parallel/test-fs-cp-async-identical-src-dest.mjs new file mode 100644 index 00000000000..20bed5f9268 --- /dev/null +++ b/test/parallel/test-fs-cp-async-identical-src-dest.mjs @@ -0,0 +1,14 @@ +// This tests that cp() returns error when src and dest are identical. + +import { mustCall } from '../common/index.mjs'; +import assert from 'node:assert'; +import { cp } from 'node:fs'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink'); +cp(src, src, mustCall((err) => { + assert.strictEqual(err.code, 'ERR_FS_CP_EINVAL'); +})); diff --git a/test/parallel/test-fs-cp-async-invalid-mode-range.mjs b/test/parallel/test-fs-cp-async-invalid-mode-range.mjs new file mode 100644 index 00000000000..12be6a6fadd --- /dev/null +++ b/test/parallel/test-fs-cp-async-invalid-mode-range.mjs @@ -0,0 +1,13 @@ +// This tests that cp() throws if mode is out of range. + +import '../common/index.mjs'; +import assert from 'node:assert'; +import { cp } from 'node:fs'; +import tmpdir from '../common/tmpdir.js'; + +tmpdir.refresh(); + +assert.throws( + () => cp('a', 'b', { mode: -1 }, () => {}), + { code: 'ERR_OUT_OF_RANGE' } +); diff --git a/test/parallel/test-fs-cp-async-invalid-options-type.mjs b/test/parallel/test-fs-cp-async-invalid-options-type.mjs new file mode 100644 index 00000000000..7c84d5f6801 --- /dev/null +++ b/test/parallel/test-fs-cp-async-invalid-options-type.mjs @@ -0,0 +1,13 @@ +// This tests that cp() throws if options is not object. + +import '../common/index.mjs'; +import assert from 'node:assert'; +import { cp } from 'node:fs'; +import tmpdir from '../common/tmpdir.js'; + +tmpdir.refresh(); + +assert.throws( + () => cp('a', 'b', 'hello', () => {}), + { code: 'ERR_INVALID_ARG_TYPE' } +); diff --git a/test/parallel/test-fs-cp-async-nested-files-folders.mjs b/test/parallel/test-fs-cp-async-nested-files-folders.mjs new file mode 100644 index 00000000000..9cc2115bc1f --- /dev/null +++ b/test/parallel/test-fs-cp-async-nested-files-folders.mjs @@ -0,0 +1,17 @@ +// This tests that cp() copies a nested folder structure with files and folders. + +import { mustCall, mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir, assertDirEquivalent } from '../common/fs.js'; +import assert from 'node:assert'; +import { cp } from 'node:fs'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink'); +const dest = nextdir(); +cp(src, dest, mustNotMutateObjectDeep({ recursive: true }), mustCall((err) => { + assert.strictEqual(err, null); + assertDirEquivalent(src, dest); +})); diff --git a/test/parallel/test-fs-cp-async-no-errors-force-false.mjs b/test/parallel/test-fs-cp-async-no-errors-force-false.mjs new file mode 100644 index 00000000000..609706a8306 --- /dev/null +++ b/test/parallel/test-fs-cp-async-no-errors-force-false.mjs @@ -0,0 +1,28 @@ +// This tests that it does not throw errors when directory is copied over and force is false. + +import { mustCall, mustNotMutateObjectDeep } from '../common/index.mjs'; +import assert from 'node:assert'; +import { cp, cpSync, lstatSync, mkdirSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; +import tmpdir from '../common/tmpdir.js'; +import { assertDirEquivalent, nextdir } from '../common/fs.js'; + +tmpdir.refresh(); + +const src = nextdir(); +mkdirSync(join(src, 'a', 'b'), mustNotMutateObjectDeep({ recursive: true })); +writeFileSync(join(src, 'README.md'), 'hello world', 'utf8'); +const dest = nextdir(); +cpSync(src, dest, mustNotMutateObjectDeep({ dereference: true, recursive: true })); +const initialStat = lstatSync(join(dest, 'README.md')); +cp(src, dest, { + dereference: true, + force: false, + recursive: true, +}, mustCall((err) => { + assert.strictEqual(err, null); + assertDirEquivalent(src, dest); + // File should not have been copied over, so access times will be identical: + const finalStat = lstatSync(join(dest, 'README.md')); + assert.strictEqual(finalStat.ctime.getTime(), initialStat.ctime.getTime()); +})); diff --git a/test/parallel/test-fs-cp-async-no-recursive.mjs b/test/parallel/test-fs-cp-async-no-recursive.mjs new file mode 100644 index 00000000000..fd58ee81d58 --- /dev/null +++ b/test/parallel/test-fs-cp-async-no-recursive.mjs @@ -0,0 +1,16 @@ +// This tests that cp() returns error if directory copied without recursive flag. + +import { mustCall } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cp } from 'node:fs'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink'); +const dest = nextdir(); +cp(src, dest, mustCall((err) => { + assert.strictEqual(err.code, 'ERR_FS_EISDIR'); +})); diff --git a/test/parallel/test-fs-cp-async-overwrites-force-true.mjs b/test/parallel/test-fs-cp-async-overwrites-force-true.mjs new file mode 100644 index 00000000000..1d5006256a7 --- /dev/null +++ b/test/parallel/test-fs-cp-async-overwrites-force-true.mjs @@ -0,0 +1,23 @@ +// This tests that it overwrites existing files if force is true. + +import { mustCall, mustNotMutateObjectDeep } from '../common/index.mjs'; +import assert from 'node:assert'; +import { cp, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; +import tmpdir from '../common/tmpdir.js'; +import { assertDirEquivalent, nextdir } from '../common/fs.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink'); +const dest = nextdir(); +mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); +writeFileSync(join(dest, 'README.md'), '# Goodbye', 'utf8'); + +cp(src, dest, mustNotMutateObjectDeep({ recursive: true }), mustCall((err) => { + assert.strictEqual(err, null); + assertDirEquivalent(src, dest); + const content = readFileSync(join(dest, 'README.md'), 'utf8'); + assert.strictEqual(content.trim(), '# Hello'); +})); diff --git a/test/parallel/test-fs-cp-async-preserve-timestamps-readonly-file.mjs b/test/parallel/test-fs-cp-async-preserve-timestamps-readonly-file.mjs new file mode 100644 index 00000000000..fff2e199fcd --- /dev/null +++ b/test/parallel/test-fs-cp-async-preserve-timestamps-readonly-file.mjs @@ -0,0 +1,26 @@ +// This tests that it makes file writeable when updating timestamp, if not writeable. + +import { mustCall, mustNotMutateObjectDeep } from '../common/index.mjs'; +import assert from 'node:assert'; +import { cp, lstatSync, mkdirSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; +import tmpdir from '../common/tmpdir.js'; +import { assertDirEquivalent, nextdir } from '../common/fs.js'; + +tmpdir.refresh(); + +const src = nextdir(); +mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); +const dest = nextdir(); +mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); +writeFileSync(join(src, 'foo.txt'), 'foo', mustNotMutateObjectDeep({ mode: 0o444 })); +cp(src, dest, { + preserveTimestamps: true, + recursive: true, +}, mustCall((err) => { + assert.strictEqual(err, null); + assertDirEquivalent(src, dest); + const srcStat = lstatSync(join(src, 'foo.txt')); + const destStat = lstatSync(join(dest, 'foo.txt')); + assert.strictEqual(srcStat.mtime.getTime(), destStat.mtime.getTime()); +})); diff --git a/test/parallel/test-fs-cp-async-preserve-timestamps.mjs b/test/parallel/test-fs-cp-async-preserve-timestamps.mjs new file mode 100644 index 00000000000..8e55f07ca60 --- /dev/null +++ b/test/parallel/test-fs-cp-async-preserve-timestamps.mjs @@ -0,0 +1,24 @@ +// This tests that cp() copies timestamps from src to dest if preserveTimestamps is true. + +import { mustCall } from '../common/index.mjs'; +import { nextdir, assertDirEquivalent } from '../common/fs.js'; +import assert from 'node:assert'; +import { cp, lstatSync } from 'node:fs'; +import { join } from 'node:path'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink'); +const dest = nextdir(); +cp(src, dest, { + preserveTimestamps: true, + recursive: true +}, mustCall((err) => { + assert.strictEqual(err, null); + assertDirEquivalent(src, dest); + const srcStat = lstatSync(join(src, 'index.js')); + const destStat = lstatSync(join(dest, 'index.js')); + assert.strictEqual(srcStat.mtime.getTime(), destStat.mtime.getTime()); +})); diff --git a/test/parallel/test-fs-cp-async-same-dir-twice.mjs b/test/parallel/test-fs-cp-async-same-dir-twice.mjs new file mode 100644 index 00000000000..0c92b3eb627 --- /dev/null +++ b/test/parallel/test-fs-cp-async-same-dir-twice.mjs @@ -0,0 +1,25 @@ +// This tests that it does not fail if the same directory is copied to dest +// twice, when dereference is true, and force is false (fails silently). + +import { mustCall, mustNotMutateObjectDeep } from '../common/index.mjs'; +import assert from 'node:assert'; +import { cp, cpSync, lstatSync } from 'node:fs'; +import { join } from 'node:path'; +import { nextdir } from '../common/fs.js'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink'); +const dest = nextdir(); +const destFile = join(dest, 'a/b/README2.md'); +cpSync(src, dest, mustNotMutateObjectDeep({ dereference: true, recursive: true })); +cp(src, dest, { + dereference: true, + recursive: true +}, mustCall((err) => { + assert.strictEqual(err, null); + const stat = lstatSync(destFile); + assert(stat.isFile()); +})); diff --git a/test/parallel/test-fs-cp-async-skip-validation-when-filtered.mjs b/test/parallel/test-fs-cp-async-skip-validation-when-filtered.mjs new file mode 100644 index 00000000000..4778bc23319 --- /dev/null +++ b/test/parallel/test-fs-cp-async-skip-validation-when-filtered.mjs @@ -0,0 +1,29 @@ +// This tests that cp() should not throw exception if dest is invalid but filtered out. + +import { mustCall, mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cp, cpSync, mkdirSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; +import tmpdir from '../common/tmpdir.js'; + +tmpdir.refresh(); + +// Create dest as a file. +// Expect: cp skips the copy logic entirely and won't throw any exception in path validation process. +const src = join(nextdir(), 'bar'); +mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); + +const destParent = nextdir(); +const dest = join(destParent, 'bar'); +mkdirSync(destParent, mustNotMutateObjectDeep({ recursive: true })); +writeFileSync(dest, 'test-content', mustNotMutateObjectDeep({ mode: 0o444 })); + +const opts = { + filter: (path) => !path.includes('bar'), + recursive: true, +}; +cp(src, dest, opts, mustCall((err) => { + assert.strictEqual(err, null); +})); +cpSync(src, dest, opts); diff --git a/test/parallel/test-fs-cp-async-socket.mjs b/test/parallel/test-fs-cp-async-socket.mjs new file mode 100644 index 00000000000..4ebc56cc3f6 --- /dev/null +++ b/test/parallel/test-fs-cp-async-socket.mjs @@ -0,0 +1,34 @@ +// This tests that cp() returns an error if attempt is made to copy socket. + +import * as common from '../common/index.mjs'; +import assert from 'node:assert'; +import { cp, mkdirSync } from 'node:fs'; +import { createServer } from 'node:net'; +import { join } from 'node:path'; +import { nextdir } from '../common/fs.js'; +import tmpdir from '../common/tmpdir.js'; + +const isWindows = process.platform === 'win32'; +if (isWindows) { + common.skip('No socket support on Windows'); +} + +// See https://github.com/nodejs/node/pull/48409 +if (common.isInsideDirWithUnusualChars) { + common.skip('Test is borken in directories with unusual characters'); +} + +tmpdir.refresh(); + +{ + const src = nextdir(); + mkdirSync(src); + const dest = nextdir(); + const sock = join(src, `${process.pid}.sock`); + const server = createServer(); + server.listen(sock); + cp(sock, dest, common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_FS_CP_SOCKET'); + server.close(); + })); +} diff --git a/test/parallel/test-fs-cp-async-subdirectory-of-self.mjs b/test/parallel/test-fs-cp-async-subdirectory-of-self.mjs new file mode 100644 index 00000000000..6884b2ddcd6 --- /dev/null +++ b/test/parallel/test-fs-cp-async-subdirectory-of-self.mjs @@ -0,0 +1,12 @@ +// This tests that cp() returns error if attempt is made to copy to subdirectory of self. + +import { mustCall } from '../common/index.mjs'; +import assert from 'node:assert'; +import { cp } from 'node:fs'; +import fixtures from '../common/fixtures.js'; + +const src = fixtures.path('copy/kitchen-sink'); +const dest = fixtures.path('copy/kitchen-sink/a'); +cp(src, dest, mustCall((err) => { + assert.strictEqual(err.code, 'ERR_FS_CP_EINVAL'); +})); diff --git a/test/parallel/test-fs-cp-async-symlink-dest-points-to-src.mjs b/test/parallel/test-fs-cp-async-symlink-dest-points-to-src.mjs new file mode 100644 index 00000000000..dab9c572f9d --- /dev/null +++ b/test/parallel/test-fs-cp-async-symlink-dest-points-to-src.mjs @@ -0,0 +1,21 @@ +// This tests that cp() returns error if symlink in dest points to location in src. + +import { mustCall, mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cp, mkdirSync, symlinkSync } from 'node:fs'; +import { join } from 'node:path'; +import tmpdir from '../common/tmpdir.js'; + +tmpdir.refresh(); + +const src = nextdir(); +mkdirSync(join(src, 'a', 'b'), mustNotMutateObjectDeep({ recursive: true })); +symlinkSync(join(src, 'a', 'b'), join(src, 'a', 'c')); + +const dest = nextdir(); +mkdirSync(join(dest, 'a'), mustNotMutateObjectDeep({ recursive: true })); +symlinkSync(src, join(dest, 'a', 'c')); +cp(src, dest, mustNotMutateObjectDeep({ recursive: true }), mustCall((err) => { + assert.strictEqual(err.code, 'ERR_FS_CP_SYMLINK_TO_SUBDIRECTORY'); +})); diff --git a/test/parallel/test-fs-cp-async-symlink-over-file.mjs b/test/parallel/test-fs-cp-async-symlink-over-file.mjs new file mode 100644 index 00000000000..8685a3d611d --- /dev/null +++ b/test/parallel/test-fs-cp-async-symlink-over-file.mjs @@ -0,0 +1,21 @@ +// This tests that cp() returns EEXIST error if attempt is made to copy symlink over file. + +import { mustCall, mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cp, mkdirSync, symlinkSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; +import tmpdir from '../common/tmpdir.js'; + +tmpdir.refresh(); + +const src = nextdir(); +mkdirSync(join(src, 'a', 'b'), mustNotMutateObjectDeep({ recursive: true })); +symlinkSync(join(src, 'a', 'b'), join(src, 'a', 'c')); + +const dest = nextdir(); +mkdirSync(join(dest, 'a'), mustNotMutateObjectDeep({ recursive: true })); +writeFileSync(join(dest, 'a', 'c'), 'hello', 'utf8'); +cp(src, dest, mustNotMutateObjectDeep({ recursive: true }), mustCall((err) => { + assert.strictEqual(err.code, 'EEXIST'); +})); diff --git a/test/parallel/test-fs-cp-async-symlink-points-to-dest.mjs b/test/parallel/test-fs-cp-async-symlink-points-to-dest.mjs new file mode 100644 index 00000000000..f8e60fe1fa2 --- /dev/null +++ b/test/parallel/test-fs-cp-async-symlink-points-to-dest.mjs @@ -0,0 +1,20 @@ +// This tests that cp() returns error if symlink in src points to location in dest. + +import { mustCall, mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cp, cpSync, mkdirSync, symlinkSync } from 'node:fs'; +import { join } from 'node:path'; +import tmpdir from '../common/tmpdir.js'; + +tmpdir.refresh(); + +const src = nextdir(); +mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); +const dest = nextdir(); +mkdirSync(dest); +symlinkSync(dest, join(src, 'link')); +cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); +cp(src, dest, mustNotMutateObjectDeep({ recursive: true }), mustCall((err) => { + assert.strictEqual(err.code, 'ERR_FS_CP_EINVAL'); +})); diff --git a/test/parallel/test-fs-cp-async-with-mode-flags.mjs b/test/parallel/test-fs-cp-async-with-mode-flags.mjs new file mode 100644 index 00000000000..99f6b5fe09d --- /dev/null +++ b/test/parallel/test-fs-cp-async-with-mode-flags.mjs @@ -0,0 +1,31 @@ +// This tests that it copies a nested folder structure with mode flags. + +import { mustCall, mustNotMutateObjectDeep } from '../common/index.mjs'; +import assert from 'node:assert'; +import { cp, constants } from 'node:fs'; +import tmpdir from '../common/tmpdir.js'; +import { assertDirEquivalent, nextdir } from '../common/fs.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink'); +const dest = nextdir(); +cp(src, dest, mustNotMutateObjectDeep({ + recursive: true, + mode: constants.COPYFILE_FICLONE_FORCE, +}), mustCall((err) => { + if (!err) { + // If the platform support `COPYFILE_FICLONE_FORCE` operation, + // it should reach to here. + assert.strictEqual(err, null); + assertDirEquivalent(src, dest); + return; + } + + // If the platform does not support `COPYFILE_FICLONE_FORCE` operation, + // it should enter this path. + assert.strictEqual(err.syscall, 'copyfile'); + assert(err.code === 'ENOTSUP' || err.code === 'ENOTTY' || + err.code === 'ENOSYS' || err.code === 'EXDEV'); +})); diff --git a/test/parallel/test-fs-cp-promises-async-error.mjs b/test/parallel/test-fs-cp-promises-async-error.mjs new file mode 100644 index 00000000000..b7817cfd807 --- /dev/null +++ b/test/parallel/test-fs-cp-promises-async-error.mjs @@ -0,0 +1,23 @@ +// This tests that fs.promises.cp() allows async error to be caught. + +import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import fs from 'node:fs'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink'); +const dest = nextdir(); +await fs.promises.cp(src, dest, mustNotMutateObjectDeep({ recursive: true })); +await assert.rejects( + fs.promises.cp(src, dest, { + dereference: true, + errorOnExist: true, + force: false, + recursive: true, + }), + { code: 'ERR_FS_CP_EEXIST' } +); diff --git a/test/parallel/test-fs-cp-promises-file-url.mjs b/test/parallel/test-fs-cp-promises-file-url.mjs new file mode 100644 index 00000000000..552e9c0b272 --- /dev/null +++ b/test/parallel/test-fs-cp-promises-file-url.mjs @@ -0,0 +1,21 @@ +// This tests that fs.promises.cp() accepts file URL as src and dest. + +import '../common/index.mjs'; +import assert from 'node:assert'; +import { promises as fs } from 'node:fs'; +import { pathToFileURL } from 'node:url'; +import { assertDirEquivalent, nextdir } from '../common/fs.js'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink'); +const dest = nextdir(); +const p = await fs.cp( + pathToFileURL(src), + pathToFileURL(dest), + { recursive: true } +); +assert.strictEqual(p, undefined); +assertDirEquivalent(src, dest); diff --git a/test/parallel/test-fs-cp-promises-invalid-mode.mjs b/test/parallel/test-fs-cp-promises-invalid-mode.mjs new file mode 100644 index 00000000000..2e200e56afe --- /dev/null +++ b/test/parallel/test-fs-cp-promises-invalid-mode.mjs @@ -0,0 +1,15 @@ +// This tests that fs.promises.cp() rejects if options.mode is invalid. + +import '../common/index.mjs'; +import assert from 'node:assert'; +import fs from 'node:fs'; +import tmpdir from '../common/tmpdir.js'; + +tmpdir.refresh(); + +await assert.rejects( + fs.promises.cp('a', 'b', { + mode: -1, + }), + { code: 'ERR_OUT_OF_RANGE' } +); diff --git a/test/parallel/test-fs-cp-promises-mode-flags.mjs b/test/parallel/test-fs-cp-promises-mode-flags.mjs new file mode 100644 index 00000000000..b5633ae5e7f --- /dev/null +++ b/test/parallel/test-fs-cp-promises-mode-flags.mjs @@ -0,0 +1,36 @@ +// This tests that fs.promises.cp() copies a nested folder structure with mode flags. +// This test is based on fs.promises.copyFile() with `COPYFILE_FICLONE_FORCE`. + +import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import assert from 'node:assert'; +import { promises as fs, constants } from 'node:fs'; +import { assertDirEquivalent, nextdir } from '../common/fs.js'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink'); +const dest = nextdir(); +let p = null; +let successFiClone = false; +try { + p = await fs.cp(src, dest, mustNotMutateObjectDeep({ + recursive: true, + mode: constants.COPYFILE_FICLONE_FORCE, + })); + successFiClone = true; +} catch (err) { + // If the platform does not support `COPYFILE_FICLONE_FORCE` operation, + // it should enter this path. + assert.strictEqual(err.syscall, 'copyfile'); + assert(err.code === 'ENOTSUP' || err.code === 'ENOTTY' || + err.code === 'ENOSYS' || err.code === 'EXDEV'); +} + +if (successFiClone) { + // If the platform support `COPYFILE_FICLONE_FORCE` operation, + // it should reach to here. + assert.strictEqual(p, undefined); + assertDirEquivalent(src, dest); +} diff --git a/test/parallel/test-fs-cp-promises-nested-folder-recursive.mjs b/test/parallel/test-fs-cp-promises-nested-folder-recursive.mjs new file mode 100644 index 00000000000..21cd1ba9d25 --- /dev/null +++ b/test/parallel/test-fs-cp-promises-nested-folder-recursive.mjs @@ -0,0 +1,16 @@ +// This tests that fs.promises.cp() copies a nested folder structure with files and folders. + +import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir, assertDirEquivalent } from '../common/fs.js'; +import assert from 'node:assert'; +import fs from 'node:fs'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink'); +const dest = nextdir(); +const p = await fs.promises.cp(src, dest, mustNotMutateObjectDeep({ recursive: true })); +assert.strictEqual(p, undefined); +assertDirEquivalent(src, dest); diff --git a/test/parallel/test-fs-cp-promises-options-validation.mjs b/test/parallel/test-fs-cp-promises-options-validation.mjs new file mode 100644 index 00000000000..a2cfad4552e --- /dev/null +++ b/test/parallel/test-fs-cp-promises-options-validation.mjs @@ -0,0 +1,13 @@ +// This tests that fs.promises.cp() rejects if options is not object. + +import '../common/index.mjs'; +import assert from 'node:assert'; +import fs from 'node:fs'; +import tmpdir from '../common/tmpdir.js'; + +tmpdir.refresh(); + +await assert.rejects( + fs.promises.cp('a', 'b', () => {}), + { code: 'ERR_INVALID_ARG_TYPE' } +); diff --git a/test/parallel/test-fs-cp-sync-apply-filter-function.mjs b/test/parallel/test-fs-cp-sync-apply-filter-function.mjs new file mode 100644 index 00000000000..02e7446c572 --- /dev/null +++ b/test/parallel/test-fs-cp-sync-apply-filter-function.mjs @@ -0,0 +1,28 @@ +// This tests that cpSync applies filter function. +import '../common/index.mjs'; +import { nextdir, collectEntries } from '../common/fs.js'; +import assert from 'node:assert'; +import { cpSync, statSync } from 'node:fs'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink'); +const dest = nextdir(); +cpSync(src, dest, { + filter: (path) => { + const pathStat = statSync(path); + return pathStat.isDirectory() || path.endsWith('.js'); + }, + dereference: true, + recursive: true, +}); +const destEntries = []; +collectEntries(dest, destEntries); +for (const entry of destEntries) { + assert.strictEqual( + entry.isDirectory() || entry.name.endsWith('.js'), + true + ); +} diff --git a/test/parallel/test-fs-cp-sync-async-filter-error.mjs b/test/parallel/test-fs-cp-sync-async-filter-error.mjs new file mode 100644 index 00000000000..0bb2e62e768 --- /dev/null +++ b/test/parallel/test-fs-cp-sync-async-filter-error.mjs @@ -0,0 +1,24 @@ +// This tests that cpSync throws error if filter function is asynchronous. +import '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cpSync, statSync } from 'node:fs'; +import { setTimeout } from 'node:timers/promises'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink'); +const dest = nextdir(); +assert.throws(() => { + cpSync(src, dest, { + filter: async (path) => { + await setTimeout(5, 'done'); + const pathStat = statSync(path); + return pathStat.isDirectory() || path.endsWith('.js'); + }, + dereference: true, + recursive: true, + }); +}, { code: 'ERR_INVALID_RETURN_VALUE' }); diff --git a/test/parallel/test-fs-cp-sync-copy-directory-to-file-error.mjs b/test/parallel/test-fs-cp-sync-copy-directory-to-file-error.mjs new file mode 100644 index 00000000000..60438cbe98b --- /dev/null +++ b/test/parallel/test-fs-cp-sync-copy-directory-to-file-error.mjs @@ -0,0 +1,24 @@ +// This tests that cpSync throws error if attempt is made to copy directory to file. +import { isInsideDirWithUnusualChars, mustNotMutateObjectDeep, skip } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cpSync, mkdirSync } from 'node:fs'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +// See https://github.com/nodejs/node/pull/48409 +if (isInsideDirWithUnusualChars) { + skip('Test is borken in directories with unusual characters'); +} + +tmpdir.refresh(); + +{ + const src = nextdir(); + mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); + const dest = fixtures.path('copy/kitchen-sink/README.md'); + assert.throws( + () => cpSync(src, dest), + { code: 'ERR_FS_CP_DIR_TO_NON_DIR' } + ); +} diff --git a/test/parallel/test-fs-cp-sync-copy-directory-without-recursive-error.mjs b/test/parallel/test-fs-cp-sync-copy-directory-without-recursive-error.mjs new file mode 100644 index 00000000000..11408997797 --- /dev/null +++ b/test/parallel/test-fs-cp-sync-copy-directory-without-recursive-error.mjs @@ -0,0 +1,16 @@ +// This tests that cpSync throws error if directory copied without recursive flag. +import '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cpSync } from 'node:fs'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink'); +const dest = nextdir(); +assert.throws( + () => cpSync(src, dest), + { code: 'ERR_FS_EISDIR' } +); diff --git a/test/parallel/test-fs-cp-sync-copy-file-to-directory-error.mjs b/test/parallel/test-fs-cp-sync-copy-file-to-directory-error.mjs new file mode 100644 index 00000000000..96e1fa3b4e2 --- /dev/null +++ b/test/parallel/test-fs-cp-sync-copy-file-to-directory-error.mjs @@ -0,0 +1,22 @@ +// This tests that cpSync throws error if attempt is made to copy file to directory. +import { mustNotMutateObjectDeep, isInsideDirWithUnusualChars, skip } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cpSync, mkdirSync } from 'node:fs'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +// See https://github.com/nodejs/node/pull/48409 +if (isInsideDirWithUnusualChars) { + skip('Test is borken in directories with unusual characters'); +} + +const src = fixtures.path('copy/kitchen-sink/README.md'); +const dest = nextdir(); +mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); +assert.throws( + () => cpSync(src, dest), + { code: 'ERR_FS_CP_NON_DIR_TO_DIR' } +); diff --git a/test/parallel/test-fs-cp-sync-copy-file-to-file-path.mjs b/test/parallel/test-fs-cp-sync-copy-file-to-file-path.mjs new file mode 100644 index 00000000000..fa7ad45d82a --- /dev/null +++ b/test/parallel/test-fs-cp-sync-copy-file-to-file-path.mjs @@ -0,0 +1,13 @@ +// This tests that cpSync allows file to be copied to a file path. +import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cpSync, lstatSync } from 'node:fs'; +import { join } from 'node:path'; +import fixtures from '../common/fixtures.js'; + +const srcFile = fixtures.path('copy/kitchen-sink/index.js'); +const destFile = join(nextdir(), 'index.js'); +cpSync(srcFile, destFile, mustNotMutateObjectDeep({ dereference: true })); +const stat = lstatSync(destFile); +assert(stat.isFile()); diff --git a/test/parallel/test-fs-cp-sync-copy-socket-error.mjs b/test/parallel/test-fs-cp-sync-copy-socket-error.mjs new file mode 100644 index 00000000000..81a06cc224a --- /dev/null +++ b/test/parallel/test-fs-cp-sync-copy-socket-error.mjs @@ -0,0 +1,34 @@ +// This tests that cpSync throws an error if attempt is made to copy socket. +import * as common from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cpSync, mkdirSync } from 'node:fs'; +import { createServer } from 'node:net'; +import { join } from 'node:path'; +import tmpdir from '../common/tmpdir.js'; + +const isWindows = process.platform === 'win32'; +if (isWindows) { + common.skip('No socket support on Windows'); +} + +// See https://github.com/nodejs/node/pull/48409 +if (common.isInsideDirWithUnusualChars) { + common.skip('Test is borken in directories with unusual characters'); +} + +tmpdir.refresh(); + +{ + const src = nextdir(); + mkdirSync(src); + const dest = nextdir(); + const sock = join(src, `${process.pid}.sock`); + const server = createServer(); + server.listen(sock); + assert.throws( + () => cpSync(sock, dest), + { code: 'ERR_FS_CP_SOCKET' } + ); + server.close(); +} diff --git a/test/parallel/test-fs-cp-sync-copy-symlink-not-pointing-to-folder.mjs b/test/parallel/test-fs-cp-sync-copy-symlink-not-pointing-to-folder.mjs new file mode 100644 index 00000000000..d23a2c8be63 --- /dev/null +++ b/test/parallel/test-fs-cp-sync-copy-symlink-not-pointing-to-folder.mjs @@ -0,0 +1,19 @@ +// This tests that cpSync copies link if it does not point to folder in src. +import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cpSync, mkdirSync, symlinkSync, readlinkSync } from 'node:fs'; +import { join } from 'node:path'; + +import tmpdir from '../common/tmpdir.js'; +tmpdir.refresh(); + +const src = nextdir(); +mkdirSync(join(src, 'a', 'b'), mustNotMutateObjectDeep({ recursive: true })); +symlinkSync(src, join(src, 'a', 'c')); +const dest = nextdir(); +mkdirSync(join(dest, 'a'), mustNotMutateObjectDeep({ recursive: true })); +symlinkSync(dest, join(dest, 'a', 'c')); +cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); +const link = readlinkSync(join(dest, 'a', 'c')); +assert.strictEqual(link, src); diff --git a/test/parallel/test-fs-cp-sync-copy-symlink-over-file-error.mjs b/test/parallel/test-fs-cp-sync-copy-symlink-over-file-error.mjs new file mode 100644 index 00000000000..cf8f055bc24 --- /dev/null +++ b/test/parallel/test-fs-cp-sync-copy-symlink-over-file-error.mjs @@ -0,0 +1,21 @@ +// This tests that cpSync throws EEXIST error if attempt is made to copy symlink over file. +import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cpSync, mkdirSync, symlinkSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; + +import tmpdir from '../common/tmpdir.js'; +tmpdir.refresh(); + +const src = nextdir(); +mkdirSync(join(src, 'a', 'b'), mustNotMutateObjectDeep({ recursive: true })); +symlinkSync(join(src, 'a', 'b'), join(src, 'a', 'c')); + +const dest = nextdir(); +mkdirSync(join(dest, 'a'), mustNotMutateObjectDeep({ recursive: true })); +writeFileSync(join(dest, 'a', 'c'), 'hello', 'utf8'); +assert.throws( + () => cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })), + { code: 'EEXIST' } +); diff --git a/test/parallel/test-fs-cp-sync-copy-symlinks-to-existing-symlinks.mjs b/test/parallel/test-fs-cp-sync-copy-symlinks-to-existing-symlinks.mjs new file mode 100644 index 00000000000..a84dc07873b --- /dev/null +++ b/test/parallel/test-fs-cp-sync-copy-symlinks-to-existing-symlinks.mjs @@ -0,0 +1,17 @@ +// This tests that cpSync allows copying symlinks in src to locations in dest with +// existing symlinks not pointing to a directory. +import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import { cpSync, mkdirSync, writeFileSync, symlinkSync } from 'node:fs'; +import { resolve, join } from 'node:path'; + +import tmpdir from '../common/tmpdir.js'; +tmpdir.refresh(); + +const src = nextdir(); +const dest = nextdir(); +mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); +writeFileSync(`${src}/test.txt`, 'test'); +symlinkSync(resolve(`${src}/test.txt`), join(src, 'link.txt')); +cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); +cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); diff --git a/test/parallel/test-fs-cp-sync-copy-to-subdirectory-error.mjs b/test/parallel/test-fs-cp-sync-copy-to-subdirectory-error.mjs new file mode 100644 index 00000000000..034c54efec2 --- /dev/null +++ b/test/parallel/test-fs-cp-sync-copy-to-subdirectory-error.mjs @@ -0,0 +1,14 @@ +// This tests that cpSync throws error if attempt is made to copy to subdirectory of self. +import '../common/index.mjs'; +import assert from 'node:assert'; +import { cpSync } from 'node:fs'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink'); +const dest = fixtures.path('copy/kitchen-sink/a'); +assert.throws( + () => cpSync(src, dest), + { code: 'ERR_FS_CP_EINVAL' } +); diff --git a/test/parallel/test-fs-cp-sync-dereference-directory.mjs b/test/parallel/test-fs-cp-sync-dereference-directory.mjs new file mode 100644 index 00000000000..0fdb827301d --- /dev/null +++ b/test/parallel/test-fs-cp-sync-dereference-directory.mjs @@ -0,0 +1,23 @@ +// This tests that cpSync overrides target directory with what symlink points to, when dereference is true. +import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir, assertDirEquivalent } from '../common/fs.js'; +import assert from 'node:assert'; +import { cpSync, mkdirSync, writeFileSync, symlinkSync, lstatSync } from 'node:fs'; +import { join } from 'node:path'; +import tmpdir from '../common/tmpdir.js'; + +tmpdir.refresh(); + +const src = nextdir(); +const symlink = nextdir(); +const dest = nextdir(); +mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); +writeFileSync(join(src, 'foo.js'), 'foo', 'utf8'); +symlinkSync(src, symlink); + +mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); + +cpSync(symlink, dest, mustNotMutateObjectDeep({ dereference: true, recursive: true })); +const destStat = lstatSync(dest); +assert(!destStat.isSymbolicLink()); +assertDirEquivalent(src, dest); diff --git a/test/parallel/test-fs-cp-sync-dereference-file.mjs b/test/parallel/test-fs-cp-sync-dereference-file.mjs new file mode 100644 index 00000000000..3615dde9aaa --- /dev/null +++ b/test/parallel/test-fs-cp-sync-dereference-file.mjs @@ -0,0 +1,23 @@ +// This tests that cpSync copies file itself, rather than symlink, when dereference is true. +import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'assert'; + +import { cpSync, mkdirSync, writeFileSync, symlinkSync, lstatSync } from 'node:fs'; +import { join } from 'node:path'; +import tmpdir from '../common/tmpdir.js'; + +tmpdir.refresh(); + +const src = nextdir(); +mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); +writeFileSync(join(src, 'foo.js'), 'foo', 'utf8'); +symlinkSync(join(src, 'foo.js'), join(src, 'bar.js')); + +const dest = nextdir(); +mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); +const destFile = join(dest, 'foo.js'); + +cpSync(join(src, 'bar.js'), destFile, mustNotMutateObjectDeep({ dereference: true, recursive: true })); +const stat = lstatSync(destFile); +assert(stat.isFile()); diff --git a/test/parallel/test-fs-cp-sync-dereference-twice.mjs b/test/parallel/test-fs-cp-sync-dereference-twice.mjs new file mode 100644 index 00000000000..921b65902f1 --- /dev/null +++ b/test/parallel/test-fs-cp-sync-dereference-twice.mjs @@ -0,0 +1,20 @@ +// This tests that cpSync does not fail if the same directory is copied to dest twice, +// when dereference is true, and force is false (fails silently). +import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'assert'; + +import { cpSync, lstatSync } from 'node:fs'; +import { join } from 'node:path'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink'); +const dest = nextdir(); +const destFile = join(dest, 'a/b/README2.md'); +cpSync(src, dest, mustNotMutateObjectDeep({ dereference: true, recursive: true })); +cpSync(src, dest, mustNotMutateObjectDeep({ dereference: true, recursive: true })); +const stat = lstatSync(destFile); +assert(stat.isFile()); diff --git a/test/parallel/test-fs-cp-sync-dest-name-prefix-match.mjs b/test/parallel/test-fs-cp-sync-dest-name-prefix-match.mjs new file mode 100644 index 00000000000..5322188f34d --- /dev/null +++ b/test/parallel/test-fs-cp-sync-dest-name-prefix-match.mjs @@ -0,0 +1,14 @@ +// This tests that cpSync must not throw error if attempt is made to copy to dest +// directory with same prefix as src directory. +import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import { cpSync, mkdirSync } from 'node:fs'; + +import tmpdir from '../common/tmpdir.js'; +tmpdir.refresh(); + +const src = nextdir('prefix', tmpdir); +const dest = nextdir('prefix-a', tmpdir); +mkdirSync(src); +mkdirSync(dest); +cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); diff --git a/test/parallel/test-fs-cp-sync-dest-parent-name-prefix-match.mjs b/test/parallel/test-fs-cp-sync-dest-parent-name-prefix-match.mjs new file mode 100644 index 00000000000..4b136398e65 --- /dev/null +++ b/test/parallel/test-fs-cp-sync-dest-parent-name-prefix-match.mjs @@ -0,0 +1,16 @@ +// This tests that cpSync must not throw error if attempt is made to copy to dest +// directory if the parent of dest has same prefix as src directory. +import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import { cpSync, mkdirSync } from 'node:fs'; + +import tmpdir from '../common/tmpdir.js'; +tmpdir.refresh(); + +const src = nextdir('aa', tmpdir); +const destParent = nextdir('aaa', tmpdir); +const dest = nextdir('aaa/aabb', tmpdir); +mkdirSync(src); +mkdirSync(destParent); +mkdirSync(dest); +cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); diff --git a/test/parallel/test-fs-cp-sync-directory-not-exist-error.mjs b/test/parallel/test-fs-cp-sync-directory-not-exist-error.mjs new file mode 100644 index 00000000000..2ea2b0aaf63 --- /dev/null +++ b/test/parallel/test-fs-cp-sync-directory-not-exist-error.mjs @@ -0,0 +1,15 @@ +// This tests that cpSync throws an error when attempting to copy a dir that does not exist. +import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cpSync } from 'node:fs'; + +import tmpdir from '../common/tmpdir.js'; +tmpdir.refresh(); + +const src = nextdir(); +const dest = nextdir(); +assert.throws( + () => cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })), + { code: 'ENOENT' } +); diff --git a/test/parallel/test-fs-cp-sync-error-on-exist.mjs b/test/parallel/test-fs-cp-sync-error-on-exist.mjs new file mode 100644 index 00000000000..700e52e3b3c --- /dev/null +++ b/test/parallel/test-fs-cp-sync-error-on-exist.mjs @@ -0,0 +1,22 @@ +// This tests that cpSync throws error if errorOnExist is true, force is false, and file or folder copied over. +import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cpSync } from 'node:fs'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink'); +const dest = nextdir(); +cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); +assert.throws( + () => cpSync(src, dest, { + dereference: true, + errorOnExist: true, + force: false, + recursive: true, + }), + { code: 'ERR_FS_CP_EEXIST' } +); diff --git a/test/parallel/test-fs-cp-sync-file-url.mjs b/test/parallel/test-fs-cp-sync-file-url.mjs new file mode 100644 index 00000000000..8da791b5c37 --- /dev/null +++ b/test/parallel/test-fs-cp-sync-file-url.mjs @@ -0,0 +1,14 @@ +// This tests that cpSync accepts file URL as src and dest. +import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir, assertDirEquivalent } from '../common/fs.js'; +import { cpSync } from 'node:fs'; +import { pathToFileURL } from 'node:url'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink'); +const dest = nextdir(); +cpSync(pathToFileURL(src), pathToFileURL(dest), mustNotMutateObjectDeep({ recursive: true })); +assertDirEquivalent(src, dest); diff --git a/test/parallel/test-fs-cp-sync-filename-too-long-error.mjs b/test/parallel/test-fs-cp-sync-filename-too-long-error.mjs new file mode 100644 index 00000000000..363a84b366f --- /dev/null +++ b/test/parallel/test-fs-cp-sync-filename-too-long-error.mjs @@ -0,0 +1,17 @@ +// This tests that cpSync throws an error when attempting to copy a file with a name that is too long. +import '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cpSync } from 'node:fs'; + +const isWindows = process.platform === 'win32'; + +import tmpdir from '../common/tmpdir.js'; +tmpdir.refresh(); + +const src = 'a'.repeat(5000); +const dest = nextdir(); +assert.throws( + () => cpSync(src, dest), + { code: isWindows ? 'ENOENT' : 'ENAMETOOLONG' } +); diff --git a/test/parallel/test-fs-cp-sync-incompatible-options-error.mjs b/test/parallel/test-fs-cp-sync-incompatible-options-error.mjs new file mode 100644 index 00000000000..629da5d9dfd --- /dev/null +++ b/test/parallel/test-fs-cp-sync-incompatible-options-error.mjs @@ -0,0 +1,14 @@ +// This tests that cpSync throws an error when both dereference and verbatimSymlinks are enabled. +import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import assert from 'node:assert'; +import { cpSync } from 'node:fs'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink'); +assert.throws( + () => cpSync(src, src, mustNotMutateObjectDeep({ dereference: true, verbatimSymlinks: true })), + { code: 'ERR_INCOMPATIBLE_OPTION_PAIR' } +); diff --git a/test/parallel/test-fs-cp-sync-mode-flags.mjs b/test/parallel/test-fs-cp-sync-mode-flags.mjs new file mode 100644 index 00000000000..5471a600852 --- /dev/null +++ b/test/parallel/test-fs-cp-sync-mode-flags.mjs @@ -0,0 +1,30 @@ +// This tests that cpSync copies a nested folder structure with mode flags. +// This test is based on fs.promises.copyFile() with `COPYFILE_FICLONE_FORCE`. +import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir, assertDirEquivalent } from '../common/fs.js'; +import assert from 'node:assert'; +import { cpSync, constants } from 'node:fs'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink'); +const dest = nextdir(); +try { + cpSync(src, dest, mustNotMutateObjectDeep({ + recursive: true, + mode: constants.COPYFILE_FICLONE_FORCE, + })); +} catch (err) { + // If the platform does not support `COPYFILE_FICLONE_FORCE` operation, + // it should enter this path. + assert.strictEqual(err.syscall, 'copyfile'); + assert(err.code === 'ENOTSUP' || err.code === 'ENOTTY' || + err.code === 'ENOSYS' || err.code === 'EXDEV'); + process.exit(0); +} + +// If the platform support `COPYFILE_FICLONE_FORCE` operation, +// it should reach to here. +assertDirEquivalent(src, dest); diff --git a/test/parallel/test-fs-cp-sync-mode-invalid.mjs b/test/parallel/test-fs-cp-sync-mode-invalid.mjs new file mode 100644 index 00000000000..2ebbd243123 --- /dev/null +++ b/test/parallel/test-fs-cp-sync-mode-invalid.mjs @@ -0,0 +1,12 @@ +// This tests that cpSync rejects if options.mode is invalid. +import '../common/index.mjs'; +import assert from 'node:assert'; +import { cpSync } from 'node:fs'; + +import tmpdir from '../common/tmpdir.js'; +tmpdir.refresh(); + +assert.throws( + () => cpSync('a', 'b', { mode: -1 }), + { code: 'ERR_OUT_OF_RANGE' } +); diff --git a/test/parallel/test-fs-cp-sync-nested-files-folders.mjs b/test/parallel/test-fs-cp-sync-nested-files-folders.mjs new file mode 100644 index 00000000000..378bb538645 --- /dev/null +++ b/test/parallel/test-fs-cp-sync-nested-files-folders.mjs @@ -0,0 +1,13 @@ +// This tests that cpSync copies a nested folder structure with files and folders. +import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir, assertDirEquivalent } from '../common/fs.js'; +import { cpSync } from 'node:fs'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink'); +const dest = nextdir(); +cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); +assertDirEquivalent(src, dest); diff --git a/test/parallel/test-fs-cp-sync-no-overwrite-force-false.mjs b/test/parallel/test-fs-cp-sync-no-overwrite-force-false.mjs new file mode 100644 index 00000000000..696d48d3edc --- /dev/null +++ b/test/parallel/test-fs-cp-sync-no-overwrite-force-false.mjs @@ -0,0 +1,21 @@ +// This tests that cpSync does not throw errors when directory is copied over and force is false. +import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir, assertDirEquivalent } from '../common/fs.js'; +import assert from 'node:assert'; +import { cpSync, mkdirSync, writeFileSync, lstatSync } from 'node:fs'; +import { join } from 'node:path'; + +import tmpdir from '../common/tmpdir.js'; +tmpdir.refresh(); + +const src = nextdir(); +mkdirSync(join(src, 'a', 'b'), mustNotMutateObjectDeep({ recursive: true })); +writeFileSync(join(src, 'README.md'), 'hello world', 'utf8'); +const dest = nextdir(); +cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); +const initialStat = lstatSync(join(dest, 'README.md')); +cpSync(src, dest, mustNotMutateObjectDeep({ force: false, recursive: true })); +// File should not have been copied over, so access times will be identical: +assertDirEquivalent(src, dest); +const finalStat = lstatSync(join(dest, 'README.md')); +assert.strictEqual(finalStat.ctime.getTime(), initialStat.ctime.getTime()); diff --git a/test/parallel/test-fs-cp-sync-options-invalid-type-error.mjs b/test/parallel/test-fs-cp-sync-options-invalid-type-error.mjs new file mode 100644 index 00000000000..d7d8bea38ff --- /dev/null +++ b/test/parallel/test-fs-cp-sync-options-invalid-type-error.mjs @@ -0,0 +1,12 @@ +// This tests that cpSync throws if options is not object. +import '../common/index.mjs'; +import assert from 'node:assert'; +import { cpSync } from 'node:fs'; + +import tmpdir from '../common/tmpdir.js'; +tmpdir.refresh(); + +assert.throws( + () => cpSync('a', 'b', () => {}), + { code: 'ERR_INVALID_ARG_TYPE' } +); diff --git a/test/parallel/test-fs-cp-sync-overwrite-force-true.mjs b/test/parallel/test-fs-cp-sync-overwrite-force-true.mjs new file mode 100644 index 00000000000..cd9f8ab3e3e --- /dev/null +++ b/test/parallel/test-fs-cp-sync-overwrite-force-true.mjs @@ -0,0 +1,19 @@ +// This tests that cpSync overwrites existing files if force is true. +import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir, assertDirEquivalent } from '../common/fs.js'; +import assert from 'node:assert'; +import { cpSync, mkdirSync, writeFileSync, readFileSync } from 'node:fs'; +import { join } from 'node:path'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink'); +const dest = nextdir(); +mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); +writeFileSync(join(dest, 'README.md'), '# Goodbye', 'utf8'); +cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); +assertDirEquivalent(src, dest); +const content = readFileSync(join(dest, 'README.md'), 'utf8'); +assert.strictEqual(content.trim(), '# Hello'); diff --git a/test/parallel/test-fs-cp-sync-parent-symlink-dest-points-to-src-error.mjs b/test/parallel/test-fs-cp-sync-parent-symlink-dest-points-to-src-error.mjs new file mode 100644 index 00000000000..52feaf81201 --- /dev/null +++ b/test/parallel/test-fs-cp-sync-parent-symlink-dest-points-to-src-error.mjs @@ -0,0 +1,26 @@ +// This tests that cpSync throws error if parent directory of symlink in dest points to src. +import { mustNotMutateObjectDeep, isInsideDirWithUnusualChars, skip } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cpSync, mkdirSync, symlinkSync } from 'node:fs'; +import { join } from 'node:path'; + +import tmpdir from '../common/tmpdir.js'; +tmpdir.refresh(); + +// See https://github.com/nodejs/node/pull/48409 +if (isInsideDirWithUnusualChars) { + skip('Test is borken in directories with unusual characters'); +} + +const src = nextdir(); +mkdirSync(join(src, 'a'), mustNotMutateObjectDeep({ recursive: true })); +const dest = nextdir(); +// Create symlink in dest pointing to src. +const destLink = join(dest, 'b'); +mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); +symlinkSync(src, destLink); +assert.throws( + () => cpSync(src, join(dest, 'b', 'c')), + { code: 'ERR_FS_CP_EINVAL' } +); diff --git a/test/parallel/test-fs-cp-sync-preserve-timestamps-readonly.mjs b/test/parallel/test-fs-cp-sync-preserve-timestamps-readonly.mjs new file mode 100644 index 00000000000..69a4de434f7 --- /dev/null +++ b/test/parallel/test-fs-cp-sync-preserve-timestamps-readonly.mjs @@ -0,0 +1,24 @@ +// This tests that cpSync makes file writeable when updating timestamp, if not writeable. +import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir, assertDirEquivalent } from '../common/fs.js'; +import assert from 'node:assert'; +import { cpSync, mkdirSync, writeFileSync, lstatSync } from 'node:fs'; +import { join } from 'node:path'; +import { setTimeout } from 'node:timers/promises'; + +import tmpdir from '../common/tmpdir.js'; +tmpdir.refresh(); + +const src = nextdir(); +mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); +const dest = nextdir(); +mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); +writeFileSync(join(src, 'foo.txt'), 'foo', mustNotMutateObjectDeep({ mode: 0o444 })); +// Small wait to make sure that destStat.mtime.getTime() would produce a time +// different from srcStat.mtime.getTime() if preserveTimestamps wasn't set to true +await setTimeout(5); +cpSync(src, dest, mustNotMutateObjectDeep({ preserveTimestamps: true, recursive: true })); +assertDirEquivalent(src, dest); +const srcStat = lstatSync(join(src, 'foo.txt')); +const destStat = lstatSync(join(dest, 'foo.txt')); +assert.strictEqual(srcStat.mtime.getTime(), destStat.mtime.getTime()); diff --git a/test/parallel/test-fs-cp-sync-preserve-timestamps.mjs b/test/parallel/test-fs-cp-sync-preserve-timestamps.mjs new file mode 100644 index 00000000000..3fb35c2e635 --- /dev/null +++ b/test/parallel/test-fs-cp-sync-preserve-timestamps.mjs @@ -0,0 +1,18 @@ +// This tests that cpSync copies timestamps from src to dest if preserveTimestamps is true. +import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir, assertDirEquivalent } from '../common/fs.js'; +import assert from 'node:assert'; +import { cpSync, lstatSync } from 'node:fs'; +import { join } from 'node:path'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink'); +const dest = nextdir(); +cpSync(src, dest, mustNotMutateObjectDeep({ preserveTimestamps: true, recursive: true })); +assertDirEquivalent(src, dest); +const srcStat = lstatSync(join(src, 'index.js')); +const destStat = lstatSync(join(dest, 'index.js')); +assert.strictEqual(srcStat.mtime.getTime(), destStat.mtime.getTime()); diff --git a/test/parallel/test-fs-cp-sync-resolve-relative-symlinks-default.mjs b/test/parallel/test-fs-cp-sync-resolve-relative-symlinks-default.mjs new file mode 100644 index 00000000000..070fabd4387 --- /dev/null +++ b/test/parallel/test-fs-cp-sync-resolve-relative-symlinks-default.mjs @@ -0,0 +1,21 @@ +// This tests that cpSync resolves relative symlinks to their absolute path by default. +import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cpSync, mkdirSync, writeFileSync, symlinkSync, readlinkSync } from 'node:fs'; +import { join } from 'node:path'; + +import tmpdir from '../common/tmpdir.js'; +tmpdir.refresh(); + +const src = nextdir(); +mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); +writeFileSync(join(src, 'foo.js'), 'foo', 'utf8'); +symlinkSync('foo.js', join(src, 'bar.js')); + +const dest = nextdir(); +mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); + +cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); +const link = readlinkSync(join(dest, 'bar.js')); +assert.strictEqual(link, join(src, 'foo.js')); diff --git a/test/parallel/test-fs-cp-sync-resolve-relative-symlinks-false.mjs b/test/parallel/test-fs-cp-sync-resolve-relative-symlinks-false.mjs new file mode 100644 index 00000000000..e5f59d655f3 --- /dev/null +++ b/test/parallel/test-fs-cp-sync-resolve-relative-symlinks-false.mjs @@ -0,0 +1,21 @@ +// This tests that cpSync resolves relative symlinks when verbatimSymlinks is false. +import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cpSync, mkdirSync, writeFileSync, symlinkSync, readlinkSync } from 'node:fs'; +import { join } from 'node:path'; + +import tmpdir from '../common/tmpdir.js'; +tmpdir.refresh(); + +const src = nextdir(); +mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); +writeFileSync(join(src, 'foo.js'), 'foo', 'utf8'); +symlinkSync('foo.js', join(src, 'bar.js')); + +const dest = nextdir(); +mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); + +cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true, verbatimSymlinks: false })); +const link = readlinkSync(join(dest, 'bar.js')); +assert.strictEqual(link, join(src, 'foo.js')); diff --git a/test/parallel/test-fs-cp-sync-src-dest-identical-error.mjs b/test/parallel/test-fs-cp-sync-src-dest-identical-error.mjs new file mode 100644 index 00000000000..81d7e1fcb63 --- /dev/null +++ b/test/parallel/test-fs-cp-sync-src-dest-identical-error.mjs @@ -0,0 +1,14 @@ +// This tests that cpSync throws error when src and dest are identical. +import '../common/index.mjs'; +import assert from 'node:assert'; +import { cpSync } from 'node:fs'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink'); +assert.throws( + () => cpSync(src, src), + { code: 'ERR_FS_CP_EINVAL' } +); diff --git a/test/parallel/test-fs-cp-sync-src-parent-of-dest-error.mjs b/test/parallel/test-fs-cp-sync-src-parent-of-dest-error.mjs new file mode 100644 index 00000000000..55e4c7014b7 --- /dev/null +++ b/test/parallel/test-fs-cp-sync-src-parent-of-dest-error.mjs @@ -0,0 +1,25 @@ +// This tests that cpSync throws error if attempt is made to copy src to dest when +// src is parent directory of the parent of dest. +import { mustNotMutateObjectDeep, isInsideDirWithUnusualChars, skip } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cpSync, mkdirSync } from 'node:fs'; + +import tmpdir from '../common/tmpdir.js'; +tmpdir.refresh(); + +// See https://github.com/nodejs/node/pull/48409 +if (isInsideDirWithUnusualChars) { + skip('Test is borken in directories with unusual characters'); +} + +const src = nextdir('a', tmpdir); +const destParent = nextdir('a/b', tmpdir); +const dest = nextdir('a/b/c', tmpdir); +mkdirSync(src); +mkdirSync(destParent); +mkdirSync(dest); +assert.throws( + () => cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })), + { code: 'ERR_FS_CP_EINVAL' }, +); diff --git a/test/parallel/test-fs-cp-sync-symlink-dest-points-to-src-error.mjs b/test/parallel/test-fs-cp-sync-symlink-dest-points-to-src-error.mjs new file mode 100644 index 00000000000..580e2ada0e2 --- /dev/null +++ b/test/parallel/test-fs-cp-sync-symlink-dest-points-to-src-error.mjs @@ -0,0 +1,21 @@ +// This tests that cpSync throws error if symlink in dest points to location in src. +import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cpSync, mkdirSync, symlinkSync } from 'node:fs'; +import { join } from 'node:path'; + +import tmpdir from '../common/tmpdir.js'; +tmpdir.refresh(); + +const src = nextdir(); +mkdirSync(join(src, 'a', 'b'), mustNotMutateObjectDeep({ recursive: true })); +symlinkSync(join(src, 'a', 'b'), join(src, 'a', 'c')); + +const dest = nextdir(); +mkdirSync(join(dest, 'a'), mustNotMutateObjectDeep({ recursive: true })); +symlinkSync(src, join(dest, 'a', 'c')); +assert.throws( + () => cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })), + { code: 'ERR_FS_CP_SYMLINK_TO_SUBDIRECTORY' } +); diff --git a/test/parallel/test-fs-cp-sync-symlink-points-to-dest-error.mjs b/test/parallel/test-fs-cp-sync-symlink-points-to-dest-error.mjs new file mode 100644 index 00000000000..141798f1d27 --- /dev/null +++ b/test/parallel/test-fs-cp-sync-symlink-points-to-dest-error.mjs @@ -0,0 +1,22 @@ +// This tests that cpSync throws error if symlink in src points to location in dest. +import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cpSync, mkdirSync, symlinkSync } from 'node:fs'; +import { join } from 'node:path'; + +import tmpdir from '../common/tmpdir.js'; +tmpdir.refresh(); + +const src = nextdir(); +mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); +const dest = nextdir(); +mkdirSync(dest); +symlinkSync(dest, join(src, 'link')); +cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); +assert.throws( + () => cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })), + { + code: 'ERR_FS_CP_EINVAL' + } +); diff --git a/test/parallel/test-fs-cp-sync-unicode-folder-names.mjs b/test/parallel/test-fs-cp-sync-unicode-folder-names.mjs new file mode 100644 index 00000000000..6393aeb2c15 --- /dev/null +++ b/test/parallel/test-fs-cp-sync-unicode-folder-names.mjs @@ -0,0 +1,13 @@ +// This tests that cpSync copies a nested folder containing UTF characters. +import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir, assertDirEquivalent } from '../common/fs.js'; +import { cpSync } from 'node:fs'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = fixtures.path('copy/utf/新建文件夹'); +const dest = nextdir(); +cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); +assertDirEquivalent(src, dest); diff --git a/test/parallel/test-fs-cp-sync-verbatim-symlinks-invalid.mjs b/test/parallel/test-fs-cp-sync-verbatim-symlinks-invalid.mjs new file mode 100644 index 00000000000..3db176487f7 --- /dev/null +++ b/test/parallel/test-fs-cp-sync-verbatim-symlinks-invalid.mjs @@ -0,0 +1,17 @@ +// This tests that cpSync throws error when verbatimSymlinks is not a boolean. +import '../common/index.mjs'; +import assert from 'node:assert'; +import { cpSync } from 'node:fs'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; + +tmpdir.refresh(); + +const src = fixtures.path('copy/kitchen-sink'); +[1, [], {}, null, 1n, undefined, null, Symbol(), '', () => {}] + .forEach((verbatimSymlinks) => { + assert.throws( + () => cpSync(src, src, { verbatimSymlinks }), + { code: 'ERR_INVALID_ARG_TYPE' } + ); + }); diff --git a/test/parallel/test-fs-cp-sync-verbatim-symlinks-true.mjs b/test/parallel/test-fs-cp-sync-verbatim-symlinks-true.mjs new file mode 100644 index 00000000000..e8d0010119f --- /dev/null +++ b/test/parallel/test-fs-cp-sync-verbatim-symlinks-true.mjs @@ -0,0 +1,21 @@ +// This tests that cpSync does not resolve relative symlinks when verbatimSymlinks is true. +import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cpSync, mkdirSync, writeFileSync, symlinkSync, readlinkSync } from 'node:fs'; +import { join } from 'node:path'; + +import tmpdir from '../common/tmpdir.js'; +tmpdir.refresh(); + +const src = nextdir(); +mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); +writeFileSync(join(src, 'foo.js'), 'foo', 'utf8'); +symlinkSync('foo.js', join(src, 'bar.js')); + +const dest = nextdir(); +mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); + +cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true, verbatimSymlinks: true })); +const link = readlinkSync(join(dest, 'bar.js')); +assert.strictEqual(link, 'foo.js'); diff --git a/test/parallel/test-fs-cp.mjs b/test/parallel/test-fs-cp.mjs deleted file mode 100644 index 589d407d756..00000000000 --- a/test/parallel/test-fs-cp.mjs +++ /dev/null @@ -1,1098 +0,0 @@ -import { mustCall, mustNotMutateObjectDeep, isInsideDirWithUnusualChars } from '../common/index.mjs'; - -import assert from 'assert'; -import fs from 'fs'; -const { - cp, - cpSync, - lstatSync, - mkdirSync, - readdirSync, - readFileSync, - readlinkSync, - symlinkSync, - statSync, - writeFileSync, -} = fs; -import net from 'net'; -import { join, resolve } from 'path'; -import { pathToFileURL } from 'url'; -import { setTimeout } from 'timers/promises'; - -const isWindows = process.platform === 'win32'; -import tmpdir from '../common/tmpdir.js'; -tmpdir.refresh(); - -let dirc = 0; -function nextdir(dirname) { - return tmpdir.resolve(dirname || `copy_%${++dirc}`); -} - -// Synchronous implementation of copy. - -// It copies a nested folder containing UTF characters. -{ - const src = './test/fixtures/copy/utf/新建文件夹'; - const dest = nextdir(); - cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); - assertDirEquivalent(src, dest); -} - -// It copies a nested folder structure with files and folders. -{ - const src = './test/fixtures/copy/kitchen-sink'; - const dest = nextdir(); - cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); - assertDirEquivalent(src, dest); -} - -// It copies a nested folder structure with mode flags. -// This test is based on fs.promises.copyFile() with `COPYFILE_FICLONE_FORCE`. -(() => { - const src = './test/fixtures/copy/kitchen-sink'; - const dest = nextdir(); - try { - cpSync(src, dest, mustNotMutateObjectDeep({ - recursive: true, - mode: fs.constants.COPYFILE_FICLONE_FORCE, - })); - } catch (err) { - // If the platform does not support `COPYFILE_FICLONE_FORCE` operation, - // it should enter this path. - assert.strictEqual(err.syscall, 'copyfile'); - assert(err.code === 'ENOTSUP' || err.code === 'ENOTTY' || - err.code === 'ENOSYS' || err.code === 'EXDEV'); - return; - } - - // If the platform support `COPYFILE_FICLONE_FORCE` operation, - // it should reach to here. - assertDirEquivalent(src, dest); -})(); - -// It does not throw errors when directory is copied over and force is false. -{ - const src = nextdir(); - mkdirSync(join(src, 'a', 'b'), mustNotMutateObjectDeep({ recursive: true })); - writeFileSync(join(src, 'README.md'), 'hello world', 'utf8'); - const dest = nextdir(); - cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); - const initialStat = lstatSync(join(dest, 'README.md')); - cpSync(src, dest, mustNotMutateObjectDeep({ force: false, recursive: true })); - // File should not have been copied over, so access times will be identical: - assertDirEquivalent(src, dest); - const finalStat = lstatSync(join(dest, 'README.md')); - assert.strictEqual(finalStat.ctime.getTime(), initialStat.ctime.getTime()); -} - -// It overwrites existing files if force is true. -{ - const src = './test/fixtures/copy/kitchen-sink'; - const dest = nextdir(); - mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); - writeFileSync(join(dest, 'README.md'), '# Goodbye', 'utf8'); - cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); - assertDirEquivalent(src, dest); - const content = readFileSync(join(dest, 'README.md'), 'utf8'); - assert.strictEqual(content.trim(), '# Hello'); -} - -// It does not fail if the same directory is copied to dest twice, -// when dereference is true, and force is false (fails silently). -{ - const src = './test/fixtures/copy/kitchen-sink'; - const dest = nextdir(); - const destFile = join(dest, 'a/b/README2.md'); - cpSync(src, dest, mustNotMutateObjectDeep({ dereference: true, recursive: true })); - cpSync(src, dest, mustNotMutateObjectDeep({ dereference: true, recursive: true })); - const stat = lstatSync(destFile); - assert(stat.isFile()); -} - - -// It copies file itself, rather than symlink, when dereference is true. -{ - const src = nextdir(); - mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); - writeFileSync(join(src, 'foo.js'), 'foo', 'utf8'); - symlinkSync(join(src, 'foo.js'), join(src, 'bar.js')); - - const dest = nextdir(); - mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); - const destFile = join(dest, 'foo.js'); - - cpSync(join(src, 'bar.js'), destFile, mustNotMutateObjectDeep({ dereference: true, recursive: true })); - const stat = lstatSync(destFile); - assert(stat.isFile()); -} - - -// It overrides target directory with what symlink points to, when dereference is true. -{ - const src = nextdir(); - const symlink = nextdir(); - const dest = nextdir(); - mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); - writeFileSync(join(src, 'foo.js'), 'foo', 'utf8'); - symlinkSync(src, symlink); - - mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); - - cpSync(symlink, dest, mustNotMutateObjectDeep({ dereference: true, recursive: true })); - const destStat = lstatSync(dest); - assert(!destStat.isSymbolicLink()); - assertDirEquivalent(src, dest); -} - -// It throws error when verbatimSymlinks is not a boolean. -{ - const src = './test/fixtures/copy/kitchen-sink'; - [1, [], {}, null, 1n, undefined, null, Symbol(), '', () => {}] - .forEach((verbatimSymlinks) => { - assert.throws( - () => cpSync(src, src, { verbatimSymlinks }), - { code: 'ERR_INVALID_ARG_TYPE' } - ); - }); -} - -// It rejects if options.mode is invalid. -{ - assert.throws( - () => cpSync('a', 'b', { mode: -1 }), - { code: 'ERR_OUT_OF_RANGE' } - ); -} - - -// It throws an error when both dereference and verbatimSymlinks are enabled. -{ - const src = './test/fixtures/copy/kitchen-sink'; - assert.throws( - () => cpSync(src, src, mustNotMutateObjectDeep({ dereference: true, verbatimSymlinks: true })), - { code: 'ERR_INCOMPATIBLE_OPTION_PAIR' } - ); -} - - -// It resolves relative symlinks to their absolute path by default. -{ - const src = nextdir(); - mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); - writeFileSync(join(src, 'foo.js'), 'foo', 'utf8'); - symlinkSync('foo.js', join(src, 'bar.js')); - - const dest = nextdir(); - mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); - - cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); - const link = readlinkSync(join(dest, 'bar.js')); - assert.strictEqual(link, join(src, 'foo.js')); -} - - -// It resolves relative symlinks when verbatimSymlinks is false. -{ - const src = nextdir(); - mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); - writeFileSync(join(src, 'foo.js'), 'foo', 'utf8'); - symlinkSync('foo.js', join(src, 'bar.js')); - - const dest = nextdir(); - mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); - - cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true, verbatimSymlinks: false })); - const link = readlinkSync(join(dest, 'bar.js')); - assert.strictEqual(link, join(src, 'foo.js')); -} - - -// It does not resolve relative symlinks when verbatimSymlinks is true. -{ - const src = nextdir(); - mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); - writeFileSync(join(src, 'foo.js'), 'foo', 'utf8'); - symlinkSync('foo.js', join(src, 'bar.js')); - - const dest = nextdir(); - mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); - - cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true, verbatimSymlinks: true })); - const link = readlinkSync(join(dest, 'bar.js')); - assert.strictEqual(link, 'foo.js'); -} - - -// It throws error when src and dest are identical. -{ - const src = './test/fixtures/copy/kitchen-sink'; - assert.throws( - () => cpSync(src, src), - { code: 'ERR_FS_CP_EINVAL' } - ); -} - -// It throws error if symlink in src points to location in dest. -{ - const src = nextdir(); - mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); - const dest = nextdir(); - mkdirSync(dest); - symlinkSync(dest, join(src, 'link')); - cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); - assert.throws( - () => cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })), - { - code: 'ERR_FS_CP_EINVAL' - } - ); -} - -// It allows cpSync copying symlinks in src to locations in dest with existing synlinks not pointing to a directory. -{ - const src = nextdir(); - const dest = nextdir(); - mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); - writeFileSync(`${src}/test.txt`, 'test'); - symlinkSync(resolve(`${src}/test.txt`), join(src, 'link.txt')); - cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); - cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); -} - -// It allows cp copying symlinks in src to locations in dest with existing synlinks not pointing to a directory. -{ - const src = nextdir(); - const dest = nextdir(); - mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); - writeFileSync(`${src}/test.txt`, 'test'); - symlinkSync(resolve(`${src}/test.txt`), join(src, 'link.txt')); - cp(src, dest, { recursive: true }, - mustCall((err) => { - assert.strictEqual(err, null); - - cp(src, dest, { recursive: true }, mustCall((err) => { - assert.strictEqual(err, null); - })); - })); -} - -// It throws error if symlink in dest points to location in src. -{ - const src = nextdir(); - mkdirSync(join(src, 'a', 'b'), mustNotMutateObjectDeep({ recursive: true })); - symlinkSync(join(src, 'a', 'b'), join(src, 'a', 'c')); - - const dest = nextdir(); - mkdirSync(join(dest, 'a'), mustNotMutateObjectDeep({ recursive: true })); - symlinkSync(src, join(dest, 'a', 'c')); - assert.throws( - () => cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })), - { code: 'ERR_FS_CP_SYMLINK_TO_SUBDIRECTORY' } - ); -} - -// It throws error if parent directory of symlink in dest points to src. -if (!isInsideDirWithUnusualChars) { - const src = nextdir(); - mkdirSync(join(src, 'a'), mustNotMutateObjectDeep({ recursive: true })); - const dest = nextdir(); - // Create symlink in dest pointing to src. - const destLink = join(dest, 'b'); - mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); - symlinkSync(src, destLink); - assert.throws( - () => cpSync(src, join(dest, 'b', 'c')), - { code: 'ERR_FS_CP_EINVAL' } - ); -} - -// It throws error if attempt is made to copy directory to file. -if (!isInsideDirWithUnusualChars) { - const src = nextdir(); - mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); - const dest = './test/fixtures/copy/kitchen-sink/README.md'; - assert.throws( - () => cpSync(src, dest), - { code: 'ERR_FS_CP_DIR_TO_NON_DIR' } - ); -} - -// It allows file to be copied to a file path. -{ - const srcFile = './test/fixtures/copy/kitchen-sink/index.js'; - const destFile = join(nextdir(), 'index.js'); - cpSync(srcFile, destFile, mustNotMutateObjectDeep({ dereference: true })); - const stat = lstatSync(destFile); - assert(stat.isFile()); -} - -// It throws error if directory copied without recursive flag. -{ - const src = './test/fixtures/copy/kitchen-sink'; - const dest = nextdir(); - assert.throws( - () => cpSync(src, dest), - { code: 'ERR_FS_EISDIR' } - ); -} - - -// It throws error if attempt is made to copy file to directory. -if (!isInsideDirWithUnusualChars) { - const src = './test/fixtures/copy/kitchen-sink/README.md'; - const dest = nextdir(); - mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); - assert.throws( - () => cpSync(src, dest), - { code: 'ERR_FS_CP_NON_DIR_TO_DIR' } - ); -} - -// It must not throw error if attempt is made to copy to dest -// directory with same prefix as src directory -// regression test for https://github.com/nodejs/node/issues/54285 -{ - const src = nextdir('prefix'); - const dest = nextdir('prefix-a'); - mkdirSync(src); - mkdirSync(dest); - cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); -} - -// It must not throw error if attempt is made to copy to dest -// directory if the parent of dest has same prefix as src directory -// regression test for https://github.com/nodejs/node/issues/54285 -{ - const src = nextdir('aa'); - const destParent = nextdir('aaa'); - const dest = nextdir('aaa/aabb'); - mkdirSync(src); - mkdirSync(destParent); - mkdirSync(dest); - cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); -} - -// It throws error if attempt is made to copy src to dest -// when src is parent directory of the parent of dest -if (!isInsideDirWithUnusualChars) { - const src = nextdir('a'); - const destParent = nextdir('a/b'); - const dest = nextdir('a/b/c'); - mkdirSync(src); - mkdirSync(destParent); - mkdirSync(dest); - assert.throws( - () => cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })), - { code: 'ERR_FS_CP_EINVAL' }, - ); -} - -// It throws error if attempt is made to copy to subdirectory of self. -{ - const src = './test/fixtures/copy/kitchen-sink'; - const dest = './test/fixtures/copy/kitchen-sink/a'; - assert.throws( - () => cpSync(src, dest), - { code: 'ERR_FS_CP_EINVAL' } - ); -} - -// It throws an error if attempt is made to copy socket. -if (!isWindows && !isInsideDirWithUnusualChars) { - const src = nextdir(); - mkdirSync(src); - const dest = nextdir(); - const sock = join(src, `${process.pid}.sock`); - const server = net.createServer(); - server.listen(sock); - assert.throws( - () => cpSync(sock, dest), - { code: 'ERR_FS_CP_SOCKET' } - ); - server.close(); -} - -// It copies timestamps from src to dest if preserveTimestamps is true. -{ - const src = './test/fixtures/copy/kitchen-sink'; - const dest = nextdir(); - cpSync(src, dest, mustNotMutateObjectDeep({ preserveTimestamps: true, recursive: true })); - assertDirEquivalent(src, dest); - const srcStat = lstatSync(join(src, 'index.js')); - const destStat = lstatSync(join(dest, 'index.js')); - assert.strictEqual(srcStat.mtime.getTime(), destStat.mtime.getTime()); -} - -// It applies filter function. -{ - const src = './test/fixtures/copy/kitchen-sink'; - const dest = nextdir(); - cpSync(src, dest, { - filter: (path) => { - const pathStat = statSync(path); - return pathStat.isDirectory() || path.endsWith('.js'); - }, - dereference: true, - recursive: true, - }); - const destEntries = []; - collectEntries(dest, destEntries); - for (const entry of destEntries) { - assert.strictEqual( - entry.isDirectory() || entry.name.endsWith('.js'), - true - ); - } -} - -// It throws error if filter function is asynchronous. -{ - const src = './test/fixtures/copy/kitchen-sink'; - const dest = nextdir(); - assert.throws(() => { - cpSync(src, dest, { - filter: async (path) => { - await setTimeout(5, 'done'); - const pathStat = statSync(path); - return pathStat.isDirectory() || path.endsWith('.js'); - }, - dereference: true, - recursive: true, - }); - }, { code: 'ERR_INVALID_RETURN_VALUE' }); -} - -// It throws error if errorOnExist is true, force is false, and file or folder -// copied over. -{ - const src = './test/fixtures/copy/kitchen-sink'; - const dest = nextdir(); - cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); - assert.throws( - () => cpSync(src, dest, { - dereference: true, - errorOnExist: true, - force: false, - recursive: true, - }), - { code: 'ERR_FS_CP_EEXIST' } - ); -} - -// It throws EEXIST error if attempt is made to copy symlink over file. -{ - const src = nextdir(); - mkdirSync(join(src, 'a', 'b'), mustNotMutateObjectDeep({ recursive: true })); - symlinkSync(join(src, 'a', 'b'), join(src, 'a', 'c')); - - const dest = nextdir(); - mkdirSync(join(dest, 'a'), mustNotMutateObjectDeep({ recursive: true })); - writeFileSync(join(dest, 'a', 'c'), 'hello', 'utf8'); - assert.throws( - () => cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })), - { code: 'EEXIST' } - ); -} - -// It throws an error when attempting to copy a file with a name that is too long. -{ - const src = 'a'.repeat(5000); - const dest = nextdir(); - assert.throws( - () => cpSync(src, dest), - { code: isWindows ? 'ENOENT' : 'ENAMETOOLONG' } - ); -} - -// It throws an error when attempting to copy a dir that does not exist. -{ - const src = nextdir(); - const dest = nextdir(); - assert.throws( - () => cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })), - { code: 'ENOENT' } - ); -} - -// It makes file writeable when updating timestamp, if not writeable. -{ - const src = nextdir(); - mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); - const dest = nextdir(); - mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); - writeFileSync(join(src, 'foo.txt'), 'foo', mustNotMutateObjectDeep({ mode: 0o444 })); - // Small wait to make sure that destStat.mtime.getTime() would produce a time - // different from srcStat.mtime.getTime() if preserveTimestamps wasn't set to true - await setTimeout(5); - cpSync(src, dest, mustNotMutateObjectDeep({ preserveTimestamps: true, recursive: true })); - assertDirEquivalent(src, dest); - const srcStat = lstatSync(join(src, 'foo.txt')); - const destStat = lstatSync(join(dest, 'foo.txt')); - assert.strictEqual(srcStat.mtime.getTime(), destStat.mtime.getTime()); -} - -// It copies link if it does not point to folder in src. -{ - const src = nextdir(); - mkdirSync(join(src, 'a', 'b'), mustNotMutateObjectDeep({ recursive: true })); - symlinkSync(src, join(src, 'a', 'c')); - const dest = nextdir(); - mkdirSync(join(dest, 'a'), mustNotMutateObjectDeep({ recursive: true })); - symlinkSync(dest, join(dest, 'a', 'c')); - cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); - const link = readlinkSync(join(dest, 'a', 'c')); - assert.strictEqual(link, src); -} - -// It accepts file URL as src and dest. -{ - const src = './test/fixtures/copy/kitchen-sink'; - const dest = nextdir(); - cpSync(pathToFileURL(src), pathToFileURL(dest), mustNotMutateObjectDeep({ recursive: true })); - assertDirEquivalent(src, dest); -} - -// It throws if options is not object. -{ - assert.throws( - () => cpSync('a', 'b', () => {}), - { code: 'ERR_INVALID_ARG_TYPE' } - ); -} - -// Callback implementation of copy. - -// It copies a nested folder structure with files and folders. -{ - const src = './test/fixtures/copy/kitchen-sink'; - const dest = nextdir(); - cp(src, dest, mustNotMutateObjectDeep({ recursive: true }), mustCall((err) => { - assert.strictEqual(err, null); - assertDirEquivalent(src, dest); - })); -} - -// It copies a nested folder structure with mode flags. -// This test is based on fs.promises.copyFile() with `COPYFILE_FICLONE_FORCE`. -{ - const src = './test/fixtures/copy/kitchen-sink'; - const dest = nextdir(); - cp(src, dest, mustNotMutateObjectDeep({ - recursive: true, - mode: fs.constants.COPYFILE_FICLONE_FORCE, - }), mustCall((err) => { - if (!err) { - // If the platform support `COPYFILE_FICLONE_FORCE` operation, - // it should reach to here. - assert.strictEqual(err, null); - assertDirEquivalent(src, dest); - return; - } - - // If the platform does not support `COPYFILE_FICLONE_FORCE` operation, - // it should enter this path. - assert.strictEqual(err.syscall, 'copyfile'); - assert(err.code === 'ENOTSUP' || err.code === 'ENOTTY' || - err.code === 'ENOSYS' || err.code === 'EXDEV'); - })); -} - -// It does not throw errors when directory is copied over and force is false. -{ - const src = nextdir(); - mkdirSync(join(src, 'a', 'b'), mustNotMutateObjectDeep({ recursive: true })); - writeFileSync(join(src, 'README.md'), 'hello world', 'utf8'); - const dest = nextdir(); - cpSync(src, dest, mustNotMutateObjectDeep({ dereference: true, recursive: true })); - const initialStat = lstatSync(join(dest, 'README.md')); - cp(src, dest, { - dereference: true, - force: false, - recursive: true, - }, mustCall((err) => { - assert.strictEqual(err, null); - assertDirEquivalent(src, dest); - // File should not have been copied over, so access times will be identical: - const finalStat = lstatSync(join(dest, 'README.md')); - assert.strictEqual(finalStat.ctime.getTime(), initialStat.ctime.getTime()); - })); -} - -// It overwrites existing files if force is true. -{ - const src = './test/fixtures/copy/kitchen-sink'; - const dest = nextdir(); - mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); - writeFileSync(join(dest, 'README.md'), '# Goodbye', 'utf8'); - - cp(src, dest, mustNotMutateObjectDeep({ recursive: true }), mustCall((err) => { - assert.strictEqual(err, null); - assertDirEquivalent(src, dest); - const content = readFileSync(join(dest, 'README.md'), 'utf8'); - assert.strictEqual(content.trim(), '# Hello'); - })); -} - -// It does not fail if the same directory is copied to dest twice, -// when dereference is true, and force is false (fails silently). -{ - const src = './test/fixtures/copy/kitchen-sink'; - const dest = nextdir(); - const destFile = join(dest, 'a/b/README2.md'); - cpSync(src, dest, mustNotMutateObjectDeep({ dereference: true, recursive: true })); - cp(src, dest, { - dereference: true, - recursive: true - }, mustCall((err) => { - assert.strictEqual(err, null); - const stat = lstatSync(destFile); - assert(stat.isFile()); - })); -} - -// It copies file itself, rather than symlink, when dereference is true. -{ - const src = nextdir(); - mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); - writeFileSync(join(src, 'foo.js'), 'foo', 'utf8'); - symlinkSync(join(src, 'foo.js'), join(src, 'bar.js')); - - const dest = nextdir(); - mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); - const destFile = join(dest, 'foo.js'); - - cp(join(src, 'bar.js'), destFile, mustNotMutateObjectDeep({ dereference: true }), - mustCall((err) => { - assert.strictEqual(err, null); - const stat = lstatSync(destFile); - assert(stat.isFile()); - }) - ); -} - -// It returns error when src and dest are identical. -{ - const src = './test/fixtures/copy/kitchen-sink'; - cp(src, src, mustCall((err) => { - assert.strictEqual(err.code, 'ERR_FS_CP_EINVAL'); - })); -} - -// It returns error if symlink in src points to location in dest. -{ - const src = nextdir(); - mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); - const dest = nextdir(); - mkdirSync(dest); - symlinkSync(dest, join(src, 'link')); - cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); - cp(src, dest, mustNotMutateObjectDeep({ recursive: true }), mustCall((err) => { - assert.strictEqual(err.code, 'ERR_FS_CP_EINVAL'); - })); -} - -// It returns error if symlink in dest points to location in src. -{ - const src = nextdir(); - mkdirSync(join(src, 'a', 'b'), mustNotMutateObjectDeep({ recursive: true })); - symlinkSync(join(src, 'a', 'b'), join(src, 'a', 'c')); - - const dest = nextdir(); - mkdirSync(join(dest, 'a'), mustNotMutateObjectDeep({ recursive: true })); - symlinkSync(src, join(dest, 'a', 'c')); - cp(src, dest, mustNotMutateObjectDeep({ recursive: true }), mustCall((err) => { - assert.strictEqual(err.code, 'ERR_FS_CP_SYMLINK_TO_SUBDIRECTORY'); - })); -} - -// It returns error if parent directory of symlink in dest points to src. -{ - const src = nextdir(); - mkdirSync(join(src, 'a'), mustNotMutateObjectDeep({ recursive: true })); - const dest = nextdir(); - // Create symlink in dest pointing to src. - const destLink = join(dest, 'b'); - mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); - symlinkSync(src, destLink); - cp(src, join(dest, 'b', 'c'), mustCall((err) => { - assert.strictEqual(err.code, 'ERR_FS_CP_EINVAL'); - })); -} - -// It returns error if attempt is made to copy directory to file. -{ - const src = nextdir(); - mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); - const dest = './test/fixtures/copy/kitchen-sink/README.md'; - cp(src, dest, mustCall((err) => { - assert.strictEqual(err.code, 'ERR_FS_CP_DIR_TO_NON_DIR'); - })); -} - -// It allows file to be copied to a file path. -{ - const srcFile = './test/fixtures/copy/kitchen-sink/README.md'; - const destFile = join(nextdir(), 'index.js'); - cp(srcFile, destFile, mustNotMutateObjectDeep({ dereference: true }), mustCall((err) => { - assert.strictEqual(err, null); - const stat = lstatSync(destFile); - assert(stat.isFile()); - })); -} - -// It returns error if directory copied without recursive flag. -{ - const src = './test/fixtures/copy/kitchen-sink'; - const dest = nextdir(); - cp(src, dest, mustCall((err) => { - assert.strictEqual(err.code, 'ERR_FS_EISDIR'); - })); -} - -// It returns error if attempt is made to copy file to directory. -{ - const src = './test/fixtures/copy/kitchen-sink/README.md'; - const dest = nextdir(); - mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); - cp(src, dest, mustCall((err) => { - assert.strictEqual(err.code, 'ERR_FS_CP_NON_DIR_TO_DIR'); - })); -} - -// It returns error if attempt is made to copy to subdirectory of self. -{ - const src = './test/fixtures/copy/kitchen-sink'; - const dest = './test/fixtures/copy/kitchen-sink/a'; - cp(src, dest, mustCall((err) => { - assert.strictEqual(err.code, 'ERR_FS_CP_EINVAL'); - })); -} - -// It returns an error if attempt is made to copy socket. -if (!isWindows && !isInsideDirWithUnusualChars) { - const src = nextdir(); - mkdirSync(src); - const dest = nextdir(); - const sock = join(src, `${process.pid}.sock`); - const server = net.createServer(); - server.listen(sock); - cp(sock, dest, mustCall((err) => { - assert.strictEqual(err.code, 'ERR_FS_CP_SOCKET'); - server.close(); - })); -} - -// It copies timestamps from src to dest if preserveTimestamps is true. -{ - const src = './test/fixtures/copy/kitchen-sink'; - const dest = nextdir(); - cp(src, dest, { - preserveTimestamps: true, - recursive: true - }, mustCall((err) => { - assert.strictEqual(err, null); - assertDirEquivalent(src, dest); - const srcStat = lstatSync(join(src, 'index.js')); - const destStat = lstatSync(join(dest, 'index.js')); - assert.strictEqual(srcStat.mtime.getTime(), destStat.mtime.getTime()); - })); -} - -// It applies filter function. -{ - const src = './test/fixtures/copy/kitchen-sink'; - const dest = nextdir(); - cp(src, dest, { - filter: (path) => { - const pathStat = statSync(path); - return pathStat.isDirectory() || path.endsWith('.js'); - }, - dereference: true, - recursive: true, - }, mustCall((err) => { - assert.strictEqual(err, null); - const destEntries = []; - collectEntries(dest, destEntries); - for (const entry of destEntries) { - assert.strictEqual( - entry.isDirectory() || entry.name.endsWith('.js'), - true - ); - } - })); -} - -// It supports async filter function. -{ - const src = './test/fixtures/copy/kitchen-sink'; - const dest = nextdir(); - cp(src, dest, { - filter: async (path) => { - await setTimeout(5, 'done'); - const pathStat = statSync(path); - return pathStat.isDirectory() || path.endsWith('.js'); - }, - dereference: true, - recursive: true, - }, mustCall((err) => { - assert.strictEqual(err, null); - const destEntries = []; - collectEntries(dest, destEntries); - for (const entry of destEntries) { - assert.strictEqual( - entry.isDirectory() || entry.name.endsWith('.js'), - true - ); - } - })); -} - -// It returns error if errorOnExist is true, force is false, and file or folder -// copied over. -{ - const src = './test/fixtures/copy/kitchen-sink'; - const dest = nextdir(); - cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); - cp(src, dest, { - dereference: true, - errorOnExist: true, - force: false, - recursive: true, - }, mustCall((err) => { - assert.strictEqual(err.code, 'ERR_FS_CP_EEXIST'); - })); -} - -// It returns EEXIST error if attempt is made to copy symlink over file. -{ - const src = nextdir(); - mkdirSync(join(src, 'a', 'b'), mustNotMutateObjectDeep({ recursive: true })); - symlinkSync(join(src, 'a', 'b'), join(src, 'a', 'c')); - - const dest = nextdir(); - mkdirSync(join(dest, 'a'), mustNotMutateObjectDeep({ recursive: true })); - writeFileSync(join(dest, 'a', 'c'), 'hello', 'utf8'); - cp(src, dest, mustNotMutateObjectDeep({ recursive: true }), mustCall((err) => { - assert.strictEqual(err.code, 'EEXIST'); - })); -} - -// It makes file writeable when updating timestamp, if not writeable. -{ - const src = nextdir(); - mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); - const dest = nextdir(); - mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); - writeFileSync(join(src, 'foo.txt'), 'foo', mustNotMutateObjectDeep({ mode: 0o444 })); - cp(src, dest, { - preserveTimestamps: true, - recursive: true, - }, mustCall((err) => { - assert.strictEqual(err, null); - assertDirEquivalent(src, dest); - const srcStat = lstatSync(join(src, 'foo.txt')); - const destStat = lstatSync(join(dest, 'foo.txt')); - assert.strictEqual(srcStat.mtime.getTime(), destStat.mtime.getTime()); - })); -} - -// It copies link if it does not point to folder in src. -{ - const src = nextdir(); - mkdirSync(join(src, 'a', 'b'), mustNotMutateObjectDeep({ recursive: true })); - symlinkSync(src, join(src, 'a', 'c')); - const dest = nextdir(); - mkdirSync(join(dest, 'a'), mustNotMutateObjectDeep({ recursive: true })); - symlinkSync(dest, join(dest, 'a', 'c')); - cp(src, dest, mustNotMutateObjectDeep({ recursive: true }), mustCall((err) => { - assert.strictEqual(err, null); - const link = readlinkSync(join(dest, 'a', 'c')); - assert.strictEqual(link, src); - })); -} - -// It accepts file URL as src and dest. -{ - const src = './test/fixtures/copy/kitchen-sink'; - const dest = nextdir(); - cp(pathToFileURL(src), pathToFileURL(dest), mustNotMutateObjectDeep({ recursive: true }), - mustCall((err) => { - assert.strictEqual(err, null); - assertDirEquivalent(src, dest); - })); -} - -// Copy should not throw exception if child folder is filtered out. -{ - const src = nextdir(); - mkdirSync(join(src, 'test-cp'), mustNotMutateObjectDeep({ recursive: true })); - - const dest = nextdir(); - mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); - writeFileSync(join(dest, 'test-cp'), 'test-content', mustNotMutateObjectDeep({ mode: 0o444 })); - - const opts = { - filter: (path) => !path.includes('test-cp'), - recursive: true, - }; - cp(src, dest, opts, mustCall((err) => { - assert.strictEqual(err, null); - })); - cpSync(src, dest, opts); -} - -// Copy should not throw exception if dest is invalid but filtered out. -{ - // Create dest as a file. - // Expect: cp skips the copy logic entirely and won't throw any exception in path validation process. - const src = join(nextdir(), 'bar'); - mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); - - const destParent = nextdir(); - const dest = join(destParent, 'bar'); - mkdirSync(destParent, mustNotMutateObjectDeep({ recursive: true })); - writeFileSync(dest, 'test-content', mustNotMutateObjectDeep({ mode: 0o444 })); - - const opts = { - filter: (path) => !path.includes('bar'), - recursive: true, - }; - cp(src, dest, opts, mustCall((err) => { - assert.strictEqual(err, null); - })); - cpSync(src, dest, opts); -} - -// It throws if options is not object. -{ - assert.throws( - () => cp('a', 'b', 'hello', () => {}), - { code: 'ERR_INVALID_ARG_TYPE' } - ); -} - -// It throws if options is not object. -{ - assert.throws( - () => cp('a', 'b', { mode: -1 }, () => {}), - { code: 'ERR_OUT_OF_RANGE' } - ); -} - -// Promises implementation of copy. - -// It copies a nested folder structure with files and folders. -{ - const src = './test/fixtures/copy/kitchen-sink'; - const dest = nextdir(); - const p = await fs.promises.cp(src, dest, mustNotMutateObjectDeep({ recursive: true })); - assert.strictEqual(p, undefined); - assertDirEquivalent(src, dest); -} - -// It copies a nested folder structure with mode flags. -// This test is based on fs.promises.copyFile() with `COPYFILE_FICLONE_FORCE`. -{ - const src = './test/fixtures/copy/kitchen-sink'; - const dest = nextdir(); - let p = null; - let successFiClone = false; - try { - p = await fs.promises.cp(src, dest, mustNotMutateObjectDeep({ - recursive: true, - mode: fs.constants.COPYFILE_FICLONE_FORCE, - })); - successFiClone = true; - } catch (err) { - // If the platform does not support `COPYFILE_FICLONE_FORCE` operation, - // it should enter this path. - assert.strictEqual(err.syscall, 'copyfile'); - assert(err.code === 'ENOTSUP' || err.code === 'ENOTTY' || - err.code === 'ENOSYS' || err.code === 'EXDEV'); - } - - if (successFiClone) { - // If the platform support `COPYFILE_FICLONE_FORCE` operation, - // it should reach to here. - assert.strictEqual(p, undefined); - assertDirEquivalent(src, dest); - } -} - -// It accepts file URL as src and dest. -{ - const src = './test/fixtures/copy/kitchen-sink'; - const dest = nextdir(); - const p = await fs.promises.cp( - pathToFileURL(src), - pathToFileURL(dest), - { recursive: true } - ); - assert.strictEqual(p, undefined); - assertDirEquivalent(src, dest); -} - -// It allows async error to be caught. -{ - const src = './test/fixtures/copy/kitchen-sink'; - const dest = nextdir(); - await fs.promises.cp(src, dest, mustNotMutateObjectDeep({ recursive: true })); - await assert.rejects( - fs.promises.cp(src, dest, { - dereference: true, - errorOnExist: true, - force: false, - recursive: true, - }), - { code: 'ERR_FS_CP_EEXIST' } - ); -} - -// It rejects if options is not object. -{ - await assert.rejects( - fs.promises.cp('a', 'b', () => {}), - { code: 'ERR_INVALID_ARG_TYPE' } - ); -} - -// It rejects if options.mode is invalid. -{ - await assert.rejects( - fs.promises.cp('a', 'b', { - mode: -1, - }), - { code: 'ERR_OUT_OF_RANGE' } - ); -} - -function assertDirEquivalent(dir1, dir2) { - const dir1Entries = []; - collectEntries(dir1, dir1Entries); - const dir2Entries = []; - collectEntries(dir2, dir2Entries); - assert.strictEqual(dir1Entries.length, dir2Entries.length); - for (const entry1 of dir1Entries) { - const entry2 = dir2Entries.find((entry) => { - return entry.name === entry1.name; - }); - assert(entry2, `entry ${entry2.name} not copied`); - if (entry1.isFile()) { - assert(entry2.isFile(), `${entry2.name} was not file`); - } else if (entry1.isDirectory()) { - assert(entry2.isDirectory(), `${entry2.name} was not directory`); - } else if (entry1.isSymbolicLink()) { - assert(entry2.isSymbolicLink(), `${entry2.name} was not symlink`); - } - } -} - -function collectEntries(dir, dirEntries) { - const newEntries = readdirSync(dir, mustNotMutateObjectDeep({ withFileTypes: true })); - for (const entry of newEntries) { - if (entry.isDirectory()) { - collectEntries(join(dir, entry.name), dirEntries); - } - } - dirEntries.push(...newEntries); -}