node/test/parallel/test-fs-cp.mjs
jazelly 56e5bd8d2a
fs: use wstring on Windows paths
Co-authored-by: Antoine du Hamel <duhamelantoine1995@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/55171
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
2024-10-18 09:07:57 +02:00

1068 lines
32 KiB
JavaScript

import { mustCall, mustNotMutateObjectDeep } 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 } 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 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.
{
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.
{
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.
{
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
{
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) {
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 }));
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) {
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);
}