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