• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import { mustCall, mustNotMutateObjectDeep } from '../common/index.mjs';
2
3import assert from 'assert';
4import fs from 'fs';
5const {
6  cp,
7  cpSync,
8  lstatSync,
9  mkdirSync,
10  readdirSync,
11  readFileSync,
12  readlinkSync,
13  symlinkSync,
14  statSync,
15  writeFileSync,
16} = fs;
17import net from 'net';
18import { join } from 'path';
19import { pathToFileURL } from 'url';
20import { setTimeout } from 'timers/promises';
21
22const isWindows = process.platform === 'win32';
23import tmpdir from '../common/tmpdir.js';
24tmpdir.refresh();
25
26let dirc = 0;
27function nextdir() {
28  return join(tmpdir.path, `copy_${++dirc}`);
29}
30
31// Synchronous implementation of copy.
32
33// It copies a nested folder structure with files and folders.
34{
35  const src = './test/fixtures/copy/kitchen-sink';
36  const dest = nextdir();
37  cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true }));
38  assertDirEquivalent(src, dest);
39}
40
41// It copies a nested folder structure with mode flags.
42// This test is based on fs.promises.copyFile() with `COPYFILE_FICLONE_FORCE`.
43(() => {
44  const src = './test/fixtures/copy/kitchen-sink';
45  const dest = nextdir();
46  try {
47    cpSync(src, dest, mustNotMutateObjectDeep({
48      recursive: true,
49      mode: fs.constants.COPYFILE_FICLONE_FORCE,
50    }));
51  } catch (err) {
52    // If the platform does not support `COPYFILE_FICLONE_FORCE` operation,
53    // it should enter this path.
54    assert.strictEqual(err.syscall, 'copyfile');
55    assert(err.code === 'ENOTSUP' || err.code === 'ENOTTY' ||
56      err.code === 'ENOSYS' || err.code === 'EXDEV');
57    return;
58  }
59
60  // If the platform support `COPYFILE_FICLONE_FORCE` operation,
61  // it should reach to here.
62  assertDirEquivalent(src, dest);
63})();
64
65// It does not throw errors when directory is copied over and force is false.
66{
67  const src = nextdir();
68  mkdirSync(join(src, 'a', 'b'), mustNotMutateObjectDeep({ recursive: true }));
69  writeFileSync(join(src, 'README.md'), 'hello world', 'utf8');
70  const dest = nextdir();
71  cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true }));
72  const initialStat = lstatSync(join(dest, 'README.md'));
73  cpSync(src, dest, mustNotMutateObjectDeep({ force: false, recursive: true }));
74  // File should not have been copied over, so access times will be identical:
75  assertDirEquivalent(src, dest);
76  const finalStat = lstatSync(join(dest, 'README.md'));
77  assert.strictEqual(finalStat.ctime.getTime(), initialStat.ctime.getTime());
78}
79
80// It overwrites existing files if force is true.
81{
82  const src = './test/fixtures/copy/kitchen-sink';
83  const dest = nextdir();
84  mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true }));
85  writeFileSync(join(dest, 'README.md'), '# Goodbye', 'utf8');
86  cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true }));
87  assertDirEquivalent(src, dest);
88  const content = readFileSync(join(dest, 'README.md'), 'utf8');
89  assert.strictEqual(content.trim(), '# Hello');
90}
91
92// It does not fail if the same directory is copied to dest twice,
93// when dereference is true, and force is false (fails silently).
94{
95  const src = './test/fixtures/copy/kitchen-sink';
96  const dest = nextdir();
97  const destFile = join(dest, 'a/b/README2.md');
98  cpSync(src, dest, mustNotMutateObjectDeep({ dereference: true, recursive: true }));
99  cpSync(src, dest, mustNotMutateObjectDeep({ dereference: true, recursive: true }));
100  const stat = lstatSync(destFile);
101  assert(stat.isFile());
102}
103
104
105// It copies file itself, rather than symlink, when dereference is true.
106{
107  const src = nextdir();
108  mkdirSync(src, mustNotMutateObjectDeep({ recursive: true }));
109  writeFileSync(join(src, 'foo.js'), 'foo', 'utf8');
110  symlinkSync(join(src, 'foo.js'), join(src, 'bar.js'));
111
112  const dest = nextdir();
113  mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true }));
114  const destFile = join(dest, 'foo.js');
115
116  cpSync(join(src, 'bar.js'), destFile, mustNotMutateObjectDeep({ dereference: true, recursive: true }));
117  const stat = lstatSync(destFile);
118  assert(stat.isFile());
119}
120
121
122// It throws error when verbatimSymlinks is not a boolean.
123{
124  const src = './test/fixtures/copy/kitchen-sink';
125  [1, [], {}, null, 1n, undefined, null, Symbol(), '', () => {}]
126    .forEach((verbatimSymlinks) => {
127      assert.throws(
128        () => cpSync(src, src, { verbatimSymlinks }),
129        { code: 'ERR_INVALID_ARG_TYPE' }
130      );
131    });
132}
133
134// It rejects if options.mode is invalid.
135{
136  assert.throws(
137    () => cpSync('a', 'b', { mode: -1 }),
138    { code: 'ERR_OUT_OF_RANGE' }
139  );
140}
141
142
143// It throws an error when both dereference and verbatimSymlinks are enabled.
144{
145  const src = './test/fixtures/copy/kitchen-sink';
146  assert.throws(
147    () => cpSync(src, src, mustNotMutateObjectDeep({ dereference: true, verbatimSymlinks: true })),
148    { code: 'ERR_INCOMPATIBLE_OPTION_PAIR' }
149  );
150}
151
152
153// It resolves relative symlinks to their absolute path by default.
154{
155  const src = nextdir();
156  mkdirSync(src, mustNotMutateObjectDeep({ recursive: true }));
157  writeFileSync(join(src, 'foo.js'), 'foo', 'utf8');
158  symlinkSync('foo.js', join(src, 'bar.js'));
159
160  const dest = nextdir();
161  mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true }));
162
163  cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true }));
164  const link = readlinkSync(join(dest, 'bar.js'));
165  assert.strictEqual(link, join(src, 'foo.js'));
166}
167
168
169// It resolves relative symlinks when verbatimSymlinks is false.
170{
171  const src = nextdir();
172  mkdirSync(src, mustNotMutateObjectDeep({ recursive: true }));
173  writeFileSync(join(src, 'foo.js'), 'foo', 'utf8');
174  symlinkSync('foo.js', join(src, 'bar.js'));
175
176  const dest = nextdir();
177  mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true }));
178
179  cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true, verbatimSymlinks: false }));
180  const link = readlinkSync(join(dest, 'bar.js'));
181  assert.strictEqual(link, join(src, 'foo.js'));
182}
183
184
185// It does not resolve relative symlinks when verbatimSymlinks is true.
186{
187  const src = nextdir();
188  mkdirSync(src, mustNotMutateObjectDeep({ recursive: true }));
189  writeFileSync(join(src, 'foo.js'), 'foo', 'utf8');
190  symlinkSync('foo.js', join(src, 'bar.js'));
191
192  const dest = nextdir();
193  mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true }));
194
195  cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true, verbatimSymlinks: true }));
196  const link = readlinkSync(join(dest, 'bar.js'));
197  assert.strictEqual(link, 'foo.js');
198}
199
200
201// It throws error when src and dest are identical.
202{
203  const src = './test/fixtures/copy/kitchen-sink';
204  assert.throws(
205    () => cpSync(src, src),
206    { code: 'ERR_FS_CP_EINVAL' }
207  );
208}
209
210// It throws error if symlink in src points to location in dest.
211{
212  const src = nextdir();
213  mkdirSync(src, mustNotMutateObjectDeep({ recursive: true }));
214  const dest = nextdir();
215  mkdirSync(dest);
216  symlinkSync(dest, join(src, 'link'));
217  cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true }));
218  assert.throws(
219    () => cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })),
220    {
221      code: 'ERR_FS_CP_EINVAL'
222    }
223  );
224}
225
226// It throws error if symlink in dest points to location in src.
227{
228  const src = nextdir();
229  mkdirSync(join(src, 'a', 'b'), mustNotMutateObjectDeep({ recursive: true }));
230  symlinkSync(join(src, 'a', 'b'), join(src, 'a', 'c'));
231
232  const dest = nextdir();
233  mkdirSync(join(dest, 'a'), mustNotMutateObjectDeep({ recursive: true }));
234  symlinkSync(src, join(dest, 'a', 'c'));
235  assert.throws(
236    () => cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })),
237    { code: 'ERR_FS_CP_SYMLINK_TO_SUBDIRECTORY' }
238  );
239}
240
241// It throws error if parent directory of symlink in dest points to src.
242{
243  const src = nextdir();
244  mkdirSync(join(src, 'a'), mustNotMutateObjectDeep({ recursive: true }));
245  const dest = nextdir();
246  // Create symlink in dest pointing to src.
247  const destLink = join(dest, 'b');
248  mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true }));
249  symlinkSync(src, destLink);
250  assert.throws(
251    () => cpSync(src, join(dest, 'b', 'c')),
252    { code: 'ERR_FS_CP_EINVAL' }
253  );
254}
255
256// It throws error if attempt is made to copy directory to file.
257{
258  const src = nextdir();
259  mkdirSync(src, mustNotMutateObjectDeep({ recursive: true }));
260  const dest = './test/fixtures/copy/kitchen-sink/README.md';
261  assert.throws(
262    () => cpSync(src, dest),
263    { code: 'ERR_FS_CP_DIR_TO_NON_DIR' }
264  );
265}
266
267// It allows file to be copied to a file path.
268{
269  const srcFile = './test/fixtures/copy/kitchen-sink/index.js';
270  const destFile = join(nextdir(), 'index.js');
271  cpSync(srcFile, destFile, mustNotMutateObjectDeep({ dereference: true }));
272  const stat = lstatSync(destFile);
273  assert(stat.isFile());
274}
275
276// It throws error if directory copied without recursive flag.
277{
278  const src = './test/fixtures/copy/kitchen-sink';
279  const dest = nextdir();
280  assert.throws(
281    () => cpSync(src, dest),
282    { code: 'ERR_FS_EISDIR' }
283  );
284}
285
286
287// It throws error if attempt is made to copy file to directory.
288{
289  const src = './test/fixtures/copy/kitchen-sink/README.md';
290  const dest = nextdir();
291  mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true }));
292  assert.throws(
293    () => cpSync(src, dest),
294    { code: 'ERR_FS_CP_NON_DIR_TO_DIR' }
295  );
296}
297
298// It throws error if attempt is made to copy to subdirectory of self.
299{
300  const src = './test/fixtures/copy/kitchen-sink';
301  const dest = './test/fixtures/copy/kitchen-sink/a';
302  assert.throws(
303    () => cpSync(src, dest),
304    { code: 'ERR_FS_CP_EINVAL' }
305  );
306}
307
308// It throws an error if attempt is made to copy socket.
309if (!isWindows) {
310  const src = nextdir();
311  mkdirSync(src);
312  const dest = nextdir();
313  const sock = join(src, `${process.pid}.sock`);
314  const server = net.createServer();
315  server.listen(sock);
316  assert.throws(
317    () => cpSync(sock, dest),
318    { code: 'ERR_FS_CP_SOCKET' }
319  );
320  server.close();
321}
322
323// It copies timestamps from src to dest if preserveTimestamps is true.
324{
325  const src = './test/fixtures/copy/kitchen-sink';
326  const dest = nextdir();
327  cpSync(src, dest, mustNotMutateObjectDeep({ preserveTimestamps: true, recursive: true }));
328  assertDirEquivalent(src, dest);
329  const srcStat = lstatSync(join(src, 'index.js'));
330  const destStat = lstatSync(join(dest, 'index.js'));
331  assert.strictEqual(srcStat.mtime.getTime(), destStat.mtime.getTime());
332}
333
334// It applies filter function.
335{
336  const src = './test/fixtures/copy/kitchen-sink';
337  const dest = nextdir();
338  cpSync(src, dest, {
339    filter: (path) => {
340      const pathStat = statSync(path);
341      return pathStat.isDirectory() || path.endsWith('.js');
342    },
343    dereference: true,
344    recursive: true,
345  });
346  const destEntries = [];
347  collectEntries(dest, destEntries);
348  for (const entry of destEntries) {
349    assert.strictEqual(
350      entry.isDirectory() || entry.name.endsWith('.js'),
351      true
352    );
353  }
354}
355
356// It throws error if filter function is asynchronous.
357{
358  const src = './test/fixtures/copy/kitchen-sink';
359  const dest = nextdir();
360  assert.throws(() => {
361    cpSync(src, dest, {
362      filter: async (path) => {
363        await setTimeout(5, 'done');
364        const pathStat = statSync(path);
365        return pathStat.isDirectory() || path.endsWith('.js');
366      },
367      dereference: true,
368      recursive: true,
369    });
370  }, { code: 'ERR_INVALID_RETURN_VALUE' });
371}
372
373// It throws error if errorOnExist is true, force is false, and file or folder
374// copied over.
375{
376  const src = './test/fixtures/copy/kitchen-sink';
377  const dest = nextdir();
378  cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true }));
379  assert.throws(
380    () => cpSync(src, dest, {
381      dereference: true,
382      errorOnExist: true,
383      force: false,
384      recursive: true,
385    }),
386    { code: 'ERR_FS_CP_EEXIST' }
387  );
388}
389
390// It throws EEXIST error if attempt is made to copy symlink over file.
391{
392  const src = nextdir();
393  mkdirSync(join(src, 'a', 'b'), mustNotMutateObjectDeep({ recursive: true }));
394  symlinkSync(join(src, 'a', 'b'), join(src, 'a', 'c'));
395
396  const dest = nextdir();
397  mkdirSync(join(dest, 'a'), mustNotMutateObjectDeep({ recursive: true }));
398  writeFileSync(join(dest, 'a', 'c'), 'hello', 'utf8');
399  assert.throws(
400    () => cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })),
401    { code: 'EEXIST' }
402  );
403}
404
405// It makes file writeable when updating timestamp, if not writeable.
406{
407  const src = nextdir();
408  mkdirSync(src, mustNotMutateObjectDeep({ recursive: true }));
409  const dest = nextdir();
410  mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true }));
411  writeFileSync(join(src, 'foo.txt'), 'foo', mustNotMutateObjectDeep({ mode: 0o444 }));
412  cpSync(src, dest, mustNotMutateObjectDeep({ preserveTimestamps: true, recursive: true }));
413  assertDirEquivalent(src, dest);
414  const srcStat = lstatSync(join(src, 'foo.txt'));
415  const destStat = lstatSync(join(dest, 'foo.txt'));
416  assert.strictEqual(srcStat.mtime.getTime(), destStat.mtime.getTime());
417}
418
419// It copies link if it does not point to folder in src.
420{
421  const src = nextdir();
422  mkdirSync(join(src, 'a', 'b'), mustNotMutateObjectDeep({ recursive: true }));
423  symlinkSync(src, join(src, 'a', 'c'));
424  const dest = nextdir();
425  mkdirSync(join(dest, 'a'), mustNotMutateObjectDeep({ recursive: true }));
426  symlinkSync(dest, join(dest, 'a', 'c'));
427  cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true }));
428  const link = readlinkSync(join(dest, 'a', 'c'));
429  assert.strictEqual(link, src);
430}
431
432// It accepts file URL as src and dest.
433{
434  const src = './test/fixtures/copy/kitchen-sink';
435  const dest = nextdir();
436  cpSync(pathToFileURL(src), pathToFileURL(dest), mustNotMutateObjectDeep({ recursive: true }));
437  assertDirEquivalent(src, dest);
438}
439
440// It throws if options is not object.
441{
442  assert.throws(
443    () => cpSync('a', 'b', () => {}),
444    { code: 'ERR_INVALID_ARG_TYPE' }
445  );
446}
447
448// Callback implementation of copy.
449
450// It copies a nested folder structure with files and folders.
451{
452  const src = './test/fixtures/copy/kitchen-sink';
453  const dest = nextdir();
454  cp(src, dest, mustNotMutateObjectDeep({ recursive: true }), mustCall((err) => {
455    assert.strictEqual(err, null);
456    assertDirEquivalent(src, dest);
457  }));
458}
459
460// It copies a nested folder structure with mode flags.
461// This test is based on fs.promises.copyFile() with `COPYFILE_FICLONE_FORCE`.
462{
463  const src = './test/fixtures/copy/kitchen-sink';
464  const dest = nextdir();
465  cp(src, dest, mustNotMutateObjectDeep({
466    recursive: true,
467    mode: fs.constants.COPYFILE_FICLONE_FORCE,
468  }), mustCall((err) => {
469    if (!err) {
470      // If the platform support `COPYFILE_FICLONE_FORCE` operation,
471      // it should reach to here.
472      assert.strictEqual(err, null);
473      assertDirEquivalent(src, dest);
474      return;
475    }
476
477    // If the platform does not support `COPYFILE_FICLONE_FORCE` operation,
478    // it should enter this path.
479    assert.strictEqual(err.syscall, 'copyfile');
480    assert(err.code === 'ENOTSUP' || err.code === 'ENOTTY' ||
481      err.code === 'ENOSYS' || err.code === 'EXDEV');
482  }));
483}
484
485// It does not throw errors when directory is copied over and force is false.
486{
487  const src = nextdir();
488  mkdirSync(join(src, 'a', 'b'), mustNotMutateObjectDeep({ recursive: true }));
489  writeFileSync(join(src, 'README.md'), 'hello world', 'utf8');
490  const dest = nextdir();
491  cpSync(src, dest, mustNotMutateObjectDeep({ dereference: true, recursive: true }));
492  const initialStat = lstatSync(join(dest, 'README.md'));
493  cp(src, dest, {
494    dereference: true,
495    force: false,
496    recursive: true,
497  }, mustCall((err) => {
498    assert.strictEqual(err, null);
499    assertDirEquivalent(src, dest);
500    // File should not have been copied over, so access times will be identical:
501    const finalStat = lstatSync(join(dest, 'README.md'));
502    assert.strictEqual(finalStat.ctime.getTime(), initialStat.ctime.getTime());
503  }));
504}
505
506// It overwrites existing files if force is true.
507{
508  const src = './test/fixtures/copy/kitchen-sink';
509  const dest = nextdir();
510  mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true }));
511  writeFileSync(join(dest, 'README.md'), '# Goodbye', 'utf8');
512
513  cp(src, dest, mustNotMutateObjectDeep({ recursive: true }), mustCall((err) => {
514    assert.strictEqual(err, null);
515    assertDirEquivalent(src, dest);
516    const content = readFileSync(join(dest, 'README.md'), 'utf8');
517    assert.strictEqual(content.trim(), '# Hello');
518  }));
519}
520
521// It does not fail if the same directory is copied to dest twice,
522// when dereference is true, and force is false (fails silently).
523{
524  const src = './test/fixtures/copy/kitchen-sink';
525  const dest = nextdir();
526  const destFile = join(dest, 'a/b/README2.md');
527  cpSync(src, dest, mustNotMutateObjectDeep({ dereference: true, recursive: true }));
528  cp(src, dest, {
529    dereference: true,
530    recursive: true
531  }, mustCall((err) => {
532    assert.strictEqual(err, null);
533    const stat = lstatSync(destFile);
534    assert(stat.isFile());
535  }));
536}
537
538// It copies file itself, rather than symlink, when dereference is true.
539{
540  const src = nextdir();
541  mkdirSync(src, mustNotMutateObjectDeep({ recursive: true }));
542  writeFileSync(join(src, 'foo.js'), 'foo', 'utf8');
543  symlinkSync(join(src, 'foo.js'), join(src, 'bar.js'));
544
545  const dest = nextdir();
546  mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true }));
547  const destFile = join(dest, 'foo.js');
548
549  cp(join(src, 'bar.js'), destFile, mustNotMutateObjectDeep({ dereference: true }),
550     mustCall((err) => {
551       assert.strictEqual(err, null);
552       const stat = lstatSync(destFile);
553       assert(stat.isFile());
554     })
555  );
556}
557
558// It returns error when src and dest are identical.
559{
560  const src = './test/fixtures/copy/kitchen-sink';
561  cp(src, src, mustCall((err) => {
562    assert.strictEqual(err.code, 'ERR_FS_CP_EINVAL');
563  }));
564}
565
566// It returns error if symlink in src points to location in dest.
567{
568  const src = nextdir();
569  mkdirSync(src, mustNotMutateObjectDeep({ recursive: true }));
570  const dest = nextdir();
571  mkdirSync(dest);
572  symlinkSync(dest, join(src, 'link'));
573  cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true }));
574  cp(src, dest, mustNotMutateObjectDeep({ recursive: true }), mustCall((err) => {
575    assert.strictEqual(err.code, 'ERR_FS_CP_EINVAL');
576  }));
577}
578
579// It returns error if symlink in dest points to location in src.
580{
581  const src = nextdir();
582  mkdirSync(join(src, 'a', 'b'), mustNotMutateObjectDeep({ recursive: true }));
583  symlinkSync(join(src, 'a', 'b'), join(src, 'a', 'c'));
584
585  const dest = nextdir();
586  mkdirSync(join(dest, 'a'), mustNotMutateObjectDeep({ recursive: true }));
587  symlinkSync(src, join(dest, 'a', 'c'));
588  cp(src, dest, mustNotMutateObjectDeep({ recursive: true }), mustCall((err) => {
589    assert.strictEqual(err.code, 'ERR_FS_CP_SYMLINK_TO_SUBDIRECTORY');
590  }));
591}
592
593// It returns error if parent directory of symlink in dest points to src.
594{
595  const src = nextdir();
596  mkdirSync(join(src, 'a'), mustNotMutateObjectDeep({ recursive: true }));
597  const dest = nextdir();
598  // Create symlink in dest pointing to src.
599  const destLink = join(dest, 'b');
600  mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true }));
601  symlinkSync(src, destLink);
602  cp(src, join(dest, 'b', 'c'), mustCall((err) => {
603    assert.strictEqual(err.code, 'ERR_FS_CP_EINVAL');
604  }));
605}
606
607// It returns error if attempt is made to copy directory to file.
608{
609  const src = nextdir();
610  mkdirSync(src, mustNotMutateObjectDeep({ recursive: true }));
611  const dest = './test/fixtures/copy/kitchen-sink/README.md';
612  cp(src, dest, mustCall((err) => {
613    assert.strictEqual(err.code, 'ERR_FS_CP_DIR_TO_NON_DIR');
614  }));
615}
616
617// It allows file to be copied to a file path.
618{
619  const srcFile = './test/fixtures/copy/kitchen-sink/README.md';
620  const destFile = join(nextdir(), 'index.js');
621  cp(srcFile, destFile, mustNotMutateObjectDeep({ dereference: true }), mustCall((err) => {
622    assert.strictEqual(err, null);
623    const stat = lstatSync(destFile);
624    assert(stat.isFile());
625  }));
626}
627
628// It returns error if directory copied without recursive flag.
629{
630  const src = './test/fixtures/copy/kitchen-sink';
631  const dest = nextdir();
632  cp(src, dest, mustCall((err) => {
633    assert.strictEqual(err.code, 'ERR_FS_EISDIR');
634  }));
635}
636
637// It returns error if attempt is made to copy file to directory.
638{
639  const src = './test/fixtures/copy/kitchen-sink/README.md';
640  const dest = nextdir();
641  mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true }));
642  cp(src, dest, mustCall((err) => {
643    assert.strictEqual(err.code, 'ERR_FS_CP_NON_DIR_TO_DIR');
644  }));
645}
646
647// It returns error if attempt is made to copy to subdirectory of self.
648{
649  const src = './test/fixtures/copy/kitchen-sink';
650  const dest = './test/fixtures/copy/kitchen-sink/a';
651  cp(src, dest, mustCall((err) => {
652    assert.strictEqual(err.code, 'ERR_FS_CP_EINVAL');
653  }));
654}
655
656// It returns an error if attempt is made to copy socket.
657if (!isWindows) {
658  const src = nextdir();
659  mkdirSync(src);
660  const dest = nextdir();
661  const sock = join(src, `${process.pid}.sock`);
662  const server = net.createServer();
663  server.listen(sock);
664  cp(sock, dest, mustCall((err) => {
665    assert.strictEqual(err.code, 'ERR_FS_CP_SOCKET');
666    server.close();
667  }));
668}
669
670// It copies timestamps from src to dest if preserveTimestamps is true.
671{
672  const src = './test/fixtures/copy/kitchen-sink';
673  const dest = nextdir();
674  cp(src, dest, {
675    preserveTimestamps: true,
676    recursive: true
677  }, mustCall((err) => {
678    assert.strictEqual(err, null);
679    assertDirEquivalent(src, dest);
680    const srcStat = lstatSync(join(src, 'index.js'));
681    const destStat = lstatSync(join(dest, 'index.js'));
682    assert.strictEqual(srcStat.mtime.getTime(), destStat.mtime.getTime());
683  }));
684}
685
686// It applies filter function.
687{
688  const src = './test/fixtures/copy/kitchen-sink';
689  const dest = nextdir();
690  cp(src, dest, {
691    filter: (path) => {
692      const pathStat = statSync(path);
693      return pathStat.isDirectory() || path.endsWith('.js');
694    },
695    dereference: true,
696    recursive: true,
697  }, mustCall((err) => {
698    assert.strictEqual(err, null);
699    const destEntries = [];
700    collectEntries(dest, destEntries);
701    for (const entry of destEntries) {
702      assert.strictEqual(
703        entry.isDirectory() || entry.name.endsWith('.js'),
704        true
705      );
706    }
707  }));
708}
709
710// It supports async filter function.
711{
712  const src = './test/fixtures/copy/kitchen-sink';
713  const dest = nextdir();
714  cp(src, dest, {
715    filter: async (path) => {
716      await setTimeout(5, 'done');
717      const pathStat = statSync(path);
718      return pathStat.isDirectory() || path.endsWith('.js');
719    },
720    dereference: true,
721    recursive: true,
722  }, mustCall((err) => {
723    assert.strictEqual(err, null);
724    const destEntries = [];
725    collectEntries(dest, destEntries);
726    for (const entry of destEntries) {
727      assert.strictEqual(
728        entry.isDirectory() || entry.name.endsWith('.js'),
729        true
730      );
731    }
732  }));
733}
734
735// It returns error if errorOnExist is true, force is false, and file or folder
736// copied over.
737{
738  const src = './test/fixtures/copy/kitchen-sink';
739  const dest = nextdir();
740  cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true }));
741  cp(src, dest, {
742    dereference: true,
743    errorOnExist: true,
744    force: false,
745    recursive: true,
746  }, mustCall((err) => {
747    assert.strictEqual(err.code, 'ERR_FS_CP_EEXIST');
748  }));
749}
750
751// It returns EEXIST error if attempt is made to copy symlink over file.
752{
753  const src = nextdir();
754  mkdirSync(join(src, 'a', 'b'), mustNotMutateObjectDeep({ recursive: true }));
755  symlinkSync(join(src, 'a', 'b'), join(src, 'a', 'c'));
756
757  const dest = nextdir();
758  mkdirSync(join(dest, 'a'), mustNotMutateObjectDeep({ recursive: true }));
759  writeFileSync(join(dest, 'a', 'c'), 'hello', 'utf8');
760  cp(src, dest, mustNotMutateObjectDeep({ recursive: true }), mustCall((err) => {
761    assert.strictEqual(err.code, 'EEXIST');
762  }));
763}
764
765// It makes file writeable when updating timestamp, if not writeable.
766{
767  const src = nextdir();
768  mkdirSync(src, mustNotMutateObjectDeep({ recursive: true }));
769  const dest = nextdir();
770  mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true }));
771  writeFileSync(join(src, 'foo.txt'), 'foo', mustNotMutateObjectDeep({ mode: 0o444 }));
772  cp(src, dest, {
773    preserveTimestamps: true,
774    recursive: true,
775  }, mustCall((err) => {
776    assert.strictEqual(err, null);
777    assertDirEquivalent(src, dest);
778    const srcStat = lstatSync(join(src, 'foo.txt'));
779    const destStat = lstatSync(join(dest, 'foo.txt'));
780    assert.strictEqual(srcStat.mtime.getTime(), destStat.mtime.getTime());
781  }));
782}
783
784// It copies link if it does not point to folder in src.
785{
786  const src = nextdir();
787  mkdirSync(join(src, 'a', 'b'), mustNotMutateObjectDeep({ recursive: true }));
788  symlinkSync(src, join(src, 'a', 'c'));
789  const dest = nextdir();
790  mkdirSync(join(dest, 'a'), mustNotMutateObjectDeep({ recursive: true }));
791  symlinkSync(dest, join(dest, 'a', 'c'));
792  cp(src, dest, mustNotMutateObjectDeep({ recursive: true }), mustCall((err) => {
793    assert.strictEqual(err, null);
794    const link = readlinkSync(join(dest, 'a', 'c'));
795    assert.strictEqual(link, src);
796  }));
797}
798
799// It accepts file URL as src and dest.
800{
801  const src = './test/fixtures/copy/kitchen-sink';
802  const dest = nextdir();
803  cp(pathToFileURL(src), pathToFileURL(dest), mustNotMutateObjectDeep({ recursive: true }),
804     mustCall((err) => {
805       assert.strictEqual(err, null);
806       assertDirEquivalent(src, dest);
807     }));
808}
809
810// Copy should not throw exception if child folder is filtered out.
811{
812  const src = nextdir();
813  mkdirSync(join(src, 'test-cp'), mustNotMutateObjectDeep({ recursive: true }));
814
815  const dest = nextdir();
816  mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true }));
817  writeFileSync(join(dest, 'test-cp'), 'test-content', mustNotMutateObjectDeep({ mode: 0o444 }));
818
819  const opts = {
820    filter: (path) => !path.includes('test-cp'),
821    recursive: true,
822  };
823  cp(src, dest, opts, mustCall((err) => {
824    assert.strictEqual(err, null);
825  }));
826  cpSync(src, dest, opts);
827}
828
829// Copy should not throw exception if dest is invalid but filtered out.
830{
831  // Create dest as a file.
832  // Expect: cp skips the copy logic entirely and won't throw any exception in path validation process.
833  const src = join(nextdir(), 'bar');
834  mkdirSync(src, mustNotMutateObjectDeep({ recursive: true }));
835
836  const destParent = nextdir();
837  const dest = join(destParent, 'bar');
838  mkdirSync(destParent, mustNotMutateObjectDeep({ recursive: true }));
839  writeFileSync(dest, 'test-content', mustNotMutateObjectDeep({ mode: 0o444 }));
840
841  const opts = {
842    filter: (path) => !path.includes('bar'),
843    recursive: true,
844  };
845  cp(src, dest, opts, mustCall((err) => {
846    assert.strictEqual(err, null);
847  }));
848  cpSync(src, dest, opts);
849}
850
851// It throws if options is not object.
852{
853  assert.throws(
854    () => cp('a', 'b', 'hello', () => {}),
855    { code: 'ERR_INVALID_ARG_TYPE' }
856  );
857}
858
859// It throws if options is not object.
860{
861  assert.throws(
862    () => cp('a', 'b', { mode: -1 }, () => {}),
863    { code: 'ERR_OUT_OF_RANGE' }
864  );
865}
866
867// Promises implementation of copy.
868
869// It copies a nested folder structure with files and folders.
870{
871  const src = './test/fixtures/copy/kitchen-sink';
872  const dest = nextdir();
873  const p = await fs.promises.cp(src, dest, mustNotMutateObjectDeep({ recursive: true }));
874  assert.strictEqual(p, undefined);
875  assertDirEquivalent(src, dest);
876}
877
878// It copies a nested folder structure with mode flags.
879// This test is based on fs.promises.copyFile() with `COPYFILE_FICLONE_FORCE`.
880{
881  const src = './test/fixtures/copy/kitchen-sink';
882  const dest = nextdir();
883  let p = null;
884  let successFiClone = false;
885  try {
886    p = await fs.promises.cp(src, dest, mustNotMutateObjectDeep({
887      recursive: true,
888      mode: fs.constants.COPYFILE_FICLONE_FORCE,
889    }));
890    successFiClone = true;
891  } catch (err) {
892    // If the platform does not support `COPYFILE_FICLONE_FORCE` operation,
893    // it should enter this path.
894    assert.strictEqual(err.syscall, 'copyfile');
895    assert(err.code === 'ENOTSUP' || err.code === 'ENOTTY' ||
896      err.code === 'ENOSYS' || err.code === 'EXDEV');
897  }
898
899  if (successFiClone) {
900    // If the platform support `COPYFILE_FICLONE_FORCE` operation,
901    // it should reach to here.
902    assert.strictEqual(p, undefined);
903    assertDirEquivalent(src, dest);
904  }
905}
906
907// It accepts file URL as src and dest.
908{
909  const src = './test/fixtures/copy/kitchen-sink';
910  const dest = nextdir();
911  const p = await fs.promises.cp(
912    pathToFileURL(src),
913    pathToFileURL(dest),
914    { recursive: true }
915  );
916  assert.strictEqual(p, undefined);
917  assertDirEquivalent(src, dest);
918}
919
920// It allows async error to be caught.
921{
922  const src = './test/fixtures/copy/kitchen-sink';
923  const dest = nextdir();
924  await fs.promises.cp(src, dest, mustNotMutateObjectDeep({ recursive: true }));
925  await assert.rejects(
926    fs.promises.cp(src, dest, {
927      dereference: true,
928      errorOnExist: true,
929      force: false,
930      recursive: true,
931    }),
932    { code: 'ERR_FS_CP_EEXIST' }
933  );
934}
935
936// It rejects if options is not object.
937{
938  await assert.rejects(
939    fs.promises.cp('a', 'b', () => {}),
940    { code: 'ERR_INVALID_ARG_TYPE' }
941  );
942}
943
944// It rejects if options.mode is invalid.
945{
946  await assert.rejects(
947    fs.promises.cp('a', 'b', {
948      mode: -1,
949    }),
950    { code: 'ERR_OUT_OF_RANGE' }
951  );
952}
953
954function assertDirEquivalent(dir1, dir2) {
955  const dir1Entries = [];
956  collectEntries(dir1, dir1Entries);
957  const dir2Entries = [];
958  collectEntries(dir2, dir2Entries);
959  assert.strictEqual(dir1Entries.length, dir2Entries.length);
960  for (const entry1 of dir1Entries) {
961    const entry2 = dir2Entries.find((entry) => {
962      return entry.name === entry1.name;
963    });
964    assert(entry2, `entry ${entry2.name} not copied`);
965    if (entry1.isFile()) {
966      assert(entry2.isFile(), `${entry2.name} was not file`);
967    } else if (entry1.isDirectory()) {
968      assert(entry2.isDirectory(), `${entry2.name} was not directory`);
969    } else if (entry1.isSymbolicLink()) {
970      assert(entry2.isSymbolicLink(), `${entry2.name} was not symlink`);
971    }
972  }
973}
974
975function collectEntries(dir, dirEntries) {
976  const newEntries = readdirSync(dir, mustNotMutateObjectDeep({ withFileTypes: true }));
977  for (const entry of newEntries) {
978    if (entry.isDirectory()) {
979      collectEntries(join(dir, entry.name), dirEntries);
980    }
981  }
982  dirEntries.push(...newEntries);
983}
984