mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 21:19:50 +01:00
56e5bd8d2a
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>
1068 lines
32 KiB
JavaScript
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);
|
|
}
|