@@ -805,3 +805,117 @@ test('cp -R should be able to copy a readonly src. issue #98; (Non window platfo
805805 shell . chmod ( '-R' , '755' , t . context . tmp ) ;
806806 } ) ;
807807} ) ;
808+
809+ test ( 'cp -p should preserve mode, ownership, and timestamp (regular file)' , t => {
810+ // Setup: copy to srcFile and modify mode and timestamp
811+ const srcFile = `${ t . context . tmp } /srcFile` ;
812+ shell . cp ( 'test/resources/cp/file1' , srcFile ) ;
813+ // Make this a round number of seconds, since the underlying system may not
814+ // have millisecond precision.
815+ const newModifyTimeMs = 12345000 ;
816+ const newAccessTimeMs = 67890000 ;
817+ shell . touch ( { '-d' : new Date ( newModifyTimeMs ) , '-m' : true } , srcFile ) ;
818+ shell . touch ( { '-d' : new Date ( newAccessTimeMs ) , '-a' : true } , srcFile ) ;
819+ const mode = '444' ;
820+ shell . chmod ( mode , srcFile ) ;
821+
822+ // Now re-copy with '-p' and verify metadata.
823+ const result = shell . cp ( '-p' , srcFile , `${ t . context . tmp } /preservedFile1` ) ;
824+ const stat = common . statFollowLinks ( srcFile ) ;
825+ const statOfResult = common . statFollowLinks ( `${ t . context . tmp } /preservedFile1` ) ;
826+
827+ t . is ( result . code , 0 ) ;
828+
829+ // Original file should be unchanged:
830+ t . is ( stat . mtime . getTime ( ) , newModifyTimeMs ) ;
831+ // cp appears to update the atime, but only of the srcFile
832+ t . is ( stat . mode . toString ( 8 ) , '100' + mode ) ;
833+
834+ // New file should keep same attributes
835+ t . is ( statOfResult . mtime . getTime ( ) , newModifyTimeMs ) ;
836+ t . is ( statOfResult . atime . getTime ( ) , newAccessTimeMs ) ;
837+ t . is ( statOfResult . mode . toString ( 8 ) , '100' + mode ) ;
838+
839+ t . is ( stat . uid , statOfResult . uid ) ;
840+ t . is ( stat . gid , statOfResult . gid ) ;
841+ } ) ;
842+
843+ test ( 'cp -p should preserve mode, ownership, and timestamp (directory)' , t => {
844+ // Setup: copy to srcFile and modify mode and timestamp
845+ const srcDir = `${ t . context . tmp } /srcDir` ;
846+ const srcFile = `${ srcDir } /srcFile` ;
847+ shell . mkdir ( srcDir ) ;
848+ shell . cp ( 'test/resources/cp/file1' , srcFile ) ;
849+ // Make this a round number of seconds, since the underlying system may not
850+ // have millisecond precision.
851+ const newModifyTimeMs = 12345000 ;
852+ const newAccessTimeMs = 67890000 ;
853+ shell . touch ( { '-d' : new Date ( newModifyTimeMs ) , '-m' : true } , srcFile ) ;
854+ shell . touch ( { '-d' : new Date ( newAccessTimeMs ) , '-a' : true } , srcFile ) ;
855+ fs . utimesSync ( srcDir , new Date ( newAccessTimeMs ) , new Date ( newModifyTimeMs ) ) ;
856+ const mode = '444' ;
857+ shell . chmod ( mode , srcFile ) ;
858+
859+ // Now re-copy (the whole dir) with '-p' and verify metadata of file contents.
860+ const result = shell . cp ( '-pr' , srcDir , `${ t . context . tmp } /preservedDir` ) ;
861+ const stat = common . statFollowLinks ( srcFile ) ;
862+ const statDir = common . statFollowLinks ( srcDir ) ;
863+ const statOfResult = common . statFollowLinks ( `${ t . context . tmp } /preservedDir/srcFile` ) ;
864+ const statOfResultDir = common . statFollowLinks ( `${ t . context . tmp } /preservedDir` ) ;
865+
866+ t . is ( result . code , 0 ) ;
867+
868+ // Both original file and original dir should be unchanged:
869+ t . is ( statDir . mtime . getTime ( ) , newModifyTimeMs ) ;
870+ t . is ( stat . mtime . getTime ( ) , newModifyTimeMs ) ;
871+ // cp appears to update the atime, but only of the srcFile & srcDir
872+ t . is ( stat . mode . toString ( 8 ) , '100' + mode ) ;
873+
874+ // Both new file and new dir should keep same attributes
875+ t . is ( statOfResultDir . mtime . getTime ( ) , newModifyTimeMs ) ;
876+ t . is ( statOfResultDir . atime . getTime ( ) , newAccessTimeMs ) ;
877+ t . is ( statOfResult . mtime . getTime ( ) , newModifyTimeMs ) ;
878+ t . is ( statOfResult . atime . getTime ( ) , newAccessTimeMs ) ;
879+ t . is ( statOfResult . mode . toString ( 8 ) , '100' + mode ) ;
880+
881+ t . is ( stat . uid , statOfResult . uid ) ;
882+ t . is ( stat . gid , statOfResult . gid ) ;
883+ } ) ;
884+
885+ test ( 'cp -p should preserve mode, ownership, and timestamp (symlink)' , t => {
886+ // Skip in Windows because symlinks require elevated permissions.
887+ utils . skipOnWin ( t , ( ) => {
888+ // Setup: copy to srcFile, create srcLink, and modify mode and timestamp
889+ shell . cp ( 'test/resources/cp/file1' , `${ t . context . tmp } /srcFile` ) ;
890+ const srcLink = `${ t . context . tmp } /srcLink` ;
891+ shell . ln ( '-s' , 'srcFile' , `${ t . context . tmp } /srcLink` ) ;
892+ // Make this a round number of seconds, since the underlying system may not
893+ // have millisecond precision.
894+ const newModifyTimeMs = 12345000 ;
895+ const newAccessTimeMs = 67890000 ;
896+ shell . touch ( { '-d' : new Date ( newModifyTimeMs ) , '-m' : true } , srcLink ) ;
897+ shell . touch ( { '-d' : new Date ( newAccessTimeMs ) , '-a' : true } , srcLink ) ;
898+ const mode = '444' ;
899+ shell . chmod ( mode , srcLink ) ;
900+
901+ // Now re-copy with '-p' and verify metadata.
902+ const result = shell . cp ( '-p' , srcLink , `${ t . context . tmp } /preservedLink` ) ;
903+ const stat = common . statFollowLinks ( srcLink ) ;
904+ const statOfResult = common . statFollowLinks ( `${ t . context . tmp } /preservedLink` ) ;
905+
906+ t . is ( result . code , 0 ) ;
907+
908+ // Original file should be unchanged:
909+ t . is ( stat . mtime . getTime ( ) , newModifyTimeMs ) ;
910+ // cp appears to update the atime, but only of the srcFile
911+ t . is ( stat . mode . toString ( 8 ) , '100' + mode ) ;
912+
913+ // New file should keep same attributes
914+ t . is ( statOfResult . mtime . getTime ( ) , newModifyTimeMs ) ;
915+ t . is ( statOfResult . atime . getTime ( ) , newAccessTimeMs ) ;
916+ t . is ( statOfResult . mode . toString ( 8 ) , '100' + mode ) ;
917+
918+ t . is ( stat . uid , statOfResult . uid ) ;
919+ t . is ( stat . gid , statOfResult . gid ) ;
920+ } ) ;
921+ } ) ;
0 commit comments