node/test/parallel/test-fs-cp.mjs
Livia Medeiros 050d974a25
test: use common.mustNotMutateObjectDeep() in fs tests
PR-URL: https://github.com/nodejs/node/pull/43819
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
2022-07-24 14:29:06 +01:00

835 lines
25 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() {
return join(tmpdir.path, `copy_${++dirc}`);
}
// Synchronous implementation of copy.
// 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 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 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 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 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 dest = nextdir();
const sock = `${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 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 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 dest = nextdir();
const sock = `${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);
}));
}
// It throws if options is not object.
{
assert.throws(
() => cp('a', 'b', 'hello', () => {}),
{ code: 'ERR_INVALID_ARG_TYPE' }
);
}
// 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 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' }
);
}
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);
}