test: split test-fs-cp.js

This test previously squeezed 70+ test cases into one single file
and has been constantly crashing on Windows with exit code
3221226505 and no stack trace. As it is already marked as flaky
there is no way to understand which test case is failing and
the Windows CI was constantly orange. This patch splits the
test cases into different files so it's easier to find out
which case is exactly failing and to be skipped.

PR-URL: https://github.com/nodejs/node/pull/59408
Refs: https://github.com/nodejs/node/issues/56794
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Zeyu "Alex" Yang <himself65@outlook.com>
Reviewed-By: Daeyeon Jeong <daeyeon.dev@gmail.com>
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
This commit is contained in:
Joyee Cheung 2025-08-10 21:35:42 +02:00 committed by GitHub
parent ca76b39356
commit aac7925801
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
77 changed files with 1585 additions and 1100 deletions

49
test/common/fs.js Normal file
View file

@ -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,
};

View file

@ -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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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());

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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());

View file

@ -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());

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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());

View file

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

View file

@ -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');

View file

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

View file

@ -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());

View file

@ -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());

View file

@ -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'));

View file

@ -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'));

View file

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

View file

@ -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' },
);

View file

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

View file

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

View file

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

View file

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

View file

@ -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');

File diff suppressed because it is too large Load diff