@@ -35,7 +35,8 @@ class TestFileManager : XCTestCase {
3535 ( " test_homedirectoryForUser " , test_homedirectoryForUser) ,
3636 ( " test_temporaryDirectoryForUser " , test_temporaryDirectoryForUser) ,
3737 ( " test_creatingDirectoryWithShortIntermediatePath " , test_creatingDirectoryWithShortIntermediatePath) ,
38- ( " test_mountedVolumeURLs " , test_mountedVolumeURLs)
38+ ( " test_mountedVolumeURLs " , test_mountedVolumeURLs) ,
39+ ( " test_contentsEqual " , test_contentsEqual)
3940 ]
4041 }
4142
@@ -690,4 +691,136 @@ class TestFileManager : XCTestCase {
690691 XCTAssertTrue ( visibleVolumes. count < volumes. count)
691692#endif
692693 }
694+
695+ func test_contentsEqual( ) {
696+ let fm = FileManager . default
697+ let tmpParentDirURL = URL ( fileURLWithPath: NSTemporaryDirectory ( ) + " test_contentsEqualdir " , isDirectory: true )
698+ let testDir1 = tmpParentDirURL. appendingPathComponent ( " testDir1 " )
699+ let testDir2 = tmpParentDirURL. appendingPathComponent ( " testDir2 " )
700+ let testDir3 = testDir1. appendingPathComponent ( " subDir/anotherDir/extraDir/lastDir " )
701+
702+ defer { try ? fm. removeItem ( atPath: tmpParentDirURL. path) }
703+
704+ func testFileURL( _ name: String , _ ext: String ) -> URL ? {
705+ guard let url = testBundle ( ) . url ( forResource: name, withExtension: ext) else {
706+ XCTFail ( " Cant open \( name) . \( ext) " )
707+ return nil
708+ }
709+ return url
710+ }
711+
712+ guard let testFile1URL = testFileURL ( " NSStringTestData " , " txt " ) else { return }
713+ guard let testFile2URL = testFileURL ( " NSURLTestData " , " plist " ) else { return }
714+ guard let testFile3URL = testFileURL ( " NSString-UTF32-BE-data " , " txt " ) else { return }
715+ guard let testFile4URL = testFileURL ( " NSString-UTF32-LE-data " , " txt " ) else { return }
716+ let symlink = testDir1. appendingPathComponent ( " testlink " ) . path
717+
718+ // Setup test directories
719+ do {
720+ // Clean out and leftover test data
721+ try ? fm. removeItem ( atPath: tmpParentDirURL. path)
722+
723+ // testDir1
724+ try fm. createDirectory ( atPath: testDir1. path, withIntermediateDirectories: true )
725+ try fm. createSymbolicLink ( atPath: testDir1. appendingPathComponent ( " null1 " ) . path, withDestinationPath: " /dev/null " )
726+ try fm. createSymbolicLink ( atPath: testDir1. appendingPathComponent ( " zero1 " ) . path, withDestinationPath: " /dev/zero " )
727+ try " foo " . write ( toFile: testDir1. appendingPathComponent ( " foo.txt " ) . path, atomically: false , encoding: . ascii)
728+ try fm. createSymbolicLink ( atPath: testDir1. appendingPathComponent ( " foo1 " ) . path, withDestinationPath: " foo.txt " )
729+ try fm. createSymbolicLink ( atPath: testDir1. appendingPathComponent ( " bar2 " ) . path, withDestinationPath: " foo1 " )
730+ let unreadable = testDir1. appendingPathComponent ( " unreadable_file " ) . path
731+ try " unreadable " . write ( toFile: unreadable, atomically: false , encoding: . ascii)
732+ try fm. setAttributes ( [ . posixPermissions: NSNumber ( value: 0 ) ] , ofItemAtPath: unreadable)
733+ try Data ( ) . write ( to: testDir1. appendingPathComponent ( " empty_file " ) )
734+ try fm. createSymbolicLink ( atPath: symlink, withDestinationPath: testFile1URL. path)
735+
736+ try fm. createSymbolicLink ( atPath: testDir1. appendingPathComponent ( " thisDir " ) . path, withDestinationPath: " . " )
737+ try fm. createSymbolicLink ( atPath: testDir1. appendingPathComponent ( " parentDir " ) . path, withDestinationPath: " .. " )
738+ try fm. createSymbolicLink ( atPath: testDir1. appendingPathComponent ( " rootDir " ) . path, withDestinationPath: " / " )
739+
740+ // testDir2
741+ try fm. createDirectory ( atPath: testDir2. path, withIntermediateDirectories: true )
742+ try fm. createSymbolicLink ( atPath: testDir2. appendingPathComponent ( " bar2 " ) . path, withDestinationPath: " foo1 " )
743+ try fm. createSymbolicLink ( atPath: testDir2. appendingPathComponent ( " foo2 " ) . path, withDestinationPath: " ../testDir1/foo.txt " )
744+
745+ // testDir3
746+ try fm. createDirectory ( atPath: testDir3. path, withIntermediateDirectories: true )
747+ try fm. createSymbolicLink ( atPath: testDir3. appendingPathComponent ( " bar2 " ) . path, withDestinationPath: " foo1 " )
748+ try fm. createSymbolicLink ( atPath: testDir3. appendingPathComponent ( " foo2 " ) . path, withDestinationPath: " ../testDir1/foo.txt " )
749+ } catch {
750+ XCTFail ( String ( describing: error) )
751+ }
752+
753+ XCTAssertTrue ( fm. contentsEqual ( atPath: " /dev/null " , andPath: " /dev/null " ) )
754+ XCTAssertTrue ( fm. contentsEqual ( atPath: " /dev/urandom " , andPath: " /dev/urandom " ) )
755+ XCTAssertFalse ( fm. contentsEqual ( atPath: " /dev/null " , andPath: " /dev/zero " ) )
756+ XCTAssertFalse ( fm. contentsEqual ( atPath: testDir1. appendingPathComponent ( " null1 " ) . path, andPath: " /dev/null " ) )
757+ XCTAssertFalse ( fm. contentsEqual ( atPath: testDir1. appendingPathComponent ( " zero " ) . path, andPath: " /dev/zero " ) )
758+ XCTAssertFalse ( fm. contentsEqual ( atPath: testDir1. appendingPathComponent ( " foo.txt " ) . path, andPath: testDir1. appendingPathComponent ( " foo1 " ) . path) )
759+ XCTAssertFalse ( fm. contentsEqual ( atPath: testDir1. appendingPathComponent ( " foo.txt " ) . path, andPath: testDir1. appendingPathComponent ( " foo2 " ) . path) )
760+ XCTAssertTrue ( fm. contentsEqual ( atPath: testDir1. appendingPathComponent ( " bar2 " ) . path, andPath: testDir2. appendingPathComponent ( " bar2 " ) . path) )
761+ XCTAssertFalse ( fm. contentsEqual ( atPath: testDir1. appendingPathComponent ( " foo1 " ) . path, andPath: testDir2. appendingPathComponent ( " foo2 " ) . path) )
762+ XCTAssertFalse ( fm. contentsEqual ( atPath: " /non_existant_file " , andPath: " /non_existant_file " ) )
763+
764+ let emptyFile = testDir1. appendingPathComponent ( " empty_file " )
765+ XCTAssertFalse ( fm. contentsEqual ( atPath: emptyFile. path, andPath: " /dev/null " ) )
766+ XCTAssertFalse ( fm. contentsEqual ( atPath: emptyFile. path, andPath: testDir1. appendingPathComponent ( " null1 " ) . path) )
767+ XCTAssertFalse ( fm. contentsEqual ( atPath: emptyFile. path, andPath: testDir1. appendingPathComponent ( " unreadable_file " ) . path) )
768+
769+ XCTAssertTrue ( fm. contentsEqual ( atPath: testFile1URL. path, andPath: testFile1URL. path) )
770+ XCTAssertFalse ( fm. contentsEqual ( atPath: testFile1URL. path, andPath: testFile2URL. path) )
771+ XCTAssertFalse ( fm. contentsEqual ( atPath: testFile3URL. path, andPath: testFile4URL. path) )
772+ XCTAssertFalse ( fm. contentsEqual ( atPath: symlink, andPath: testFile1URL. path) )
773+
774+ XCTAssertTrue ( fm. contentsEqual ( atPath: testDir1. path, andPath: testDir1. path) )
775+ XCTAssertTrue ( fm. contentsEqual ( atPath: testDir2. path, andPath: testDir3. path) )
776+ XCTAssertFalse ( fm. contentsEqual ( atPath: testDir1. path, andPath: testDir2. path) )
777+
778+ // Copy everything in testDir1 to testDir2 to make them equal
779+ do {
780+ for entry in try fm. subpathsOfDirectory ( atPath: testDir1. path) {
781+ // Skip entries that already exist
782+ if entry == " bar2 " || entry == " unreadable_file " {
783+ continue
784+ }
785+ let srcPath = testDir1. appendingPathComponent ( entry) . path
786+ let dstPath = testDir2. appendingPathComponent ( entry) . path
787+ if let attrs = try ? fm. attributesOfItem ( atPath: srcPath) ,
788+ let fileType = attrs [ . type] as? FileAttributeType , fileType == . typeDirectory {
789+ try fm. createDirectory ( atPath: dstPath, withIntermediateDirectories: false , attributes: nil )
790+ } else {
791+ try fm. copyItem ( atPath: srcPath, toPath: dstPath)
792+ }
793+ }
794+ } catch {
795+ XCTFail ( " Failed to copy \( testDir1. path) to \( testDir2. path) , \( error) " )
796+ return
797+ }
798+ // This will still fail due to unreadable files and a file in testDir2 not in testDir1
799+ XCTAssertFalse ( fm. contentsEqual ( atPath: testDir1. path, andPath: testDir2. path) )
800+ do {
801+ try fm. copyItem ( atPath: testDir2. appendingPathComponent ( " foo2 " ) . path, toPath: testDir1. appendingPathComponent ( " foo2 " ) . path)
802+ try fm. removeItem ( atPath: testDir1. appendingPathComponent ( " unreadable_file " ) . path)
803+ } catch {
804+ XCTFail ( String ( describing: error) )
805+ return
806+ }
807+ XCTAssertTrue ( fm. contentsEqual ( atPath: testDir1. path, andPath: testDir2. path) )
808+
809+ let dataFile1 = testDir1. appendingPathComponent ( " dataFile " )
810+ let dataFile2 = testDir2. appendingPathComponent ( " dataFile " )
811+ do {
812+ try Data ( count: 100_000 ) . write ( to: dataFile1)
813+ try fm. copyItem ( atPath: dataFile1. path, toPath: dataFile2. path)
814+ } catch {
815+ XCTFail ( " Could not create test data files: \( error) " )
816+ return
817+ }
818+ XCTAssertTrue ( fm. contentsEqual ( atPath: dataFile1. path, andPath: dataFile2. path) )
819+ XCTAssertTrue ( fm. contentsEqual ( atPath: testDir1. path, andPath: testDir2. path) )
820+ var data = Data ( count: 100_000 )
821+ data [ 99_999 ] = 1
822+ try ? data. write ( to: dataFile1)
823+ XCTAssertFalse ( fm. contentsEqual ( atPath: dataFile1. path, andPath: dataFile2. path) )
824+ XCTAssertFalse ( fm. contentsEqual ( atPath: testDir1. path, andPath: testDir2. path) )
825+ }
693826}
0 commit comments