// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//

#if NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT
    #if canImport(SwiftFoundation) && !DEPLOYMENT_RUNTIME_OBJC
        @testable import SwiftFoundation
    #else
        @testable import Foundation
    #endif
#endif

class TestFileManager : XCTestCase {
#if os(Windows)
    let pathSep = "\\"
#else
    let pathSep = "/"
#endif

    func test_createDirectory() {
        let fm = FileManager.default
        let path = NSTemporaryDirectory() + "testdir\(NSUUID().uuidString)"
        
        try? fm.removeItem(atPath: path)
        
        do {
            try fm.createDirectory(atPath: path, withIntermediateDirectories: false, attributes: nil)
        } catch {
            XCTFail()
        }

        // Ensure attempting to create the directory again fails gracefully.
        XCTAssertNil(try? fm.createDirectory(atPath: path, withIntermediateDirectories:false, attributes:nil))

        var isDir: ObjCBool = false
        let exists = fm.fileExists(atPath: path, isDirectory: &isDir)
        XCTAssertTrue(exists)
        XCTAssertTrue(isDir.boolValue)

        do {
            try fm.removeItem(atPath: path)
        } catch {
            XCTFail("Failed to clean up file")
        }
        
    }
    
    func test_createFile() {
        let fm = FileManager.default
        let path = NSTemporaryDirectory() + "testfile\(NSUUID().uuidString)"
        
        try? fm.removeItem(atPath: path)
        
        XCTAssertTrue(fm.createFile(atPath: path, contents: Data(), attributes: nil))
        
        var isDir: ObjCBool = false
        let exists = fm.fileExists(atPath: path, isDirectory: &isDir)
        XCTAssertTrue(exists)
        XCTAssertFalse(isDir.boolValue)
        
        do {
            try fm.removeItem(atPath: path)
        } catch {
            XCTFail("Failed to clean up file")
        }

#if os(Windows)
        let permissions = NSNumber(value: Int16(0o700))
#else
        let permissions = NSNumber(value: Int16(0o753))
#endif
        let attributes = [FileAttributeKey.posixPermissions: permissions]
        XCTAssertTrue(fm.createFile(atPath: path, contents: Data(),
                                    attributes: attributes))
        guard let retrievedAtributes = try? fm.attributesOfItem(atPath: path) else {
            XCTFail("Failed to retrieve file attributes from created file")
            return
        }

        XCTAssertTrue(retrievedAtributes.contains(where: { (attribute) -> Bool in
            guard let attributeValue = attribute.value as? NSNumber else {
                return false
            }
            return (attribute.key == .posixPermissions)
                && (attributeValue == permissions)
        }))

        do {
            try fm.removeItem(atPath: path)
        } catch {
            XCTFail("Failed to clean up file")
        }
    }

    func test_creatingDirectoryWithShortIntermediatePath() {
        let fileManager = FileManager.default
        fileManager.changeCurrentDirectoryPath(NSTemporaryDirectory())

        let relativePath = NSUUID().uuidString

        do {
            try fileManager.createDirectory(atPath: relativePath, withIntermediateDirectories: true, attributes: nil)
            try fileManager.removeItem(atPath: relativePath)
        } catch {
            XCTFail("Failed to create and clean up directory")
        }
    }

    func test_moveFile() {
        let fm = FileManager.default
        let path = NSTemporaryDirectory() + "testfile\(NSUUID().uuidString)"
        let path2 = NSTemporaryDirectory() + "testfile2\(NSUUID().uuidString)"

        func cleanup() {
            try? fm.removeItem(atPath: path)
            try? fm.removeItem(atPath: path2)
        }

        cleanup()

        XCTAssertTrue(fm.createFile(atPath: path, contents: Data(), attributes: nil))
        defer { cleanup() }

        do {
            try fm.moveItem(atPath: path, toPath: path2)
        } catch {
            XCTFail("Failed to move file: \(error)")
        }
    }

    func test_fileSystemRepresentation() {
        let str = "☃"
        let result = FileManager.default.fileSystemRepresentation(withPath: str)
        XCTAssertEqual(UInt8(bitPattern: result[0]), 0xE2)
        XCTAssertEqual(UInt8(bitPattern: result[1]), 0x98)
        XCTAssertEqual(UInt8(bitPattern: result[2]), 0x83)

#if !DARWIN_COMPATIBILITY_TESTS // auto-released by Darwin's Foundation
        result.deallocate()
#endif
    }

    func test_fileExists() {
        let fm = FileManager.default
        let tmpDir = fm.temporaryDirectory.appendingPathComponent("testFileExistsDir")
        let testFile = tmpDir.appendingPathComponent("testFile")
        let goodSymLink = tmpDir.appendingPathComponent("goodSymLink")
        let badSymLink = tmpDir.appendingPathComponent("badSymLink")
        let dirSymLink = tmpDir.appendingPathComponent("dirSymlink")

        try? fm.removeItem(atPath: tmpDir.path)

        do {
            try fm.createDirectory(atPath: tmpDir.path, withIntermediateDirectories: false, attributes: nil)
            XCTAssertTrue(fm.createFile(atPath: testFile.path, contents: Data()))
            try fm.createSymbolicLink(atPath: goodSymLink.path, withDestinationPath: testFile.path)
#if os(Windows)
            // Creating a broken symlink is expected to fail on Windows
            XCTAssertNil(try? fm.createSymbolicLink(atPath: badSymLink.path, withDestinationPath: "no_such_file"))
#else
            try fm.createSymbolicLink(atPath: badSymLink.path, withDestinationPath: "no_such_file")
#endif
            try fm.createSymbolicLink(atPath: dirSymLink.path, withDestinationPath: "..")

            var isDirFlag: ObjCBool = false
            XCTAssertTrue(fm.fileExists(atPath: tmpDir.path))
            XCTAssertTrue(fm.fileExists(atPath: tmpDir.path, isDirectory: &isDirFlag))
            XCTAssertTrue(isDirFlag.boolValue)

            isDirFlag = true
            XCTAssertTrue(fm.fileExists(atPath: testFile.path))
            XCTAssertTrue(fm.fileExists(atPath: testFile.path, isDirectory: &isDirFlag))
            XCTAssertFalse(isDirFlag.boolValue)

            isDirFlag = true
            XCTAssertTrue(fm.fileExists(atPath: goodSymLink.path))
            XCTAssertTrue(fm.fileExists(atPath: goodSymLink.path, isDirectory: &isDirFlag))
            XCTAssertFalse(isDirFlag.boolValue)

            isDirFlag = true
            XCTAssertFalse(fm.fileExists(atPath: badSymLink.path))
            XCTAssertFalse(fm.fileExists(atPath: badSymLink.path, isDirectory: &isDirFlag))

            isDirFlag = false
            XCTAssertTrue(fm.fileExists(atPath: dirSymLink.path))
            XCTAssertTrue(fm.fileExists(atPath: dirSymLink.path, isDirectory: &isDirFlag))
            XCTAssertTrue(isDirFlag.boolValue)
        } catch {
            XCTFail(String(describing: error))
        }
        try? fm.removeItem(atPath: tmpDir.path)
    }

    func test_isReadableFile() {
        let fm = FileManager.default
        let path = NSTemporaryDirectory() + "test_isReadableFile\(NSUUID().uuidString)"

        do {
            // create test file
            XCTAssertTrue(fm.createFile(atPath: path, contents: Data()))

            // test unReadable if file has no permissions
            try fm.setAttributes([.posixPermissions : NSNumber(value: Int16(0o0000))], ofItemAtPath: path)
#if os(Windows)
            // Files are always readable on Windows
            XCTAssertTrue(fm.isReadableFile(atPath: path))
#else
            XCTAssertFalse(fm.isReadableFile(atPath: path))
#endif

            // test readable if file has read permissions
            try fm.setAttributes([.posixPermissions : NSNumber(value: Int16(0o0400))], ofItemAtPath: path)
            XCTAssertTrue(fm.isReadableFile(atPath: path))
        } catch let e {
            XCTFail("\(e)")
        }
    }

    func test_isWritableFile() {
        let fm = FileManager.default
        let path = NSTemporaryDirectory() + "test_isWritableFile\(NSUUID().uuidString)"

        do {
            // create test file
            XCTAssertTrue(fm.createFile(atPath: path, contents: Data()))

            // test unWritable if file has no permissions
            try fm.setAttributes([.posixPermissions : NSNumber(value: Int16(0o0000))], ofItemAtPath: path)
            XCTAssertFalse(fm.isWritableFile(atPath: path))

            // test writable if file has write permissions
            try fm.setAttributes([.posixPermissions : NSNumber(value: Int16(0o0200))], ofItemAtPath: path)
            XCTAssertTrue(fm.isWritableFile(atPath: path))
        } catch let e {
            XCTFail("\(e)")
        }
    }

    func test_isExecutableFile() {
        let fm = FileManager.default
        let path = NSTemporaryDirectory() + "test_isExecutableFile\(NSUUID().uuidString)"

        do {
            // create test file
            XCTAssertTrue(fm.createFile(atPath: path, contents: Data()))

            // test unExecutable if file has no permissions
            try fm.setAttributes([.posixPermissions : NSNumber(value: Int16(0o0000))], ofItemAtPath: path)
#if os(Windows)
            // Files are always executable on Windows
            XCTAssertTrue(fm.isExecutableFile(atPath: path))
#else
            XCTAssertFalse(fm.isExecutableFile(atPath: path))
#endif

            // test executable if file has execute permissions
            try fm.setAttributes([.posixPermissions : NSNumber(value: Int16(0o0100))], ofItemAtPath: path)
            XCTAssertTrue(fm.isExecutableFile(atPath: path))
        } catch let e {
            XCTFail("\(e)")
        }
    }

    func test_isDeletableFile() {
        let fm = FileManager.default

        do {
            let dir_path = NSTemporaryDirectory() + "/test_isDeletableFile_dir/"
            let file_path = dir_path + "test_isDeletableFile\(NSUUID().uuidString)"
            // create test directory
            try fm.createDirectory(atPath: dir_path, withIntermediateDirectories: true)
            // create test file
            XCTAssertTrue(fm.createFile(atPath: file_path, contents: Data()))

            // test undeletable if parent directory has no permissions
            try fm.setAttributes([.posixPermissions : NSNumber(value: Int16(0o0000))], ofItemAtPath: dir_path)
            XCTAssertFalse(fm.isDeletableFile(atPath: file_path))

            // test deletable if parent directory has all necessary permissions
            try fm.setAttributes([.posixPermissions : NSNumber(value: Int16(0o0755))], ofItemAtPath: dir_path)
            XCTAssertTrue(fm.isDeletableFile(atPath: file_path))
        }
        catch { XCTFail("\(error)") }

        // test against known undeletable file
        XCTAssertFalse(fm.isDeletableFile(atPath: "/dev/null"))
    }

    func test_fileAttributes() throws {
        let fm = FileManager.default
        let path = NSTemporaryDirectory() + "test_fileAttributes\(NSUUID().uuidString)"

        try? fm.removeItem(atPath: path)
        
        XCTAssertTrue(fm.createFile(atPath: path, contents: Data(), attributes: nil))
        
        do {
            let attrs = try fm.attributesOfItem(atPath: path)
            
            XCTAssertTrue(attrs.count > 0)
            
            let fileSize = attrs[.size] as? NSNumber
            XCTAssertEqual(fileSize!.int64Value, 0)
            
            let fileModificationDate = attrs[.modificationDate] as? Date
            XCTAssertGreaterThan(Date().timeIntervalSince1970, fileModificationDate!.timeIntervalSince1970)
            
            let filePosixPermissions = attrs[.posixPermissions] as? NSNumber
            XCTAssertNotEqual(filePosixPermissions!.int64Value, 0)
            
            let fileReferenceCount = attrs[.referenceCount] as? NSNumber
            XCTAssertEqual(fileReferenceCount!.int64Value, 1)
            
            let fileSystemNumber = attrs[.systemNumber] as? NSNumber
            XCTAssertNotEqual(fileSystemNumber!.int64Value, 0)
            
#if !os(Windows)
            let fileSystemFileNumber = attrs[.systemFileNumber] as? NSNumber
            XCTAssertNotEqual(fileSystemFileNumber!.int64Value, 0)
#endif
            
            let fileType = attrs[.type] as? FileAttributeType
            XCTAssertEqual(fileType!, .typeRegular)
            
            let fileOwnerAccountID = attrs[.ownerAccountID] as? NSNumber
            XCTAssertNotNil(fileOwnerAccountID)
            
            let fileGroupOwnerAccountID = attrs[.groupOwnerAccountID] as? NSNumber
            XCTAssertNotNil(fileGroupOwnerAccountID)

#if os(Linux)
            /* ⚠️ */
            if shouldAttemptXFailTests("Checking that .creationDate is set is failing on Ubuntu 16.04 when running from a Docker image. https://bugs.swift.org/browse/SR-10512") {
                let requiredVersion = OperatingSystemVersion(majorVersion: 4, minorVersion: 11, patchVersion: 0)
                let creationDate = attrs[.creationDate] as? Date
                if ProcessInfo.processInfo.isOperatingSystemAtLeast(requiredVersion) {
                    XCTAssertNotNil(creationDate)
                    XCTAssertGreaterThan(Date().timeIntervalSince1970, try XCTUnwrap(creationDate).timeIntervalSince1970)
                } else {
                    XCTAssertNil(creationDate)
                }
            }
            /* ⚠️ */ 
#endif
            
            if let fileOwnerAccountName = attrs[.ownerAccountName] {
                XCTAssertNotNil(fileOwnerAccountName as? String)
                if let fileOwnerAccountNameStr = fileOwnerAccountName as? String {
                    XCTAssertFalse(fileOwnerAccountNameStr.isEmpty)
                }
            }
            
            if let fileGroupOwnerAccountName = attrs[.groupOwnerAccountName] {
                XCTAssertNotNil(fileGroupOwnerAccountName as? String)
                if let fileGroupOwnerAccountNameStr = fileGroupOwnerAccountName as? String {
                    XCTAssertFalse(fileGroupOwnerAccountNameStr.isEmpty)
                }
            }
            
        } catch {
            XCTFail("\(error)")
        }
        
        do {
            try fm.removeItem(atPath: path)
        } catch {
            XCTFail("Failed to clean up files")
        }
    }
    
    func test_fileSystemAttributes() {
        let fm = FileManager.default
        let path = NSTemporaryDirectory()
        
        do {
            let attrs = try fm.attributesOfFileSystem(forPath: path)
            
            XCTAssertTrue(attrs.count > 0)
            
            let systemNumber = attrs[.systemNumber] as? NSNumber
            XCTAssertNotNil(systemNumber)
            
            let systemFreeSize = attrs[.systemFreeSize] as? NSNumber
            XCTAssertNotNil(systemFreeSize)
            XCTAssertNotEqual(systemFreeSize!.uint64Value, 0)
            
            let systemSize = attrs[.systemSize] as? NSNumber
            XCTAssertNotNil(systemSize)
            XCTAssertGreaterThan(systemSize!.uint64Value, systemFreeSize!.uint64Value)

            if shouldAttemptWindowsXFailTests("FileAttributes[.systemFreeNodes], FileAttributes[.systemNodes] not implemented") {
              let systemFreeNodes = attrs[.systemFreeNodes] as? NSNumber
              XCTAssertNotNil(systemFreeNodes)
              XCTAssertNotEqual(systemFreeNodes!.uint64Value, 0)

              let systemNodes = attrs[.systemNodes] as? NSNumber
              XCTAssertNotNil(systemNodes)
              XCTAssertGreaterThan(systemNodes!.uint64Value, systemFreeNodes!.uint64Value)
            }
        } catch {
            XCTFail("\(error)")
        }
    }
    
    func test_setFileAttributes() {
        let path = NSTemporaryDirectory() + "test_setFileAttributes\(NSUUID().uuidString)"
        let fm = FileManager.default
        
        try? fm.removeItem(atPath: path)
        XCTAssertTrue(fm.createFile(atPath: path, contents: Data(), attributes: nil))
        
        do {
            try fm.setAttributes([.posixPermissions : NSNumber(value: Int16(0o0600))], ofItemAtPath: path)
        }
        catch { XCTFail("\(error)") }
        
        //read back the attributes
        do {
            let attributes = try fm.attributesOfItem(atPath: path)
#if os(Windows)
            XCTAssert((attributes[.posixPermissions] as? NSNumber)?.int16Value == 0o0700)
#else
            XCTAssert((attributes[.posixPermissions] as? NSNumber)?.int16Value == 0o0600)
#endif
        }
        catch { XCTFail("\(error)") }

        do {
            try fm.removeItem(atPath: path)
        } catch {
            XCTFail("Failed to clean up files")
        }

        // test non existent file
        let noSuchFile = NSTemporaryDirectory() + "fileThatDoesntExist"
        try? fm.removeItem(atPath: noSuchFile)
        do {
            try fm.setAttributes([.posixPermissions: 0], ofItemAtPath: noSuchFile)
            XCTFail("Setting permissions of non-existent file should throw")
        } catch {
        }
    }
    
    func test_pathEnumerator() {
        let fm = FileManager.default
        let testDirName = "testdir\(NSUUID().uuidString)"
        let basePath = NSTemporaryDirectory() + "\(testDirName)"
        let itemPath = NSTemporaryDirectory() + "\(testDirName)/item"
        let basePath2 = NSTemporaryDirectory() + "\(testDirName)/path2"
        let itemPath2 = NSTemporaryDirectory() + "\(testDirName)/path2/item"
        
        try? fm.removeItem(atPath: basePath)
        
        do {
            try fm.createDirectory(atPath: basePath, withIntermediateDirectories: false, attributes: nil)
            try fm.createDirectory(atPath: basePath2, withIntermediateDirectories: false, attributes: nil)

            let _ = fm.createFile(atPath: itemPath, contents: Data(count: 123), attributes: nil)
            let _ = fm.createFile(atPath: itemPath2, contents: Data(count: 456), attributes: nil)

        } catch {
            XCTFail()
        }

        var item1FileAttributes: [FileAttributeKey: Any]!
        var item2FileAttributes: [FileAttributeKey: Any]!
        if let e = FileManager.default.enumerator(atPath: basePath) {
            let attrs = e.directoryAttributes
            XCTAssertNotNil(attrs)
            XCTAssertEqual(attrs?[.type] as? FileAttributeType, .typeDirectory)

            var foundItems = Set<String>()
            while let item = e.nextObject() as? String {
                foundItems.insert(item)
                if item == "item" {
                    item1FileAttributes = e.fileAttributes
                } else if item == "path2\(pathSep)item" {
                    item2FileAttributes = e.fileAttributes
                }
            }
            XCTAssertEqual(foundItems, Set(["item", "path2", "path2\(pathSep)item"]))
        } else {
            XCTFail()
        }

        XCTAssertNotNil(item1FileAttributes)
        if let size = item1FileAttributes[.size] as? NSNumber {
            XCTAssertEqual(size.int64Value, 123)
        } else {
            XCTFail("Cant get file size for 'item'")
        }

        XCTAssertNotNil(item2FileAttributes)
        if let size = item2FileAttributes[.size] as? NSNumber {
            XCTAssertEqual(size.int64Value, 456)
        } else {
            XCTFail("Cant get file size for 'path2/item'")
        }

        if let e2 = FileManager.default.enumerator(atPath: basePath) {
            var foundItems = Set<String>()
            while let item = e2.nextObject() as? String {
                foundItems.insert(item)
                if item == "path2" {
                    e2.skipDescendants()
                    XCTAssertEqual(e2.level, 1)
                    XCTAssertNotNil(e2.fileAttributes)
                }
            }
            XCTAssertEqual(foundItems, Set(["item", "path2"]))
        } else {
            XCTFail()
        }
    }
    
    func test_directoryEnumerator() {
        let fm = FileManager.default
        let basePath = NSTemporaryDirectory() + "testdir\(NSUUID().uuidString)/"
        let hiddenDir1 = basePath + "subdir1/subdir2/.hiddenDir/"
        let subDirs1 = hiddenDir1 + "subdir3/"
        let itemPath1 = basePath + "itemFile1"
#if os(Windows)
        // Filenames ending with '.' are not valid on Windows, so don't bother testing them
        let hiddenDir2 = basePath + "subdir1/subdir2/subdir4.app/subdir5/.subdir6.ext/"
        let subDirs2 = hiddenDir2 + "subdir7.ext/"
        let itemPath2 = subDirs1 + "itemFile2"
        let itemPath3 = subDirs1 + "itemFile3.ext"
#else
        let hiddenDir2 = basePath + "subdir1/subdir2/subdir4.app/subdir5./.subdir6.ext/"
        let subDirs2 = hiddenDir2 + "subdir7.ext./"
        let itemPath2 = subDirs1 + "itemFile2."
        let itemPath3 = subDirs1 + "itemFile3.ext."
#endif
        let hiddenItem1 = basePath + ".hiddenFile1"
        let hiddenItem2 = subDirs1 + ".hiddenFile2"
        let hiddenItem3 = subDirs2 + ".hiddenFile3"
        let hiddenItem4 = subDirs2 + ".hiddenFile4.ext"

        var fileLevels: [String: Int] = [
            "itemFile1": 1,
            ".hiddenFile1": 1,
            "subdir1": 1,
            "subdir2": 2,
            "subdir4.app": 3,
            ".subdir6.ext": 5,
            ".hiddenFile4.ext": 7,
            ".hiddenFile3": 7,
            ".hiddenDir": 3,
            "subdir3": 4,
            ".hiddenFile2": 5,
        ]
#if os(Windows)
        fileLevels["itemFile2"] = 5
        fileLevels["subdir5"] = 4
        fileLevels["subdir7.ext"] = 6
        fileLevels["itemFile3.ext"] = 5
#else
        fileLevels["itemFile2."] = 5
        fileLevels["subdir5."] = 4
        fileLevels["subdir7.ext."] = 6
        fileLevels["itemFile3.ext."] = 5
#endif

        func directoryItems(options: FileManager.DirectoryEnumerationOptions) -> [String: Int]? {
            if let e = FileManager.default.enumerator(at: URL(fileURLWithPath: basePath), includingPropertiesForKeys: nil, options: options, errorHandler: nil) {
                var foundItems = [String:Int]()
                while let item = e.nextObject() as? URL {
                    foundItems[item.lastPathComponent] = e.level
                }
                return foundItems
            } else {
                return nil
            }
        }

        try? fm.removeItem(atPath: basePath)
        defer { try? fm.removeItem(atPath: basePath) }

        XCTAssertNotNil(try? fm.createDirectory(atPath: subDirs1, withIntermediateDirectories: true, attributes: nil))
        XCTAssertNotNil(try? fm.createDirectory(atPath: subDirs2, withIntermediateDirectories: true, attributes: nil))
        for filename in [itemPath1, itemPath2, itemPath3] {
            XCTAssertTrue(fm.createFile(atPath: filename, contents: Data(), attributes: nil), "Cant create file '\(filename)'")
        }

        var resourceValues = URLResourceValues()
        resourceValues.isHidden = true
        for filename in [ hiddenItem1, hiddenItem2, hiddenItem3, hiddenItem4] {
            XCTAssertTrue(fm.createFile(atPath: filename, contents: Data(), attributes: nil), "Cant create file '\(filename)'")
#if os(Windows)
            do {
                var url = URL(fileURLWithPath: filename)
                try url.setResourceValues(resourceValues)
            } catch {
                XCTFail("Couldn't make \(filename) a hidden file")
            }
#endif
        }

#if os(Windows)
        do {
            var hiddenURL1 = URL(fileURLWithPath: hiddenDir1)
            var hiddenURL2 = URL(fileURLWithPath: hiddenDir2)
            try hiddenURL1.setResourceValues(resourceValues)
            try hiddenURL2.setResourceValues(resourceValues)
        } catch {
            XCTFail("Couldn't make \(hiddenDir1) and \(hiddenDir2) hidden directories")
        }
#endif

        if let foundItems = directoryItems(options: []) {
            XCTAssertEqual(foundItems.count, fileLevels.count)
            for (name, level) in foundItems {
                XCTAssertEqual(fileLevels[name], level, "File level for \(name) is wrong")
            }
        } else {
            XCTFail("Cant enumerate directory at \(basePath) with options: []")
        }

        if let foundItems = directoryItems(options: [.skipsHiddenFiles]) {
            XCTAssertEqual(foundItems.count, 5)
        } else {
            XCTFail("Cant enumerate directory at \(basePath) with options: [.skipsHiddenFiles]")
        }

        if let foundItems = directoryItems(options: [.skipsSubdirectoryDescendants]) {
            XCTAssertEqual(foundItems.count, 3)
        } else {
            XCTFail("Cant enumerate directory at \(basePath) with options: [.skipsSubdirectoryDescendants]")
        }

        if let foundItems = directoryItems(options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants]) {
            XCTAssertEqual(foundItems.count, 2)
        } else {
            XCTFail("Cant enumerate directory at \(basePath) with options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants]")
        }

        if let foundItems = directoryItems(options: [.skipsPackageDescendants]) {
#if DARWIN_COMPATIBILITY_TESTS
            XCTAssertEqual(foundItems.count, 10)    // Only native Foundation does not gnore .skipsPackageDescendants
#else
            XCTAssertEqual(foundItems.count, 15)
#endif
        } else {
            XCTFail("Cant enumerate directory at \(basePath) with options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants]")
        }

        var didGetError = false
        let handler : (URL, Error) -> Bool = { (URL, Error) in
            didGetError = true
            return true
        }
        if let e = FileManager.default.enumerator(at: URL(fileURLWithPath: "/nonexistent-path"), includingPropertiesForKeys: nil, options: [], errorHandler: handler) {
            XCTAssertNil(e.nextObject())
        } else {
            XCTFail()
        }
        XCTAssertTrue(didGetError)
        
        do {
            let contents = try FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: basePath), includingPropertiesForKeys: nil, options: []).map {
                return $0.path
            }
            XCTAssertEqual(contents.count, 3)
        } catch {
            XCTFail()
        }
    }
    
    func test_contentsOfDirectoryAtPath() {
        let fm = FileManager.default
        let testDirName = "testdir\(NSUUID().uuidString)"
        let path = NSTemporaryDirectory() + "\(testDirName)"
        let itemPath1 = NSTemporaryDirectory() + "\(testDirName)/item"
        let itemPath2 = NSTemporaryDirectory() + "\(testDirName)/item2"
        
        try? fm.removeItem(atPath: path)
        
        do {
            try fm.createDirectory(atPath: path, withIntermediateDirectories: false, attributes: nil)
            let _ = fm.createFile(atPath: itemPath1, contents: Data(), attributes: nil)
            let _ = fm.createFile(atPath: itemPath2, contents: Data(), attributes: nil)
        } catch {
            XCTFail()
        }
        
        do {
            let entries = try fm.contentsOfDirectory(atPath: path)
            
            XCTAssertEqual(2, entries.count)
            XCTAssertTrue(entries.contains("item"))
            XCTAssertTrue(entries.contains("item2"))
        }
        catch {
            XCTFail()
        }
        
        do {
            // Check a bad path fails
            let _ = try fm.contentsOfDirectory(atPath: "/...")
            XCTFail()
        }
        catch {
            // Invalid directories should fail.
        }
        
        do {
            try fm.removeItem(atPath: path)
        } catch {
            XCTFail("Failed to clean up files")
        }
    }
    
    func test_subpathsOfDirectoryAtPath() {
        let fm = FileManager.default
        let path = NSTemporaryDirectory() + "testdir"
        let path2 = NSTemporaryDirectory() + "testdir/sub"
        let itemPath1 = NSTemporaryDirectory() + "testdir/item"
        let itemPath2 = NSTemporaryDirectory() + "testdir/item2"
        let itemPath3 = NSTemporaryDirectory() + "testdir/sub/item3"
                
        try? fm.removeItem(atPath: path)
        
        do {
            try fm.createDirectory(atPath: path, withIntermediateDirectories: false, attributes: nil)
            let _ = fm.createFile(atPath: itemPath1, contents: Data(), attributes: nil)
            let _ = fm.createFile(atPath: itemPath2, contents: Data(), attributes: nil)
            
            try fm.createDirectory(atPath: path2, withIntermediateDirectories: false, attributes: nil)
            let _ = fm.createFile(atPath: itemPath3, contents: Data(), attributes: nil)
        } catch {
            XCTFail()
        }
        
        do {
            let entries = try fm.subpathsOfDirectory(atPath: path)
            
            XCTAssertEqual(4, entries.count)
            XCTAssertTrue(entries.contains("item"))
            XCTAssertTrue(entries.contains("item2"))
            XCTAssertTrue(entries.contains("sub"))
            XCTAssertTrue(entries.contains("sub/item3"))
            XCTAssertEqual(fm.subpaths(atPath: path), entries)
        }
        catch {
            XCTFail()
        }
        
        do {
            // Check a bad path fails
            XCTAssertNil(fm.subpaths(atPath: "/..."))

            let _ = try fm.subpathsOfDirectory(atPath: "/...")
            XCTFail()
        }
        catch {
            // Invalid directories should fail.
        }
        
        do {
            try fm.removeItem(atPath: path)
        } catch {
            XCTFail("Failed to clean up files")
        }
    }

    private func directoryExists(atPath path: String) -> Bool {
        var isDir: ObjCBool = false
        let exists = FileManager.default.fileExists(atPath: path, isDirectory: &isDir)
        return exists && isDir.boolValue
    }

    func test_copyItemAtPathToPath() {
        let fm = FileManager.default
        let srcPath = NSTemporaryDirectory() + "testdir\(NSUUID().uuidString)"
        let destPath = NSTemporaryDirectory() + "testdir\(NSUUID().uuidString)"

        func cleanup() {
            try? fm.removeItem(atPath: srcPath)
            try? fm.removeItem(atPath: destPath)
        }

        func createDirectory(atPath path: String) {
            do {
                try fm.createDirectory(atPath: path, withIntermediateDirectories: false, attributes: nil)
            } catch {
                XCTFail("Unable to create directory: \(error)")
            }
            XCTAssertTrue(directoryExists(atPath: path))
        }
        
        func createFile(atPath path: String) {
            XCTAssertTrue(fm.createFile(atPath: path, contents: Data(), attributes: nil))
        }
        
        cleanup()
        createFile(atPath: srcPath)
        do {
            try fm.copyItem(atPath: srcPath, toPath: destPath)
        } catch {
            XCTFail("Failed to copy file: \(error)")
        }

        cleanup()
        createDirectory(atPath: srcPath)
        createDirectory(atPath: "\(srcPath)/tempdir")
        createDirectory(atPath: "\(srcPath)/tempdir/subdir")
        createDirectory(atPath: "\(srcPath)/tempdir/subdir/otherdir")
        createDirectory(atPath: "\(srcPath)/tempdir/subdir/otherdir/extradir")
        createFile(atPath: "\(srcPath)/tempdir/tempfile")
        createFile(atPath: "\(srcPath)/tempdir/tempfile2")
        createFile(atPath: "\(srcPath)/tempdir/subdir/otherdir/extradir/tempfile2")

        do {
            try fm.copyItem(atPath: srcPath, toPath: destPath)
        } catch {
            XCTFail("Unable to copy directory: \(error)")
        }
        XCTAssertTrue(directoryExists(atPath: destPath))
        XCTAssertTrue(directoryExists(atPath: "\(destPath)/tempdir"))
        XCTAssertTrue(fm.fileExists(atPath: "\(destPath)/tempdir/tempfile"))
        XCTAssertTrue(fm.fileExists(atPath: "\(destPath)/tempdir/tempfile2"))
        XCTAssertTrue(directoryExists(atPath: "\(destPath)/tempdir/subdir/otherdir/extradir"))
        XCTAssertTrue(fm.fileExists(atPath: "\(destPath)/tempdir/subdir/otherdir/extradir/tempfile2"))

        if (false == directoryExists(atPath: destPath)) {
            return
        }
        do {
            try fm.copyItem(atPath: srcPath, toPath: destPath)
            XCTFail("Copy overwrites a file/folder that already exists")
        } catch {
            // ignore
        }

        // Test copying a symlink
        let srcLink = srcPath + "/testlink"
        let destLink = destPath + "/testlink"
        do {
#if os(Windows)
            fm.createFile(atPath: srcPath.appendingPathComponent("linkdest"), contents: Data(), attributes: nil)
#endif
            try fm.createSymbolicLink(atPath: srcLink, withDestinationPath: "linkdest")
            try fm.copyItem(atPath: srcLink, toPath: destLink)
            XCTAssertEqual(try fm.destinationOfSymbolicLink(atPath: destLink), "linkdest")
        } catch {
            XCTFail("\(error)")
        }

        do {
            try fm.copyItem(atPath: srcLink, toPath: destLink)
            XCTFail("Creating link where one already exists")
        } catch {
            // ignore
        }
    }

    func test_linkItemAtPathToPath() {
        let fm = FileManager.default
        let basePath = NSTemporaryDirectory() + "linkItemAtPathToPath/"
        let srcPath = basePath + "testdir\(NSUUID().uuidString)"
        let destPath = basePath + "testdir\(NSUUID().uuidString)"
        defer { try? fm.removeItem(atPath: basePath) }

        func getFileInfo(atPath path: String, _ body: (String, Bool, UInt64, UInt64) -> ()) {
            guard let enumerator = fm.enumerator(atPath: path) else {
                XCTFail("Cant enumerate \(path)")
                return
            }
            while let item = enumerator.nextObject() as? String {
                let fname = "\(path)/\(item)"
                do {
                    let attrs = try fm.attributesOfItem(atPath: fname)
                    let inode = (attrs[.systemFileNumber] as? NSNumber)?.uint64Value
                    let linkCount = (attrs[.referenceCount] as? NSNumber)?.uint64Value
                    let ftype = attrs[.type] as? FileAttributeType

                    if inode == nil || linkCount == nil || ftype == nil {
                        XCTFail("Unable to get attributes of \(fname)")
                        return
                    }
                    let isDir = (ftype == .typeDirectory)
                    body(item, isDir, inode!, linkCount!)
                } catch {
                    XCTFail("Unable to get attributes of \(fname): \(error)")
                    return
                }
            }
        }

        try? fm.removeItem(atPath: basePath)
        XCTAssertNotNil(try? fm.createDirectory(atPath: "\(srcPath)/tempdir/subdir/otherdir/extradir", withIntermediateDirectories: true, attributes: nil))
        XCTAssertTrue(fm.createFile(atPath: "\(srcPath)/tempdir/tempfile", contents: Data(), attributes: nil))
        XCTAssertTrue(fm.createFile(atPath: "\(srcPath)/tempdir/tempfile2", contents: Data(), attributes: nil))
        XCTAssertTrue(fm.createFile(atPath: "\(srcPath)/tempdir/subdir/otherdir/extradir/tempfile2", contents: Data(), attributes: nil))

        var fileInfos: [String: (Bool, UInt64, UInt64)] = [:]
        getFileInfo(atPath: srcPath, { name, isDir, inode, linkCount in
            fileInfos[name] = (isDir, inode, linkCount)
        })
        XCTAssertEqual(fileInfos.count, 7)
        XCTAssertNotNil(try? fm.linkItem(atPath: srcPath, toPath: destPath), "Unable to link directory")

        getFileInfo(atPath: destPath, { name, isDir, inode, linkCount in
            guard let srcFileInfo = fileInfos.removeValue(forKey: name) else {
                XCTFail("Cant find \(name) in \(destPath)")
                return
            }
            let (srcIsDir, srcInode, srcLinkCount) = srcFileInfo
            XCTAssertEqual(srcIsDir, isDir, "Directory/File type mismatch")
            if isDir {
                XCTAssertEqual(srcLinkCount, linkCount)
            } else {
                XCTAssertEqual(srcInode, inode)
                XCTAssertEqual(srcLinkCount + 1, linkCount)
            }
        })

        XCTAssertEqual(fileInfos.count, 0)
        // linkItem should fail a 2nd time
        XCTAssertNil(try? fm.linkItem(atPath: srcPath, toPath: destPath), "Copy overwrites a file/folder that already exists")

        // Test 'linking' a symlink, which actually does a copy
        let srcLink = srcPath + "/testlink"
        let destLink = destPath + "/testlink"
        do {
#if os(Windows)
            fm.createFile(atPath: srcPath.appendingPathComponent("linkdest"), contents: Data(), attributes: nil)
#endif
            try fm.createSymbolicLink(atPath: srcLink, withDestinationPath: "linkdest")
            try fm.linkItem(atPath: srcLink, toPath: destLink)
            XCTAssertEqual(try fm.destinationOfSymbolicLink(atPath: destLink), "linkdest")
        } catch {
            XCTFail("\(error)")
        }
        XCTAssertNil(try? fm.linkItem(atPath: srcLink, toPath: destLink), "Creating link where one already exists")
    }

    func test_homedirectoryForUser() {
        let filemanger = FileManager.default
        XCTAssertNil(filemanger.homeDirectory(forUser: "someuser"))
        XCTAssertNil(filemanger.homeDirectory(forUser: ""))
        XCTAssertNotNil(filemanger.homeDirectoryForCurrentUser)
    }
    
    func test_temporaryDirectoryForUser() {
        let filemanger = FileManager.default
        let tmpDir = filemanger.temporaryDirectory
        let tmpFileUrl = tmpDir.appendingPathComponent("test.bin")
        let tmpFilePath = tmpFileUrl.path
        
        do {
            if filemanger.fileExists(atPath: tmpFilePath) {
                try filemanger.removeItem(at: tmpFileUrl)
            }
            
            try "hello world".write(to: tmpFileUrl, atomically: false, encoding: .utf8)
            XCTAssert(filemanger.fileExists(atPath: tmpFilePath))

            try filemanger.removeItem(at: tmpFileUrl)
        } catch {
            XCTFail("Unable to write a file to the temporary directory: \(tmpDir), err: \(error)")
        }
    }

    func test_mountedVolumeURLs() {
        guard let volumes = FileManager.default.mountedVolumeURLs(includingResourceValuesForKeys:[], options: []) else {
            XCTFail("mountedVolumeURLs returned nil")
            return
        }
        XCTAssertNotEqual(0, volumes.count)
        XCTAssertTrue(volumes.contains(URL(fileURLWithPath: "/")))
#if os(macOS)
        // On macOS, .skipHiddenVolumes should hide 'nobrowse' volumes of which there should be at least one
        guard let visibleVolumes = FileManager.default.mountedVolumeURLs(includingResourceValuesForKeys: [], options: [.skipHiddenVolumes]) else {
            XCTFail("mountedVolumeURLs returned nil")
            return
        }
        XCTAssertTrue(visibleVolumes.count > 0)
        XCTAssertTrue(visibleVolumes.count < volumes.count)
#endif
    }

    func test_contentsEqual() {
        let fm = FileManager.default
        let tmpParentDirURL = URL(fileURLWithPath: NSTemporaryDirectory() + "test_contentsEqualdir", isDirectory: true)
        let testDir1 = tmpParentDirURL.appendingPathComponent("testDir1")
        let testDir2 = tmpParentDirURL.appendingPathComponent("testDir2")
        let testDir3 = testDir1.appendingPathComponent("subDir/anotherDir/extraDir/lastDir")

        defer { try? fm.removeItem(atPath: tmpParentDirURL.path) }

        func testFileURL(_ name: String, _ ext: String) -> URL? {
            guard let url = testBundle().url(forResource: name, withExtension: ext) else {
                XCTFail("Cant open \(name).\(ext)")
                return nil
            }
            return url
        }

        guard let testFile1URL = testFileURL("NSStringTestData", "txt") else { return }
        guard let testFile2URL = testFileURL("NSURLTestData", "plist")  else { return }
        guard let testFile3URL = testFileURL("NSString-UTF32-BE-data", "txt") else { return }
        guard let testFile4URL = testFileURL("NSString-UTF32-LE-data", "txt") else { return }
        let symlink = testDir1.appendingPathComponent("testlink").path

        // Setup test directories
        do {
            // Clean out and leftover test data
            try? fm.removeItem(atPath: tmpParentDirURL.path)

            // testDir1
            try fm.createDirectory(atPath: testDir1.path, withIntermediateDirectories: true)
#if !os(Windows)
            try fm.createSymbolicLink(atPath: testDir1.appendingPathComponent("null1").path, withDestinationPath: "/dev/null")
            try fm.createSymbolicLink(atPath: testDir1.appendingPathComponent("zero1").path, withDestinationPath: "/dev/zero")
#endif
            try "foo".write(toFile: testDir1.appendingPathComponent("foo.txt").path, atomically: false, encoding: .ascii)
            try fm.createSymbolicLink(atPath: testDir1.appendingPathComponent("foo1").path, withDestinationPath: "foo.txt")
            try fm.createSymbolicLink(atPath: testDir1.appendingPathComponent("bar2").path, withDestinationPath: "foo1")
            let unreadable = testDir1.appendingPathComponent("unreadable_file").path
            try "unreadable".write(toFile: unreadable, atomically: false, encoding: .ascii)
            try fm.setAttributes([.posixPermissions: NSNumber(value: 0)], ofItemAtPath: unreadable)
            try Data().write(to: testDir1.appendingPathComponent("empty_file"))
            try fm.createSymbolicLink(atPath: symlink, withDestinationPath: testFile1URL.path)

            try fm.createSymbolicLink(atPath: testDir1.appendingPathComponent("thisDir").path, withDestinationPath: ".")
            try fm.createSymbolicLink(atPath: testDir1.appendingPathComponent("parentDir").path, withDestinationPath: "..")
            try fm.createSymbolicLink(atPath: testDir1.appendingPathComponent("rootDir").path, withDestinationPath: "/")

            // testDir2
            try fm.createDirectory(atPath: testDir2.path, withIntermediateDirectories: true)
#if os(Windows)
            try "foo".write(toFile: testDir2.appendingPathComponent("foo1").path, atomically: false, encoding: .ascii)
            try fm.createDirectory(atPath: testDir2.appendingPathComponent("../testDir1").path, withIntermediateDirectories: true)
            try "foo".write(toFile: testDir2.appendingPathComponent("../testDir1/foo.txt").path, atomically: false, encoding: .ascii)
#endif
            try fm.createSymbolicLink(atPath: testDir2.appendingPathComponent("bar2").path, withDestinationPath: "foo1")
            try fm.createSymbolicLink(atPath: testDir2.appendingPathComponent("foo2").path, withDestinationPath: "../testDir1/foo.txt")

            // testDir3
            try fm.createDirectory(atPath: testDir3.path, withIntermediateDirectories: true)
#if os(Windows)
            try fm.createDirectory(atPath: testDir3.appendingPathComponent("../testDir1").path, withIntermediateDirectories: true)
            try "foo".write(toFile: testDir3.appendingPathComponent("../testDir1/foo.txt").path, atomically: false, encoding: .ascii)
            try "foo".write(toFile: testDir3.appendingPathComponent("foo1").path, atomically: false, encoding: .ascii)
#endif
            try fm.createSymbolicLink(atPath: testDir3.appendingPathComponent("bar2").path, withDestinationPath: "foo1")
            try fm.createSymbolicLink(atPath: testDir3.appendingPathComponent("foo2").path, withDestinationPath: "../testDir1/foo.txt")
        } catch {
            XCTFail(String(describing: error))
        }

#if os(Windows)
        XCTAssertFalse(fm.contentsEqual(atPath: "NUL", andPath: "NUL"))
#else
        XCTAssertTrue(fm.contentsEqual(atPath: "/dev/null", andPath: "/dev/null"))
        XCTAssertTrue(fm.contentsEqual(atPath: "/dev/urandom", andPath: "/dev/urandom"))
        XCTAssertFalse(fm.contentsEqual(atPath: "/dev/null", andPath: "/dev/zero"))
        XCTAssertFalse(fm.contentsEqual(atPath: testDir1.appendingPathComponent("null1").path, andPath: "/dev/null"))
        XCTAssertFalse(fm.contentsEqual(atPath: testDir1.appendingPathComponent("zero").path, andPath: "/dev/zero"))
#endif
        XCTAssertFalse(fm.contentsEqual(atPath: testDir1.appendingPathComponent("foo.txt").path, andPath: testDir1.appendingPathComponent("foo1").path))
        XCTAssertFalse(fm.contentsEqual(atPath: testDir1.appendingPathComponent("foo.txt").path, andPath: testDir1.appendingPathComponent("foo2").path))
        XCTAssertTrue(fm.contentsEqual(atPath: testDir1.appendingPathComponent("bar2").path, andPath: testDir2.appendingPathComponent("bar2").path))
        XCTAssertFalse(fm.contentsEqual(atPath: testDir1.appendingPathComponent("foo1").path, andPath: testDir2.appendingPathComponent("foo2").path))
        XCTAssertFalse(fm.contentsEqual(atPath: "/non_existent_file", andPath: "/non_existent_file"))

        let emptyFile = testDir1.appendingPathComponent("empty_file")
#if os(Windows)
        XCTAssertFalse(fm.contentsEqual(atPath: emptyFile.path, andPath: "NUL"))
#else
        XCTAssertFalse(fm.contentsEqual(atPath: emptyFile.path, andPath: "/dev/null"))
#endif
        XCTAssertFalse(fm.contentsEqual(atPath: emptyFile.path, andPath: testDir1.appendingPathComponent("null1").path))
#if os(Windows)
        // A file cannot be unreadable on Windows
        XCTAssertTrue(fm.contentsEqual(atPath: emptyFile.path, andPath: testDir1.appendingPathComponent("unreadable_file").path))
#else
        XCTAssertFalse(fm.contentsEqual(atPath: emptyFile.path, andPath: testDir1.appendingPathComponent("unreadable_file").path))
#endif

        XCTAssertTrue(fm.contentsEqual(atPath: testFile1URL.path, andPath: testFile1URL.path))
        XCTAssertFalse(fm.contentsEqual(atPath: testFile1URL.path, andPath: testFile2URL.path))
        XCTAssertFalse(fm.contentsEqual(atPath: testFile3URL.path, andPath: testFile4URL.path))
        XCTAssertFalse(fm.contentsEqual(atPath: symlink, andPath: testFile1URL.path))

        XCTAssertTrue(fm.contentsEqual(atPath: testDir1.path, andPath: testDir1.path))
        XCTAssertTrue(fm.contentsEqual(atPath: testDir2.path, andPath: testDir3.path))
        XCTAssertFalse(fm.contentsEqual(atPath: testDir1.path, andPath: testDir2.path))

        // Copy everything in testDir1 to testDir2 to make them equal
        do {
            for entry in try fm.subpathsOfDirectory(atPath: testDir1.path) {
                // Skip entries that already exist
                if entry == "bar2" || entry == "unreadable_file" {
                    continue
                }
                let srcPath = testDir1.appendingPathComponent(entry).path
                let dstPath = testDir2.appendingPathComponent(entry).path
                if let attrs = try? fm.attributesOfItem(atPath: srcPath),
                    let fileType = attrs[.type] as? FileAttributeType, fileType == .typeDirectory {
                    try fm.createDirectory(atPath: dstPath, withIntermediateDirectories: false, attributes: nil)
                } else {
                    try fm.copyItem(atPath: srcPath, toPath: dstPath)
                }
            }
        } catch {
            XCTFail("Failed to copy \(testDir1.path) to \(testDir2.path), \(error)")
            return
        }
        // This will still fail due to unreadable files and a file in testDir2 not in testDir1
        XCTAssertFalse(fm.contentsEqual(atPath: testDir1.path, andPath: testDir2.path))
        do {
            try fm.copyItem(atPath: testDir2.appendingPathComponent("foo2").path, toPath: testDir1.appendingPathComponent("foo2").path)
            try fm.removeItem(atPath: testDir1.appendingPathComponent("unreadable_file").path)
        } catch {
            XCTFail(String(describing: error))
            return
        }
        XCTAssertTrue(fm.contentsEqual(atPath: testDir1.path, andPath: testDir2.path))

        let dataFile1 = testDir1.appendingPathComponent("dataFile")
        let dataFile2 = testDir2.appendingPathComponent("dataFile")
        do {
            try Data(count: 100_000).write(to: dataFile1)
            try fm.copyItem(atPath: dataFile1.path, toPath: dataFile2.path)
        } catch {
            XCTFail("Could not create test data files: \(error)")
            return
        }
        XCTAssertTrue(fm.contentsEqual(atPath: dataFile1.path, andPath: dataFile2.path))
        XCTAssertTrue(fm.contentsEqual(atPath: testDir1.path, andPath: testDir2.path))
        var data = Data(count: 100_000)
        data[99_999] = 1
        try? data.write(to: dataFile1)
        XCTAssertFalse(fm.contentsEqual(atPath: dataFile1.path, andPath: dataFile2.path))
        XCTAssertFalse(fm.contentsEqual(atPath: testDir1.path, andPath: testDir2.path))
    }

    func test_copyItemsPermissions() throws {
        let fm = FileManager.default
        let tmpDir = fm.temporaryDirectory.appendingPathComponent("test_copyItemsPermissions")
        try fm.createDirectory(at: tmpDir, withIntermediateDirectories: true)
        defer { try? fm.removeItem(atPath: tmpDir.path) }

        let srcFile = tmpDir.appendingPathComponent("file1.txt")
        let destFile = tmpDir.appendingPathComponent("file2.txt")

        let source = "This is the source file"
        try? fm.removeItem(at: srcFile)
        try source.write(toFile: srcFile.path, atomically: false, encoding: .utf8)

        func testCopy() throws {
            try? fm.removeItem(at: destFile)
            try fm.copyItem(at: srcFile, to: destFile)
            let copy = try String(contentsOf: destFile)
            XCTAssertEqual(source, copy)
            if let srcPerms = (try fm.attributesOfItem(atPath: srcFile.path)[.posixPermissions] as? NSNumber)?.intValue,
                let destPerms = (try fm.attributesOfItem(atPath: destFile.path)[.posixPermissions] as? NSNumber)?.intValue {
                XCTAssertEqual(srcPerms, destPerms)
            } else {
                XCTFail("Cant get file permissions")
            }
        }

        try testCopy()

        try fm.setAttributes([ .posixPermissions: 0o417], ofItemAtPath: srcFile.path)
        try testCopy()

        try fm.setAttributes([ .posixPermissions: 0o400], ofItemAtPath: srcFile.path)
        try testCopy()

        try fm.setAttributes([ .posixPermissions: 0o700], ofItemAtPath: srcFile.path)
        try testCopy()

        try fm.setAttributes([ .posixPermissions: 0o707], ofItemAtPath: srcFile.path)
        try testCopy()

        try fm.setAttributes([ .posixPermissions: 0o411], ofItemAtPath: srcFile.path)
        try testCopy()
    }
    
#if !DEPLOYMENT_RUNTIME_OBJC && !os(Android) // XDG tests require swift-corelibs-foundation
    
    #if NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT // These are white box tests for the internals of XDG parsing:
    func test_xdgStopgapsCoverAllConstants() {
        let stopgaps = _XDGUserDirectory.stopgapDefaultDirectoryURLs
        for directory in _XDGUserDirectory.allDirectories {
            XCTAssertNotNil(stopgaps[directory])
        }
    }
    
    func test_parseXDGConfiguration() {
        let home = URL(fileURLWithPath: NSHomeDirectory(), isDirectory: true)
        
        let assertConfigurationProduces = { (configuration: String, paths: [_XDGUserDirectory: String]) in
            XCTAssertEqual(_XDGUserDirectory.userDirectories(fromConfiguration: configuration).mapValues({ $0.absoluteURL.path }),
                           paths.mapValues({ URL(fileURLWithPath: $0, isDirectory: true, relativeTo: home).absoluteURL.path }))
        }
        
        assertConfigurationProduces("", [:])
        
        // Test partial configuration and paths relative to home.
        assertConfigurationProduces(
"""
DESKTOP=/xdg_test/Desktop
MUSIC=/xdg_test/Music
PICTURES=Pictures
""", [ .desktop: "/xdg_test/Desktop",
       .music: "/xdg_test/Music",
       .pictures: "Pictures" ])

        // Test full configuration with XDG_…_DIR syntax, duplicate keys and varying indentation
        // 'XDG_MUSIC_DIR' is duplicated, below.
        assertConfigurationProduces(
"""
	XDG_MUSIC_DIR=ShouldNotBeUsedUseTheOneBelowInstead

	XDG_DESKTOP_DIR=Desktop
		XDG_DOWNLOAD_DIR=Download
	XDG_PUBLICSHARE_DIR=Public
XDG_DOCUMENTS_DIR=Documents
	XDG_MUSIC_DIR=Music
XDG_PICTURES_DIR=Pictures
	XDG_VIDEOS_DIR=Videos
""", [ .desktop: "Desktop",
       .download: "Download",
       .publicShare: "Public",
       .documents: "Documents",
       .music: "Music",
       .pictures: "Pictures",
       .videos: "Videos" ])
        
        // Same, without XDG…DIR.
        assertConfigurationProduces(
"""
    MUSIC=ShouldNotBeUsedUseTheOneBelowInstead

    DESKTOP=Desktop
        DOWNLOAD=Download
    PUBLICSHARE=Public
DOCUMENTS=Documents
    MUSIC=Music
PICTURES=Pictures
    VIDEOS=Videos
""", [ .desktop: "Desktop",
       .download: "Download",
       .publicShare: "Public",
       .documents: "Documents",
       .music: "Music",
       .pictures: "Pictures",
       .videos: "Videos" ])
    
        assertConfigurationProduces(
"""
    DESKTOP=/home/Desktop
This configuration file has an invalid syntax.
""", [:])
    }
    
    func test_xdgURLSelection() {
        let home = URL(fileURLWithPath: NSHomeDirectory(), isDirectory: true)
        
        let configuration = _XDGUserDirectory.userDirectories(fromConfiguration:
"""
DESKTOP=UserDesktop
"""
        )
        
        let osDefaults = _XDGUserDirectory.userDirectories(fromConfiguration:
"""
DESKTOP=SystemDesktop
PUBLICSHARE=SystemPublicShare
"""
        )
        
        let stopgaps = _XDGUserDirectory.userDirectories(fromConfiguration:
"""
DESKTOP=StopgapDesktop
DOWNLOAD=StopgapDownload
PUBLICSHARE=StopgapPublicShare
DOCUMENTS=StopgapDocuments
MUSIC=StopgapMusic
PICTURES=StopgapPictures
VIDEOS=StopgapVideos
"""
        )
        
        let assertSameAbsolutePath = { (lhs: URL, rhs: URL) in
            XCTAssertEqual(lhs.absoluteURL.path, rhs.absoluteURL.path)
        }
        
        assertSameAbsolutePath(_XDGUserDirectory.desktop.url(userConfiguration: configuration, osDefaultConfiguration: osDefaults, stopgaps: stopgaps), home.appendingPathComponent("UserDesktop"))
        assertSameAbsolutePath(_XDGUserDirectory.publicShare.url(userConfiguration: configuration, osDefaultConfiguration: osDefaults, stopgaps: stopgaps), home.appendingPathComponent("SystemPublicShare"))
        assertSameAbsolutePath(_XDGUserDirectory.music.url(userConfiguration: configuration, osDefaultConfiguration: osDefaults, stopgaps: stopgaps), home.appendingPathComponent("StopgapMusic"))
    }
    #endif // NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT
    
    // This test below is a black box test, and does not require @testable import.

    #if !os(Android)
    func printPathByRunningHelper(withConfiguration config: String, method: String, identifier: String) throws -> String {
        let uuid = UUID().uuidString
        let path = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("org.swift.Foundation.XDGTestHelper").appendingPathComponent(uuid)
        try FileManager.default.createDirectory(at: path, withIntermediateDirectories: true)
        
        let configFilePath = path.appendingPathComponent("user-dirs.dirs")
        try config.write(to: configFilePath, atomically: true, encoding: .utf8)
        defer {
            try? FileManager.default.removeItem(at: path)
        }
        
        var environment = [ "XDG_CONFIG_HOME": path.path,
                            "_NSFileManagerUseXDGPathsForDirectoryDomains": "YES" ]
        
        // Copy all LD_* and DYLD_* variables over, in case we're running with altered paths (e.g. from ninja test on Linux)
        for entry in ProcessInfo.processInfo.environment.lazy.filter({ $0.key.hasPrefix("DYLD_") || $0.key.hasPrefix("LD_") }) {
            environment[entry.key] = entry.value
        }
        
        let helper = xdgTestHelperURL()
        let (stdout, _) = try runTask([ helper.path, "--nspathfor", method, identifier ],
                                      environment: environment)
        
        return stdout.trimmingCharacters(in: CharacterSet.newlines)
    }
    
    func assertFetchingPath(withConfiguration config: String, identifier: String, yields path: String) {
        for method in [ "NSSearchPath", "FileManagerDotURLFor", "FileManagerDotURLsFor" ] {
            do {
                let found = try printPathByRunningHelper(withConfiguration: config, method: method, identifier: identifier)
                XCTAssertEqual(found, path)
            } catch let error {
                XCTFail("Failed with method \(method), configuration \(config), identifier \(identifier), equal to \(path), error \(error)")
            }
        }
    }
    
    func test_fetchXDGPathsFromHelper() {
        let prefix = NSHomeDirectory() + "/_Foundation_Test_"
        
        let configuration = """
        DESKTOP=\(prefix)/Desktop
        DOWNLOAD=\(prefix)/Download
        PUBLICSHARE=\(prefix)/PublicShare
        DOCUMENTS=\(prefix)/Documents
        MUSIC=\(prefix)/Music
        PICTURES=\(prefix)/Pictures
        VIDEOS=\(prefix)/Videos
        """
        
        assertFetchingPath(withConfiguration: configuration, identifier: "desktop", yields: "\(prefix)/Desktop")
        assertFetchingPath(withConfiguration: configuration, identifier: "download", yields: "\(prefix)/Download")
        assertFetchingPath(withConfiguration: configuration, identifier: "publicShare", yields: "\(prefix)/PublicShare")
        assertFetchingPath(withConfiguration: configuration, identifier: "documents", yields: "\(prefix)/Documents")
        assertFetchingPath(withConfiguration: configuration, identifier: "music", yields: "\(prefix)/Music")
        assertFetchingPath(withConfiguration: configuration, identifier: "pictures", yields: "\(prefix)/Pictures")
        assertFetchingPath(withConfiguration: configuration, identifier: "videos", yields: "\(prefix)/Videos")
    }
    #endif // !os(Android)
#endif // !DEPLOYMENT_RUNTIME_OBJC

    func test_emptyFilename() {

        // Some of these tests will throw an NSException on Darwin which would be normally be
        // modelled by a fatalError() or other hard failure, however since most of these functions
        // are thorwable, an NSError is thrown instead which is more useful.
        let fm = FileManager.default

        XCTAssertNil(fm.homeDirectory(forUser: ""))
        XCTAssertNil(NSHomeDirectoryForUser(""))

        XCTAssertThrowsError(try fm.contentsOfDirectory(atPath: "")) {
            let code = CocoaError.Code(rawValue: ($0 as? NSError)!.code)
            XCTAssertEqual(code, .fileReadInvalidFileName)
        }

        XCTAssertNil(fm.enumerator(atPath: ""))
        XCTAssertNil(fm.subpaths(atPath: ""))
        XCTAssertThrowsError(try fm.subpathsOfDirectory(atPath: "")) {
            let code = CocoaError.Code(rawValue: ($0 as? NSError)!.code)
            XCTAssertEqual(code, .fileReadInvalidFileName)
        }

        XCTAssertThrowsError(try fm.createDirectory(atPath: "", withIntermediateDirectories: true)) {
            let code = CocoaError.Code(rawValue: ($0 as? NSError)!.code)
            XCTAssertEqual(code, .fileReadInvalidFileName)
        }
        XCTAssertFalse(fm.createFile(atPath: "", contents: Data()))
        XCTAssertThrowsError(try fm.removeItem(atPath: "")) {
            let code = CocoaError.Code(rawValue: ($0 as? NSError)!.code)
            XCTAssertEqual(code, .fileReadInvalidFileName)
        }

        XCTAssertThrowsError(try fm.copyItem(atPath: "", toPath: "/tmp/t"))  {
            let code = CocoaError.Code(rawValue: ($0 as? NSError)!.code)
            XCTAssertEqual(code, .fileReadInvalidFileName)
        }
        XCTAssertThrowsError(try fm.copyItem(atPath: "", toPath: ""))  {
            let code = CocoaError.Code(rawValue: ($0 as? NSError)!.code)
            XCTAssertEqual(code, .fileReadInvalidFileName)
        }
        XCTAssertThrowsError(try fm.copyItem(atPath: "/tmp/t", toPath: "")) {
            let code = CocoaError.Code(rawValue: ($0 as? NSError)!.code)
            XCTAssertEqual(code, .fileReadNoSuchFile)
        }

        XCTAssertThrowsError(try fm.moveItem(atPath: "", toPath: "/tmp/t")) {
            let code = CocoaError.Code(rawValue: ($0 as? NSError)!.code)
            XCTAssertEqual(code, .fileReadInvalidFileName)
        }
        XCTAssertThrowsError(try fm.moveItem(atPath: "", toPath: "")) {
            let code = CocoaError.Code(rawValue: ($0 as? NSError)!.code)
            XCTAssertEqual(code, .fileReadInvalidFileName)
        }
        XCTAssertThrowsError(try fm.moveItem(atPath: "/tmp/t", toPath: "")) {
            let code = CocoaError.Code(rawValue: ($0 as? NSError)!.code)
            XCTAssertEqual(code, .fileReadInvalidFileName)
        }

        XCTAssertThrowsError(try fm.linkItem(atPath: "", toPath: "/tmp/t")) {
            let code = CocoaError.Code(rawValue: ($0 as? NSError)!.code)
            XCTAssertEqual(code, .fileReadInvalidFileName)
        }
        XCTAssertThrowsError(try fm.linkItem(atPath: "", toPath: "")) {
            let code = CocoaError.Code(rawValue: ($0 as? NSError)!.code)
            XCTAssertEqual(code, .fileReadInvalidFileName)
        }
        XCTAssertThrowsError(try fm.linkItem(atPath: "/tmp/t", toPath: "")) {
            let code = CocoaError.Code(rawValue: ($0 as? NSError)!.code)
            XCTAssertEqual(code, .fileReadNoSuchFile)
        }

        XCTAssertThrowsError(try fm.createSymbolicLink(atPath: "", withDestinationPath: "")) {
            let code = CocoaError.Code(rawValue: ($0 as? NSError)!.code)
            XCTAssertEqual(code, .fileReadInvalidFileName)
        }
        XCTAssertThrowsError(try fm.createSymbolicLink(atPath: "", withDestinationPath: "/tmp/t")) {
            let code = CocoaError.Code(rawValue: ($0 as? NSError)!.code)
            XCTAssertEqual(code, .fileReadInvalidFileName)
        }
        XCTAssertThrowsError(try fm.createSymbolicLink(atPath: "/tmp/t", withDestinationPath: "")) {
            let code = CocoaError.Code(rawValue: ($0 as? NSError)!.code)
            XCTAssertEqual(code, .fileReadInvalidFileName)
        }

        XCTAssertThrowsError(try fm.destinationOfSymbolicLink(atPath: "")) {
            let code = CocoaError.Code(rawValue: ($0 as? NSError)!.code)
            XCTAssertEqual(code, .fileReadInvalidFileName)
        }
        XCTAssertFalse(fm.fileExists(atPath: ""))
        XCTAssertFalse(fm.fileExists(atPath: "", isDirectory: nil))
        XCTAssertFalse(fm.isReadableFile(atPath: ""))
        XCTAssertFalse(fm.isWritableFile(atPath: ""))
        XCTAssertFalse(fm.isExecutableFile(atPath: ""))
        XCTAssertTrue(fm.isDeletableFile(atPath: ""))

        XCTAssertThrowsError(try fm.attributesOfItem(atPath: "")) {
            let code = CocoaError.Code(rawValue: ($0 as? NSError)!.code)
            XCTAssertEqual(code, .fileReadInvalidFileName)
        }
        XCTAssertThrowsError(try fm.attributesOfFileSystem(forPath: "")) {
            let code = CocoaError.Code(rawValue: ($0 as? NSError)!.code)
            XCTAssertEqual(code, .fileReadInvalidFileName)
        }
        XCTAssertThrowsError(try fm.setAttributes([:], ofItemAtPath: "")) {
            let code = CocoaError.Code(rawValue: ($0 as? NSError)!.code)
            XCTAssertEqual(code, .fileReadInvalidFileName)
        }

        XCTAssertNil(fm.contents(atPath: ""))
        XCTAssertFalse(fm.contentsEqual(atPath: "", andPath: ""))
        XCTAssertFalse(fm.contentsEqual(atPath: "/tmp/t", andPath: ""))
        XCTAssertFalse(fm.contentsEqual(atPath: "", andPath: "/tmp/t"))

        //_ = fm.fileSystemRepresentation(withPath: "")  // NSException
        XCTAssertEqual(fm.string(withFileSystemRepresentation: UnsafePointer(bitPattern: 1)!, length: 0), "")

        XCTAssertFalse(fm.changeCurrentDirectoryPath(""))
        XCTAssertNotEqual(fm.currentDirectoryPath, "")

        // Not Implemented - XCTAssertNil(fm.componentsToDisplay(forPath: ""))
        // Not Implemented - XCTAssertEqual(fm.displayName(atPath: ""), "")
    }
    
    func test_getRelationship() throws {
        /* a/
           a/b
           a/bb
           c -> symlink to a/b
           d */
        
        let a        = writableTestDirectoryURL.appendingPathComponent("a")
        let a_b      = a.appendingPathComponent("b")
        let a_bb     = a.appendingPathComponent("bb")
        let c        = writableTestDirectoryURL.appendingPathComponent("c")
        let a_b_d    = a_b.appendingPathComponent("d")
        
        let fm = FileManager.default
        try fm.createDirectory(at: a, withIntermediateDirectories: true)
        try fm.createDirectory(at: a_b, withIntermediateDirectories: true)
        try Data().write(to: a_bb)
        try Data().write(to: c)
        try fm.createSymbolicLink(at: a_b_d, withDestinationURL: a)
        
        var relationship: FileManager.URLRelationship = .other
        
        try fm.getRelationship(&relationship, ofDirectoryAt: writableTestDirectoryURL, toItemAt: a)
        XCTAssertEqual(relationship, .contains)
        
        try fm.getRelationship(&relationship, ofDirectoryAt: a, toItemAt: a_b)
        XCTAssertEqual(relationship, .contains)
        
        // The path of one is a prefix to the other, but lacks the directory separator.
        try fm.getRelationship(&relationship, ofDirectoryAt: a_b, toItemAt: a_bb)
        XCTAssertEqual(relationship, .other)
        
        try fm.getRelationship(&relationship, ofDirectoryAt: a_b, toItemAt: c)
        XCTAssertEqual(relationship, .other)
        
        try fm.getRelationship(&relationship, ofDirectoryAt: a_b_d, toItemAt: a)
        XCTAssertEqual(relationship, .same)
    }
    
    func test_displayNames() throws {
        /* a/
           a/Test.localized (with a ./.localized/ subdirectory and strings file);
           a/Test.localized/b
        */
        
        let a = writableTestDirectoryURL.appendingPathComponent("a")
        let a_Test = a.appendingPathComponent("Test.localized")
        let a_Test_dotLocalized = a_Test.appendingPathComponent(".localized")
        let a_Test_dotLocalized_enStrings = a_Test_dotLocalized.appendingPathComponent("en.strings")
        let a_Test_dotLocalized_itStrings = a_Test_dotLocalized.appendingPathComponent("it.strings")
        let a_Test_b = a_Test.appendingPathComponent("b")
        
        let fm = FileManager.default
        try fm.createDirectory(at: a, withIntermediateDirectories: true)
        try fm.createDirectory(at: a_Test, withIntermediateDirectories: true)
        try fm.createDirectory(at: a_Test_dotLocalized, withIntermediateDirectories: true)
        try Data("\"Test\" = \"Test\";".utf8).write(to: a_Test_dotLocalized_enStrings)
        try Data("\"Test\" = \"Prova\";".utf8).write(to: a_Test_dotLocalized_itStrings)
        try fm.createDirectory(at: a_Test_b, withIntermediateDirectories: true)

        XCTAssertEqual(fm.displayName(atPath: a.path), "a")
        
        #if NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT
        fm._overridingDisplayNameLanguages(with: ["en", "es", "it"]) {
            XCTAssertEqual(fm.displayName(atPath: a_Test.path), "Test")
        }
        fm._overridingDisplayNameLanguages(with: ["it", "en", "es"]) {
            XCTAssertEqual(fm.displayName(atPath: a_Test.path), "Prova")
        }
        fm._overridingDisplayNameLanguages(with: ["es", "it", "en"]) {
            XCTAssertEqual(fm.displayName(atPath: a_Test.path), "Prova")
        }
        #endif
        
        do {
            let components = try XCTUnwrap(fm.componentsToDisplay(forPath: a.path))
            XCTAssertGreaterThanOrEqual(components.count, 2)
            XCTAssertEqual(components.last, "a")
        }
        
        do {
            #if NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT
            let components = try fm._overridingDisplayNameLanguages(with: ["it"]) {
                return try XCTUnwrap(fm.componentsToDisplay(forPath: a_Test.path))
            }
            #else
            let components = try XCTUnwrap(fm.componentsToDisplay(forPath: a_Test.path))
            #endif
            
            XCTAssertGreaterThanOrEqual(components.count, 3)
            XCTAssertEqual(components[components.count - 2], "a")
            
            #if NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT
            XCTAssertEqual(components.last, "Prova")
            #endif
        }
        
        do {
            #if NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT
            let components = try fm._overridingDisplayNameLanguages(with: ["en"]) {
                return try XCTUnwrap(fm.componentsToDisplay(forPath: a_Test_b.path))
            }
            #else
            let components = try XCTUnwrap(fm.componentsToDisplay(forPath: a_Test_b.path))
            #endif
            
            XCTAssertGreaterThanOrEqual(components.count, 4)
            XCTAssertEqual(components[components.count - 3], "a")
            
            #if NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT
            XCTAssertEqual(components[components.count - 2], "Test")
            #endif
            
            XCTAssertEqual(components.last, "b")
        }
        
    }
    
    func test_getItemReplacementDirectory() throws {
        try FileManager.default.createDirectory(at: writableTestDirectoryURL, withIntermediateDirectories: true)
        let a = writableTestDirectoryURL.appendingPathComponent("a")
        try Data().write(to: a)
        
        let whereAt = try FileManager.default.url(for: .itemReplacementDirectory, in: .userDomainMask, appropriateFor: a, create: false)
        // Either this is in the temporary directory, or this is on the same filesystem as 'a' (at writableTestDirectoryURL).
        if whereAt.deletingLastPathComponent() == writableTestDirectoryURL {
            XCTAssertEqual(whereAt.deletingLastPathComponent(), writableTestDirectoryURL)
        } else {
            var relationship: FileManager.URLRelationship = .other
            try FileManager.default.getRelationship(&relationship, ofDirectoryAt: URL(fileURLWithPath: NSTemporaryDirectory()), toItemAt: whereAt.deletingLastPathComponent())
            XCTAssertEqual(relationship, .contains)
        }
        
        // To avoid races, Darwin always creates the directory even if create: false. Check this is the case.
        XCTAssertNotNil(try? FileManager.default.attributesOfItem(atPath: whereAt.path))
        try? FileManager.default.removeItem(at: whereAt)
    }
    
    func test_replacement() throws {
        let fm = FileManager.default
        let a = writableTestDirectoryURL.appendingPathComponent("a")

        let initialData = Data("INITIAL".utf8)
        let finalData = Data("FINAL".utf8)
        var temporaryDirectory: URL!
        var b: URL!

        let initialFileName = "INITIAL"
        let finalFileName = "FINAL"
        
        func setUpReplacement(aIsDirectory: Bool, bIsDirectory: Bool) throws {
            try fm.createDirectory(at: writableTestDirectoryURL, withIntermediateDirectories: true)
            if aIsDirectory {
                try fm.createDirectory(at: a, withIntermediateDirectories: true)
                try Data().write(to: a.appendingPathComponent(initialFileName))
            } else {
                try initialData.write(to: a)
            }
            
            temporaryDirectory = try fm.url(for: .itemReplacementDirectory, in: .userDomainMask, appropriateFor: a, create: true)
            
            if bIsDirectory {
                b = temporaryDirectory.appendingPathComponent("b")
                try fm.createDirectory(at: b, withIntermediateDirectories: true)
                try Data().write(to: b.appendingPathComponent(finalFileName))
            } else {
                b = temporaryDirectory.appendingPathComponent("b")
                try finalData.write(to: b)
            }
        }
        
        func tearDownReplacement() throws {
            try? fm.removeItem(at: a)
            try? fm.removeItem(at: writableTestDirectoryURL.appendingPathComponent("c"))
            try? fm.removeItem(at: temporaryDirectory)
        }
        
        var stderr = FileHandle.standardError
        
        func testReplaceMethod(invokedBy replace: (URL, URL, String?, FileManager.ItemReplacementOptions) throws -> URL?) throws {
            func runSingleTest(aIsDirectory: Bool, bIsDirectory: Bool, options: FileManager.ItemReplacementOptions = []) throws {
                print("note: Testing with: a is directory? \(aIsDirectory), b is directory? \(bIsDirectory), using new metadata only? \(options.contains(.usingNewMetadataOnly)), without deleting backup item? \(options.contains(.withoutDeletingBackupItem))", to: &stderr)
                try setUpReplacement(aIsDirectory: aIsDirectory, bIsDirectory: bIsDirectory)
                
                let initialAttributes = options.contains(.usingNewMetadataOnly) ? try fm.attributesOfItem(atPath: b.path) : try fm.attributesOfItem(atPath: a.path)
                
                // Do the thing.
                let result = try replace(a, b, "c", options)
                
                let c = writableTestDirectoryURL.appendingPathComponent("c")
                let cAttributes = try? fm.attributesOfItem(atPath: c.path)
                if options.contains(.withoutDeletingBackupItem) {
                    XCTAssertNotNil(cAttributes)
                    
                    if aIsDirectory {
                        XCTAssertNotNil(try? fm.attributesOfItem(atPath: c.appendingPathComponent(initialFileName).path))
                        XCTAssertNil(try? fm.attributesOfItem(atPath: c.appendingPathComponent(finalFileName).path))
                    } else {
                        XCTAssertEqual(try? Data(contentsOf: c), initialData)
                    }
                    
                    // Remove the backup manually.
                    try? fm.removeItem(at: c)
                } else {
                    XCTAssertNil(cAttributes)
                }
                
                let newA = try XCTUnwrap(result)

                let finalAttributes = try fm.attributesOfItem(atPath: newA.path)
                XCTAssertEqual(initialAttributes[.creationDate] as? AnyHashable, finalAttributes[.creationDate] as? AnyHashable)
                XCTAssertEqual(initialAttributes[.posixPermissions] as? AnyHashable, finalAttributes[.posixPermissions] as? AnyHashable)

                if bIsDirectory {
                    // Ensure we have execute permission, which can happen if .…newMetadataOnly isn't used, and we replace a file with a directory. (That's why we check attributes first.)
                    try? fm.setAttributes([.posixPermissions: 0o777], ofItemAtPath: newA.path)
                    XCTAssertNil(try? fm.attributesOfItem(atPath: newA.appendingPathComponent(initialFileName).path))
                    XCTAssertNotNil(try? fm.attributesOfItem(atPath: newA.appendingPathComponent(finalFileName).path))
                } else {
                    XCTAssertEqual(try? Data(contentsOf: newA), finalData)
                }
                
                try tearDownReplacement()
            }
            
            try runSingleTest(aIsDirectory: false, bIsDirectory: false)
            try runSingleTest(aIsDirectory: false, bIsDirectory: false, options: .withoutDeletingBackupItem)
            try runSingleTest(aIsDirectory: false, bIsDirectory: false, options: .usingNewMetadataOnly)
            try runSingleTest(aIsDirectory: false, bIsDirectory: false, options: [.withoutDeletingBackupItem, .usingNewMetadataOnly])
            try runSingleTest(aIsDirectory: true, bIsDirectory: true)
            try runSingleTest(aIsDirectory: true, bIsDirectory: true, options: .withoutDeletingBackupItem)
            try runSingleTest(aIsDirectory: true, bIsDirectory: true, options: .usingNewMetadataOnly)
            try runSingleTest(aIsDirectory: true, bIsDirectory: true, options: [.withoutDeletingBackupItem, .usingNewMetadataOnly])
            try runSingleTest(aIsDirectory: false, bIsDirectory: true)
            try runSingleTest(aIsDirectory: false, bIsDirectory: true, options: .withoutDeletingBackupItem)
            try runSingleTest(aIsDirectory: false, bIsDirectory: true, options: .usingNewMetadataOnly)
            try runSingleTest(aIsDirectory: false, bIsDirectory: true, options: [.withoutDeletingBackupItem, .usingNewMetadataOnly])
            try runSingleTest(aIsDirectory: true, bIsDirectory: false)
            try runSingleTest(aIsDirectory: true, bIsDirectory: false, options: .withoutDeletingBackupItem)
            try runSingleTest(aIsDirectory: true, bIsDirectory: false, options: .usingNewMetadataOnly)
            try runSingleTest(aIsDirectory: true, bIsDirectory: false, options: [.withoutDeletingBackupItem, .usingNewMetadataOnly])
        }

        print("Testing Darwin Foundation compatible replace", to: &stderr)
        try testReplaceMethod { (a, b, backupItemName, options) -> URL? in
            try fm.replaceItemAt(a, withItemAt: b, backupItemName: backupItemName, options: options)
        }

        #if !DARWIN_COMPATIBILITY_TESTS
        print("note: Testing platform-specific replace implementation.", to: &stderr)
        try testReplaceMethod { (a, b, backupItemName, options) -> URL? in
            try fm.replaceItem(at: a, withItemAt: b, backupItemName: backupItemName, options: options)
        }
        #endif

        #if NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT
        print("note: Testing cross-platform replace implementation.", to: &stderr)
        try testReplaceMethod { (a, b, backupItemName, options) -> URL? in
            try fm._replaceItem(at: a, withItemAt: b, backupItemName: backupItemName, options: options, allowPlatformSpecificSyscalls: false)
        }
        #endif
    }
    
    // -----
    
    var writableTestDirectoryURL: URL!
    
    override func setUp() {
        super.setUp()
        
        let pid = ProcessInfo.processInfo.processIdentifier
        writableTestDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("org.swift.TestFoundation.TestFileManager.\(pid)")
    }
    
    override func tearDown() {
        if let directoryURL = writableTestDirectoryURL,
           (try? FileManager.default.attributesOfItem(atPath: directoryURL.path)) != nil {
            do {
                try FileManager.default.removeItem(at: directoryURL)
            } catch {
                NSLog("Could not remove test directory at URL \(directoryURL): \(error)")
            }
        }
        
        super.tearDown()
    }
    
    static var allTests: [(String, (TestFileManager) -> () throws -> Void)] {
        var tests: [(String, (TestFileManager) -> () throws -> Void)] = [
            ("test_createDirectory", test_createDirectory ),
            ("test_createFile", test_createFile ),
            ("test_moveFile", test_moveFile),
            ("test_fileSystemRepresentation", test_fileSystemRepresentation),
            ("test_fileExists", test_fileExists),
            ("test_isReadableFile", test_isReadableFile),
            ("test_isWritableFile", test_isWritableFile),
            ("test_isExecutableFile", test_isExecutableFile),
            ("test_isDeletableFile", test_isDeletableFile),
            ("test_fileAttributes", test_fileAttributes),
            ("test_fileSystemAttributes", test_fileSystemAttributes),
            ("test_setFileAttributes", test_setFileAttributes),
            ("test_directoryEnumerator", test_directoryEnumerator),
            ("test_pathEnumerator",test_pathEnumerator),
            ("test_contentsOfDirectoryAtPath", test_contentsOfDirectoryAtPath),
            ("test_subpathsOfDirectoryAtPath", test_subpathsOfDirectoryAtPath),
            ("test_copyItemAtPathToPath", test_copyItemAtPathToPath),
            ("test_linkItemAtPathToPath", testExpectedToFailOnAndroid(test_linkItemAtPathToPath, "Android doesn't allow hard links")),
            ("test_homedirectoryForUser", test_homedirectoryForUser),
            ("test_temporaryDirectoryForUser", test_temporaryDirectoryForUser),
            ("test_creatingDirectoryWithShortIntermediatePath", test_creatingDirectoryWithShortIntermediatePath),
            ("test_mountedVolumeURLs", test_mountedVolumeURLs),
            ("test_copyItemsPermissions", test_copyItemsPermissions),
            ("test_emptyFilename", test_emptyFilename),
            ("test_getRelationship", test_getRelationship),
            ("test_displayNames", test_displayNames),
            ("test_getItemReplacementDirectory", test_getItemReplacementDirectory),
            ("test_contentsEqual", test_contentsEqual),
            /* ⚠️  */ ("test_replacement", testExpectedToFail(test_replacement,
            /* ⚠️  */     "<https://bugs.swift.org/browse/SR-10819> Re-enable Foundation test TestFileManager.test_replacement")),
        ]
        
        #if !DEPLOYMENT_RUNTIME_OBJC && NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT && !os(Android)
        tests.append(contentsOf: [
            ("test_xdgStopgapsCoverAllConstants", test_xdgStopgapsCoverAllConstants),
            ("test_parseXDGConfiguration", test_parseXDGConfiguration),
            ("test_xdgURLSelection", test_xdgURLSelection),
            ])
        #endif
        
        #if !DEPLOYMENT_RUNTIME_OBJC && !os(Android)
        tests.append(contentsOf: [
            ("test_fetchXDGPathsFromHelper", test_fetchXDGPathsFromHelper),
            ])
        #endif
        
        return tests
    }
}
