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); -}