mirror of
https://github.com/apple/swift-foundation.git
synced 2025-05-25 23:48:33 +08:00
1077 lines
48 KiB
Swift
1077 lines
48 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2024 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
#if canImport(TestSupport)
|
|
import TestSupport
|
|
#endif // canImport(TestSupport)
|
|
|
|
#if canImport(FoundationEssentials)
|
|
@testable import FoundationEssentials
|
|
#endif
|
|
|
|
#if FOUNDATION_FRAMEWORK
|
|
@testable import Foundation
|
|
#endif
|
|
|
|
#if canImport(Android)
|
|
import Android
|
|
#endif
|
|
|
|
extension FileManager {
|
|
fileprivate var delegateCaptures: DelegateCaptures {
|
|
(self.delegate as! CapturingFileManagerDelegate).captures
|
|
}
|
|
}
|
|
|
|
private struct DelegateCaptures : Equatable, Sendable {
|
|
struct Operation : Equatable, CustomStringConvertible {
|
|
let src: String
|
|
let dst: String?
|
|
|
|
var description: String {
|
|
if let dst {
|
|
"'\(src)' --> '\(dst)'"
|
|
} else {
|
|
"'\(src)'"
|
|
}
|
|
}
|
|
|
|
init(_ src: String, _ dst: String? = nil) {
|
|
self.src = src
|
|
self.dst = dst
|
|
}
|
|
}
|
|
|
|
struct ErrorOperation : Equatable, CustomStringConvertible {
|
|
let op: Operation
|
|
let code: CocoaError.Code?
|
|
|
|
init(_ src: String, _ dst: String? = nil, code: CocoaError.Code?) {
|
|
self.op = Operation(src, dst)
|
|
self.code = code
|
|
}
|
|
|
|
var description: String {
|
|
if let code {
|
|
"\(op.description) {\(code.rawValue)}"
|
|
} else {
|
|
"\(op.description) {non-CocoaError}"
|
|
}
|
|
}
|
|
}
|
|
var shouldCopy: [Operation] = []
|
|
var shouldProceedAfterCopyError: [ErrorOperation] = []
|
|
var shouldMove: [Operation] = []
|
|
var shouldProceedAfterMoveError: [ErrorOperation] = []
|
|
var shouldLink: [Operation] = []
|
|
var shouldProceedAfterLinkError: [ErrorOperation] = []
|
|
var shouldRemove: [Operation] = []
|
|
var shouldProceedAfterRemoveError: [ErrorOperation] = []
|
|
|
|
var isEmpty: Bool {
|
|
self == DelegateCaptures()
|
|
}
|
|
}
|
|
|
|
#if FOUNDATION_FRAMEWORK
|
|
class CapturingFileManagerDelegate : NSObject, FileManagerDelegate, @unchecked Sendable {
|
|
// Sendable note: This is only used on one thread during testing
|
|
fileprivate nonisolated(unsafe) var captures = DelegateCaptures()
|
|
|
|
func fileManager(_ fileManager: FileManager, shouldCopyItemAtPath srcPath: String, toPath dstPath: String) -> Bool {
|
|
captures.shouldCopy.append(.init(srcPath, dstPath))
|
|
return true
|
|
}
|
|
|
|
func fileManager(_ fileManager: FileManager, shouldProceedAfterError error: any Error, copyingItemAtPath srcPath: String, toPath dstPath: String) -> Bool {
|
|
captures.shouldProceedAfterCopyError.append(.init(srcPath, dstPath, code: (error as? CocoaError)?.code))
|
|
return true
|
|
}
|
|
|
|
func fileManager(_ fileManager: FileManager, shouldMoveItemAtPath srcPath: String, toPath dstPath: String) -> Bool {
|
|
captures.shouldMove.append(.init(srcPath, dstPath))
|
|
return true
|
|
}
|
|
|
|
func fileManager(_ fileManager: FileManager, shouldProceedAfterError error: any Error, movingItemAtPath srcPath: String, toPath dstPath: String) -> Bool {
|
|
captures.shouldProceedAfterMoveError.append(.init(srcPath, dstPath, code: (error as? CocoaError)?.code))
|
|
return true
|
|
}
|
|
|
|
func fileManager(_ fileManager: FileManager, shouldLinkItemAtPath srcPath: String, toPath dstPath: String) -> Bool {
|
|
captures.shouldLink.append(.init(srcPath, dstPath))
|
|
return true
|
|
}
|
|
|
|
func fileManager(_ fileManager: FileManager, shouldProceedAfterError error: any Error, linkingItemAtPath srcPath: String, toPath dstPath: String) -> Bool {
|
|
captures.shouldProceedAfterLinkError.append(.init(srcPath, dstPath, code: (error as? CocoaError)?.code))
|
|
return true
|
|
}
|
|
|
|
func fileManager(_ fileManager: FileManager, shouldRemoveItemAtPath path: String) -> Bool {
|
|
captures.shouldRemove.append(.init(path))
|
|
return true
|
|
}
|
|
|
|
func fileManager(_ fileManager: FileManager, shouldProceedAfterError error: any Error, removingItemAtPath path: String) -> Bool {
|
|
captures.shouldProceedAfterRemoveError.append(DelegateCaptures.ErrorOperation(path, code: (error as? CocoaError)?.code))
|
|
return true
|
|
}
|
|
}
|
|
#else
|
|
final class CapturingFileManagerDelegate : FileManagerDelegate, Sendable {
|
|
// Sendable note: This is only used on one thread during testing
|
|
fileprivate nonisolated(unsafe) var captures = DelegateCaptures()
|
|
|
|
func fileManager(_ fileManager: FileManager, shouldCopyItemAtPath srcPath: String, toPath dstPath: String) -> Bool {
|
|
captures.shouldCopy.append(.init(srcPath, dstPath))
|
|
return true
|
|
}
|
|
|
|
func fileManager(_ fileManager: FileManager, shouldProceedAfterError error: any Error, copyingItemAtPath srcPath: String, toPath dstPath: String) -> Bool {
|
|
captures.shouldProceedAfterCopyError.append(.init(srcPath, dstPath, code: (error as? CocoaError)?.code))
|
|
return true
|
|
}
|
|
|
|
func fileManager(_ fileManager: FileManager, shouldMoveItemAtPath srcPath: String, toPath dstPath: String) -> Bool {
|
|
captures.shouldMove.append(.init(srcPath, dstPath))
|
|
return true
|
|
}
|
|
|
|
func fileManager(_ fileManager: FileManager, shouldProceedAfterError error: any Error, movingItemAtPath srcPath: String, toPath dstPath: String) -> Bool {
|
|
captures.shouldProceedAfterMoveError.append(.init(srcPath, dstPath, code: (error as? CocoaError)?.code))
|
|
return true
|
|
}
|
|
|
|
func fileManager(_ fileManager: FileManager, shouldLinkItemAtPath srcPath: String, toPath dstPath: String) -> Bool {
|
|
captures.shouldLink.append(.init(srcPath, dstPath))
|
|
return true
|
|
}
|
|
|
|
func fileManager(_ fileManager: FileManager, shouldProceedAfterError error: any Error, linkingItemAtPath srcPath: String, toPath dstPath: String) -> Bool {
|
|
captures.shouldProceedAfterLinkError.append(.init(srcPath, dstPath, code: (error as? CocoaError)?.code))
|
|
return true
|
|
}
|
|
|
|
func fileManager(_ fileManager: FileManager, shouldRemoveItemAtPath path: String) -> Bool {
|
|
captures.shouldRemove.append(.init(path))
|
|
return true
|
|
}
|
|
|
|
func fileManager(_ fileManager: FileManager, shouldProceedAfterError error: any Error, removingItemAtPath path: String) -> Bool {
|
|
captures.shouldProceedAfterRemoveError.append(DelegateCaptures.ErrorOperation(path, code: (error as? CocoaError)?.code))
|
|
return true
|
|
}
|
|
}
|
|
#endif
|
|
|
|
final class FileManagerTests : XCTestCase {
|
|
private func randomData(count: Int = 10000) -> Data {
|
|
Data((0 ..< count).map { _ in UInt8.random(in: .min ..< .max) })
|
|
}
|
|
|
|
func testContentsAtPath() throws {
|
|
let data = randomData()
|
|
try FileManagerPlayground {
|
|
File("test", contents: data)
|
|
}.test {
|
|
XCTAssertEqual($0.contents(atPath: "test"), data)
|
|
}
|
|
}
|
|
|
|
func testContentsEqualAtPaths() throws {
|
|
try FileManagerPlayground {
|
|
Directory("dir1") {
|
|
Directory("dir2") {
|
|
"Foo"
|
|
"Bar"
|
|
}
|
|
Directory("dir3") {
|
|
"Baz"
|
|
}
|
|
}
|
|
Directory("dir1_copy") {
|
|
Directory("dir2") {
|
|
"Foo"
|
|
"Bar"
|
|
}
|
|
Directory("dir3") {
|
|
"Baz"
|
|
}
|
|
}
|
|
Directory("dir1_diffdata") {
|
|
Directory("dir2") {
|
|
"Foo"
|
|
"Bar"
|
|
}
|
|
Directory("dir3") {
|
|
File("Baz", contents: randomData())
|
|
}
|
|
}
|
|
Directory("symlinks") {
|
|
File("Foo", contents: randomData())
|
|
SymbolicLink("LinkToFoo", destination: "Foo")
|
|
}
|
|
Directory("EmptyDirectory") {}
|
|
"EmptyFile"
|
|
}.test {
|
|
XCTAssertTrue($0.contentsEqual(atPath: "dir1", andPath: "dir1_copy"))
|
|
XCTAssertFalse($0.contentsEqual(atPath: "dir1/dir2", andPath: "dir1/dir3"))
|
|
XCTAssertFalse($0.contentsEqual(atPath: "dir1", andPath: "dir1_diffdata"))
|
|
XCTAssertFalse($0.contentsEqual(atPath: "symlinks/LinkToFoo", andPath: "symlinks/Foo"), "Symbolic link should not be equal to its destination")
|
|
XCTAssertFalse($0.contentsEqual(atPath: "symlinks/LinkToFoo", andPath: "EmptyFile"), "Symbolic link should not be equal to an empty file")
|
|
XCTAssertFalse($0.contentsEqual(atPath: "symlinks/LinkToFoo", andPath: "EmptyDirectory"), "Symbolic link should not be equal to an empty directory")
|
|
XCTAssertFalse($0.contentsEqual(atPath: "symlinks/EmptyDirectory", andPath: "EmptyFile"), "Empty directory should not be equal to empty file")
|
|
}
|
|
}
|
|
|
|
func testDirectoryContentsAtPath() throws {
|
|
try FileManagerPlayground {
|
|
Directory("dir1") {
|
|
Directory("dir2") {
|
|
"Foo"
|
|
"Bar"
|
|
}
|
|
Directory("dir3") {
|
|
"Baz"
|
|
}
|
|
}
|
|
}.test {
|
|
XCTAssertEqual(try $0.contentsOfDirectory(atPath: "dir1").sorted(), ["dir2", "dir3"])
|
|
XCTAssertEqual(try $0.contentsOfDirectory(atPath: "dir1/dir2").sorted(), ["Bar", "Foo"])
|
|
XCTAssertEqual(try $0.contentsOfDirectory(atPath: "dir1/dir3").sorted(), ["Baz"])
|
|
XCTAssertThrowsError(try $0.contentsOfDirectory(atPath: "does_not_exist")) {
|
|
XCTAssertEqual(($0 as? CocoaError)?.code, .fileReadNoSuchFile)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testSubpathsOfDirectoryAtPath() throws {
|
|
try FileManagerPlayground {
|
|
Directory("dir1") {
|
|
Directory("dir2") {
|
|
"Foo"
|
|
"Bar"
|
|
}
|
|
Directory("dir3") {
|
|
"Baz"
|
|
}
|
|
}
|
|
Directory("symlinks") {
|
|
"Foo"
|
|
SymbolicLink("Bar", destination: "Foo")
|
|
SymbolicLink("Parent", destination: "..")
|
|
}
|
|
}.test {
|
|
XCTAssertEqual(try $0.subpathsOfDirectory(atPath: "dir1").sorted(), ["dir2", "dir2/Bar", "dir2/Foo", "dir3", "dir3/Baz"])
|
|
XCTAssertEqual(try $0.subpathsOfDirectory(atPath: "dir1/dir2").sorted(), ["Bar", "Foo"])
|
|
XCTAssertEqual(try $0.subpathsOfDirectory(atPath: "dir1/dir3").sorted(), ["Baz"])
|
|
|
|
XCTAssertEqual(try $0.subpathsOfDirectory(atPath: "symlinks").sorted(), ["Bar", "Foo", "Parent"])
|
|
|
|
XCTAssertThrowsError(try $0.subpathsOfDirectory(atPath: "does_not_exist")) {
|
|
XCTAssertEqual(($0 as? CocoaError)?.code, .fileReadNoSuchFile)
|
|
}
|
|
|
|
XCTAssertThrowsError(try $0.subpathsOfDirectory(atPath: "")) {
|
|
#if os(Windows)
|
|
XCTAssertEqual(($0 as? CocoaError)?.code, .fileReadInvalidFileName)
|
|
#else
|
|
XCTAssertEqual(($0 as? CocoaError)?.code, .fileReadNoSuchFile)
|
|
#endif
|
|
}
|
|
|
|
let fullContents = ["dir1", "dir1/dir2", "dir1/dir2/Bar", "dir1/dir2/Foo", "dir1/dir3", "dir1/dir3/Baz", "symlinks", "symlinks/Bar", "symlinks/Foo", "symlinks/Parent"]
|
|
let cwd = $0.currentDirectoryPath
|
|
XCTAssertNotEqual(cwd.last, "/")
|
|
let paths = [cwd, "\(cwd)/", "\(cwd)//", ".", "./", ".//"]
|
|
for path in paths {
|
|
XCTAssertEqual(try $0.subpathsOfDirectory(atPath: path).sorted(), fullContents)
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
func testCreateDirectoryAtPath() throws {
|
|
try FileManagerPlayground {
|
|
"preexisting_file"
|
|
}.test {
|
|
try $0.createDirectory(atPath: "create_dir_test", withIntermediateDirectories: false)
|
|
XCTAssertEqual(try $0.contentsOfDirectory(atPath: ".").sorted(), ["create_dir_test", "preexisting_file"])
|
|
try $0.createDirectory(atPath: "create_dir_test2/nested", withIntermediateDirectories: true)
|
|
XCTAssertEqual(try $0.contentsOfDirectory(atPath: "create_dir_test2"), ["nested"])
|
|
try $0.createDirectory(atPath: "create_dir_test2/nested2", withIntermediateDirectories: true)
|
|
XCTAssertEqual(try $0.contentsOfDirectory(atPath: "create_dir_test2").sorted(), ["nested", "nested2"])
|
|
XCTAssertNoThrow(try $0.createDirectory(atPath: "create_dir_test2/nested2", withIntermediateDirectories: true))
|
|
|
|
#if os(Windows)
|
|
try $0.createDirectory(atPath: "create_dir_test3\\nested", withIntermediateDirectories: true)
|
|
XCTAssertEqual(try $0.contentsOfDirectory(atPath: "create_dir_test3"), ["nested"])
|
|
#endif
|
|
|
|
XCTAssertThrowsError(try $0.createDirectory(atPath: "create_dir_test", withIntermediateDirectories: false)) {
|
|
XCTAssertEqual(($0 as? CocoaError)?.code, .fileWriteFileExists)
|
|
}
|
|
XCTAssertThrowsError(try $0.createDirectory(atPath: "create_dir_test4/nested", withIntermediateDirectories: false)) {
|
|
XCTAssertEqual(($0 as? CocoaError)?.code, .fileNoSuchFile)
|
|
}
|
|
XCTAssertThrowsError(try $0.createDirectory(atPath: "preexisting_file", withIntermediateDirectories: false)) {
|
|
XCTAssertEqual(($0 as? CocoaError)?.code, .fileWriteFileExists)
|
|
}
|
|
XCTAssertThrowsError(try $0.createDirectory(atPath: "preexisting_file", withIntermediateDirectories: true)) {
|
|
XCTAssertEqual(($0 as? CocoaError)?.code, .fileWriteFileExists)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testLinkFileAtPathToPath() throws {
|
|
try FileManagerPlayground {
|
|
"foo"
|
|
}.test(captureDelegateCalls: true) {
|
|
XCTAssertTrue($0.delegateCaptures.isEmpty)
|
|
try $0.linkItem(atPath: "foo", toPath: "bar")
|
|
XCTAssertEqual($0.delegateCaptures.shouldLink, [.init("foo", "bar")])
|
|
#if os(Android) // Hard links are not normally allowed on Android.
|
|
XCTAssertEqual($0.delegateCaptures.shouldProceedAfterLinkError, [.init("foo", "bar", code: .fileWriteNoPermission)])
|
|
XCTAssertFalse($0.fileExists(atPath: "bar"))
|
|
#else
|
|
XCTAssertEqual($0.delegateCaptures.shouldProceedAfterLinkError, [])
|
|
XCTAssertTrue($0.fileExists(atPath: "bar"))
|
|
#endif
|
|
}
|
|
|
|
try FileManagerPlayground {
|
|
"foo"
|
|
"bar"
|
|
}.test(captureDelegateCalls: true) {
|
|
XCTAssertTrue($0.delegateCaptures.isEmpty)
|
|
try $0.linkItem(atPath: "foo", toPath: "bar")
|
|
XCTAssertEqual($0.delegateCaptures.shouldLink, [.init("foo", "bar")])
|
|
XCTAssertEqual($0.delegateCaptures.shouldProceedAfterLinkError, [.init("foo", "bar", code: .fileWriteFileExists)])
|
|
}
|
|
}
|
|
|
|
func testCopyFileAtPathToPath() throws {
|
|
try FileManagerPlayground {
|
|
"foo"
|
|
}.test(captureDelegateCalls: true) {
|
|
XCTAssertTrue($0.delegateCaptures.isEmpty)
|
|
try $0.copyItem(atPath: "foo", toPath: "bar")
|
|
XCTAssertEqual($0.delegateCaptures.shouldCopy, [.init("foo", "bar")])
|
|
XCTAssertEqual($0.delegateCaptures.shouldProceedAfterCopyError, [])
|
|
XCTAssertTrue($0.fileExists(atPath: "bar"))
|
|
}
|
|
|
|
try FileManagerPlayground {
|
|
"foo"
|
|
"bar"
|
|
}.test(captureDelegateCalls: true) {
|
|
XCTAssertTrue($0.delegateCaptures.isEmpty)
|
|
try $0.copyItem(atPath: "foo", toPath: "bar")
|
|
XCTAssertEqual($0.delegateCaptures.shouldCopy, [.init("foo", "bar")])
|
|
XCTAssertEqual($0.delegateCaptures.shouldProceedAfterCopyError, [.init("foo", "bar", code: .fileWriteFileExists)])
|
|
}
|
|
|
|
try FileManagerPlayground {
|
|
"foo"
|
|
SymbolicLink("bar", destination: "foo")
|
|
}.test(captureDelegateCalls: true) {
|
|
XCTAssertTrue($0.delegateCaptures.isEmpty)
|
|
try $0.copyItem(atPath: "bar", toPath: "copy")
|
|
XCTAssertEqual($0.delegateCaptures.shouldCopy, [.init("bar", "copy")])
|
|
XCTAssertEqual($0.delegateCaptures.shouldProceedAfterCopyError, [])
|
|
let copyDestination = try $0.destinationOfSymbolicLink(atPath: "copy")
|
|
XCTAssertEqual(copyDestination.lastPathComponent, "foo", "Copied symbolic link points at \(copyDestination) instead of foo")
|
|
}
|
|
|
|
try FileManagerPlayground {
|
|
Directory("dir") {
|
|
"foo"
|
|
}
|
|
SymbolicLink("link", destination: "dir")
|
|
}.test(captureDelegateCalls: true) {
|
|
XCTAssertTrue($0.delegateCaptures.isEmpty)
|
|
try $0.copyItem(atPath: "link", toPath: "copy")
|
|
XCTAssertEqual($0.delegateCaptures.shouldCopy, [.init("link", "copy")])
|
|
XCTAssertEqual($0.delegateCaptures.shouldProceedAfterCopyError, [])
|
|
let copyDestination = try $0.destinationOfSymbolicLink(atPath: "copy")
|
|
XCTAssertEqual(copyDestination.lastPathComponent, "dir", "Copied symbolic link points at \(copyDestination) instead of foo")
|
|
}
|
|
}
|
|
|
|
func testCreateSymbolicLinkAtPath() throws {
|
|
try FileManagerPlayground {
|
|
"foo"
|
|
Directory("dir") {}
|
|
}.test {
|
|
try $0.createSymbolicLink(atPath: "bar", withDestinationPath: "foo")
|
|
XCTAssertEqual(try $0.destinationOfSymbolicLink(atPath: "bar"), "foo")
|
|
|
|
try $0.createSymbolicLink(atPath: "dir_link", withDestinationPath: "dir")
|
|
XCTAssertEqual(try $0.destinationOfSymbolicLink(atPath: "dir_link"), "dir")
|
|
|
|
XCTAssertThrowsError(try $0.createSymbolicLink(atPath: "bar", withDestinationPath: "foo")) {
|
|
XCTAssertEqual(($0 as? CocoaError)?.code, .fileWriteFileExists)
|
|
}
|
|
XCTAssertThrowsError(try $0.createSymbolicLink(atPath: "foo", withDestinationPath: "baz")) {
|
|
XCTAssertEqual(($0 as? CocoaError)?.code, .fileWriteFileExists)
|
|
}
|
|
XCTAssertThrowsError(try $0.destinationOfSymbolicLink(atPath: "foo")) {
|
|
XCTAssertEqual(($0 as? CocoaError)?.code, .fileReadUnknown)
|
|
}
|
|
}
|
|
|
|
try FileManagerPlayground {
|
|
Directory("dir") {
|
|
Directory("other_dir") {
|
|
"file"
|
|
}
|
|
}
|
|
}.test {
|
|
// Create a relative symlink to other_dir from within dir (tests windows special dir symlink handling)
|
|
try $0.createSymbolicLink(atPath: "dir/link", withDestinationPath: "other_dir")
|
|
|
|
// Ensure it is created successfully
|
|
XCTAssertEqual(try $0.destinationOfSymbolicLink(atPath: "dir/link"), "other_dir")
|
|
XCTAssertEqual(try $0.contentsOfDirectory(atPath: "dir/link"), ["file"])
|
|
|
|
do {
|
|
// Second symlink creation with an absolute path
|
|
let absolute = URL(filePath: "dir/link2", relativeTo: URL(filePath: $0.currentDirectoryPath, directoryHint: .isDirectory)).path
|
|
try $0.createSymbolicLink(atPath: absolute, withDestinationPath: "other_dir")
|
|
|
|
// Ensure it is created successfully
|
|
XCTAssertEqual(try $0.destinationOfSymbolicLink(atPath: "dir/link2"), "other_dir")
|
|
XCTAssertEqual(try $0.contentsOfDirectory(atPath: "dir/link2"), ["file"])
|
|
}
|
|
|
|
do {
|
|
// And lastly a symlink to an absolute path
|
|
let absolute = URL(filePath: "dir/other_dir", relativeTo: URL(filePath: $0.currentDirectoryPath, directoryHint: .isDirectory)).path
|
|
try $0.createSymbolicLink(atPath: "dir/link3", withDestinationPath: absolute)
|
|
|
|
// Ensure it is created successfully
|
|
XCTAssertEqual(try $0.destinationOfSymbolicLink(atPath: "dir/link3"), absolute.withFileSystemRepresentation { String(cString: $0!) })
|
|
XCTAssertEqual(try $0.contentsOfDirectory(atPath: "dir/link3"), ["file"])
|
|
}
|
|
}
|
|
}
|
|
|
|
func testMoveItemAtPathToPath() throws {
|
|
let data = randomData()
|
|
try FileManagerPlayground {
|
|
Directory("dir") {
|
|
File("foo", contents: data)
|
|
"bar"
|
|
}
|
|
"other_file"
|
|
}.test(captureDelegateCalls: true) {
|
|
XCTAssertTrue($0.delegateCaptures.isEmpty)
|
|
try $0.moveItem(atPath: "dir", toPath: "dir2")
|
|
XCTAssertEqual(try $0.subpathsOfDirectory(atPath: ".").sorted(), ["dir2", "dir2/bar", "dir2/foo", "other_file"])
|
|
XCTAssertEqual($0.contents(atPath: "dir2/foo"), data)
|
|
|
|
let rootDir = URL(fileURLWithPath: $0.currentDirectoryPath).path
|
|
XCTAssertEqual($0.delegateCaptures.shouldMove, [.init("\(rootDir)/dir", "\(rootDir)/dir2")])
|
|
|
|
try $0.moveItem(atPath: "does_not_exist", toPath: "dir3")
|
|
XCTAssertEqual($0.delegateCaptures.shouldProceedAfterMoveError, [.init("\(rootDir)/does_not_exist", "\(rootDir)/dir3", code: .fileNoSuchFile)])
|
|
|
|
try $0.moveItem(atPath: "dir2", toPath: "other_file")
|
|
XCTAssertTrue($0.delegateCaptures.shouldProceedAfterMoveError.contains(.init("\(rootDir)/dir2", "\(rootDir)/other_file", code: .fileWriteFileExists)))
|
|
}
|
|
}
|
|
|
|
func testCopyItemAtPathToPath() throws {
|
|
let data = randomData()
|
|
try FileManagerPlayground {
|
|
Directory("dir") {
|
|
File("foo", contents: data)
|
|
"bar"
|
|
}
|
|
"other_file"
|
|
}.test(captureDelegateCalls: true) {
|
|
XCTAssertTrue($0.delegateCaptures.isEmpty)
|
|
try $0.copyItem(atPath: "dir", toPath: "dir2")
|
|
XCTAssertEqual(try $0.subpathsOfDirectory(atPath: ".").sorted(), ["dir", "dir/bar", "dir/foo", "dir2", "dir2/bar", "dir2/foo", "other_file"])
|
|
XCTAssertEqual($0.contents(atPath: "dir/foo"), data)
|
|
XCTAssertEqual($0.contents(atPath: "dir2/foo"), data)
|
|
#if os(Windows)
|
|
XCTAssertEqual($0.delegateCaptures.shouldCopy, [.init("dir", "dir2"), .init("dir/bar", "dir2/bar"), .init("dir/foo", "dir2/foo")])
|
|
#else
|
|
XCTAssertEqual($0.delegateCaptures.shouldCopy, [.init("dir", "dir2"), .init("dir/foo", "dir2/foo"), .init("dir/bar", "dir2/bar")])
|
|
#endif
|
|
|
|
XCTAssertThrowsError(try $0.copyItem(atPath: "does_not_exist", toPath: "dir3")) {
|
|
XCTAssertEqual(($0 as? CocoaError)?.code, .fileReadNoSuchFile)
|
|
}
|
|
|
|
try $0.copyItem(atPath: "dir", toPath: "other_file")
|
|
XCTAssertTrue($0.delegateCaptures.shouldProceedAfterCopyError.contains(.init("dir", "other_file", code: .fileWriteFileExists)))
|
|
}
|
|
}
|
|
|
|
func testRemoveItemAtPath() throws {
|
|
try FileManagerPlayground {
|
|
Directory("dir") {
|
|
"foo"
|
|
"bar"
|
|
}
|
|
"other"
|
|
}.test(captureDelegateCalls: true) {
|
|
XCTAssertTrue($0.delegateCaptures.isEmpty)
|
|
try $0.removeItem(atPath: "dir/bar")
|
|
XCTAssertEqual(try $0.subpathsOfDirectory(atPath: ".").sorted(), ["dir", "dir/foo", "other"])
|
|
XCTAssertEqual($0.delegateCaptures.shouldRemove, [.init("dir/bar")])
|
|
XCTAssertEqual($0.delegateCaptures.shouldProceedAfterRemoveError, [])
|
|
|
|
let rootDir = URL(fileURLWithPath: $0.currentDirectoryPath).path
|
|
try $0.removeItem(atPath: "dir")
|
|
XCTAssertEqual(try $0.subpathsOfDirectory(atPath: ".").sorted(), ["other"])
|
|
XCTAssertEqual($0.delegateCaptures.shouldRemove, [.init("dir/bar"), .init("\(rootDir)/dir"), .init("\(rootDir)/dir/foo")])
|
|
XCTAssertEqual($0.delegateCaptures.shouldProceedAfterRemoveError, [])
|
|
|
|
try $0.removeItem(atPath: "other")
|
|
XCTAssertEqual(try $0.subpathsOfDirectory(atPath: ".").sorted(), [])
|
|
XCTAssertEqual($0.delegateCaptures.shouldRemove, [.init("dir/bar"), .init("\(rootDir)/dir"), .init("\(rootDir)/dir/foo"), .init("other")])
|
|
XCTAssertEqual($0.delegateCaptures.shouldProceedAfterRemoveError, [])
|
|
|
|
try $0.removeItem(atPath: "does_not_exist")
|
|
XCTAssertEqual($0.delegateCaptures.shouldRemove, [.init("dir/bar"), .init("\(rootDir)/dir"), .init("\(rootDir)/dir/foo"), .init("other"), .init("does_not_exist")])
|
|
XCTAssertEqual($0.delegateCaptures.shouldProceedAfterRemoveError, [.init("does_not_exist", code: .fileNoSuchFile)])
|
|
}
|
|
|
|
try FileManagerPlayground {
|
|
Directory("dir") {
|
|
Directory("dir2") {
|
|
"file"
|
|
}
|
|
}
|
|
}.test(captureDelegateCalls: true) {
|
|
let rootDir = URL(fileURLWithPath: $0.currentDirectoryPath).path
|
|
|
|
XCTAssertTrue($0.delegateCaptures.isEmpty)
|
|
try $0.removeItem(atPath: "dir")
|
|
XCTAssertEqual(try $0.subpathsOfDirectory(atPath: ".").sorted(), [])
|
|
XCTAssertEqual($0.delegateCaptures.shouldRemove, [.init("\(rootDir)/dir"), .init("\(rootDir)/dir/dir2"), .init("\(rootDir)/dir/dir2/file")])
|
|
XCTAssertEqual($0.delegateCaptures.shouldProceedAfterRemoveError, [])
|
|
}
|
|
|
|
#if canImport(Darwin)
|
|
// not supported on linux as the test depends on FileManager.removeItem calling removefile(3)
|
|
// not supported on older versions of Darwin where removefile would return ENOENT instead of ENAMETOOLONG
|
|
if #available(macOS 14.4, iOS 17.0, watchOS 10.0, tvOS 17.0, *) {
|
|
try FileManagerPlayground {
|
|
}.test {
|
|
// Create hierarchy in which the leaf is a long path (length > PATH_MAX)
|
|
let rootDir = $0.currentDirectoryPath
|
|
let aas = Array(repeating: "a", count: Int(NAME_MAX) - 3).joined()
|
|
let bbs = Array(repeating: "b", count: Int(NAME_MAX) - 3).joined()
|
|
let ccs = Array(repeating: "c", count: Int(NAME_MAX) - 3).joined()
|
|
let dds = Array(repeating: "d", count: Int(NAME_MAX) - 3).joined()
|
|
let ees = Array(repeating: "e", count: Int(NAME_MAX) - 3).joined()
|
|
let leaf = "longpath"
|
|
|
|
try $0.createDirectory(atPath: aas, withIntermediateDirectories: true)
|
|
XCTAssertTrue($0.changeCurrentDirectoryPath(aas))
|
|
try $0.createDirectory(atPath: bbs, withIntermediateDirectories: true)
|
|
XCTAssertTrue($0.changeCurrentDirectoryPath(bbs))
|
|
try $0.createDirectory(atPath: ccs, withIntermediateDirectories: true)
|
|
XCTAssertTrue($0.changeCurrentDirectoryPath(ccs))
|
|
try $0.createDirectory(atPath: dds, withIntermediateDirectories: true)
|
|
XCTAssertTrue($0.changeCurrentDirectoryPath(dds))
|
|
try $0.createDirectory(atPath: ees, withIntermediateDirectories: true)
|
|
XCTAssertTrue($0.changeCurrentDirectoryPath(ees))
|
|
try $0.createDirectory(atPath: leaf, withIntermediateDirectories: true)
|
|
|
|
XCTAssertTrue($0.changeCurrentDirectoryPath(rootDir))
|
|
let fullPath = "\(aas)/\(bbs)/\(ccs)/\(dds)/\(ees)/\(leaf)"
|
|
XCTAssertThrowsError(try $0.removeItem(atPath: fullPath)) {
|
|
let underlyingPosixError = ($0 as? CocoaError)?.underlying as? POSIXError
|
|
XCTAssertEqual(underlyingPosixError?.code, .ENAMETOOLONG, "removeItem didn't fail with ENAMETOOLONG; produced error: \($0)")
|
|
}
|
|
|
|
// Clean up
|
|
XCTAssertTrue($0.changeCurrentDirectoryPath(aas))
|
|
XCTAssertTrue($0.changeCurrentDirectoryPath(bbs))
|
|
XCTAssertTrue($0.changeCurrentDirectoryPath(ccs))
|
|
XCTAssertTrue($0.changeCurrentDirectoryPath(dds))
|
|
try $0.removeItem(atPath: ees)
|
|
XCTAssertTrue($0.changeCurrentDirectoryPath(".."))
|
|
try $0.removeItem(atPath: dds)
|
|
XCTAssertTrue($0.changeCurrentDirectoryPath(".."))
|
|
try $0.removeItem(atPath: ccs)
|
|
XCTAssertTrue($0.changeCurrentDirectoryPath(".."))
|
|
try $0.removeItem(atPath: bbs)
|
|
XCTAssertTrue($0.changeCurrentDirectoryPath(".."))
|
|
try $0.removeItem(atPath: aas)
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
func testFileExistsAtPath() throws {
|
|
try FileManagerPlayground {
|
|
Directory("dir") {
|
|
"foo"
|
|
"bar"
|
|
}
|
|
"other"
|
|
SymbolicLink("link_to_file", destination: "other")
|
|
SymbolicLink("link_to_dir", destination: "dir")
|
|
SymbolicLink("link_to_nonexistent", destination: "does_not_exist")
|
|
}.test {
|
|
#if FOUNDATION_FRAMEWORK
|
|
var isDir: ObjCBool = false
|
|
func isDirBool() -> Bool {
|
|
isDir.boolValue
|
|
}
|
|
#else
|
|
var isDir: Bool = false
|
|
func isDirBool() -> Bool {
|
|
isDir
|
|
}
|
|
#endif
|
|
XCTAssertTrue($0.fileExists(atPath: "dir/foo", isDirectory: &isDir))
|
|
XCTAssertFalse(isDirBool())
|
|
XCTAssertTrue($0.fileExists(atPath: "dir/bar", isDirectory: &isDir))
|
|
XCTAssertFalse(isDirBool())
|
|
XCTAssertTrue($0.fileExists(atPath: "dir", isDirectory: &isDir))
|
|
XCTAssertTrue(isDirBool())
|
|
XCTAssertTrue($0.fileExists(atPath: "other", isDirectory: &isDir))
|
|
XCTAssertFalse(isDirBool())
|
|
XCTAssertTrue($0.fileExists(atPath: "link_to_file", isDirectory: &isDir))
|
|
XCTAssertFalse(isDirBool())
|
|
XCTAssertTrue($0.fileExists(atPath: "link_to_dir", isDirectory: &isDir))
|
|
XCTAssertTrue(isDirBool())
|
|
XCTAssertFalse($0.fileExists(atPath: "does_not_exist"))
|
|
XCTAssertFalse($0.fileExists(atPath: "link_to_nonexistent"))
|
|
}
|
|
}
|
|
|
|
func testFileAccessAtPath() throws {
|
|
#if !os(Windows)
|
|
guard getuid() != 0 else {
|
|
// Root users can always access anything, so this test will not function when run as root
|
|
throw XCTSkip("This test is not available when running as the root user")
|
|
}
|
|
#endif
|
|
|
|
try FileManagerPlayground {
|
|
File("000", attributes: [.posixPermissions: 0o000])
|
|
File("111", attributes: [.posixPermissions: 0o111])
|
|
File("222", attributes: [.posixPermissions: 0o222])
|
|
File("333", attributes: [.posixPermissions: 0o333])
|
|
File("444", attributes: [.posixPermissions: 0o444])
|
|
File("555", attributes: [.posixPermissions: 0o555])
|
|
File("666", attributes: [.posixPermissions: 0o666])
|
|
File("777", attributes: [.posixPermissions: 0o777])
|
|
}.test {
|
|
#if os(Windows)
|
|
// All files are readable on Windows
|
|
let readable = ["000", "111", "222", "333", "444", "555", "666", "777"]
|
|
// None of these files are executable on Windows
|
|
let executable: [String] = []
|
|
#else
|
|
let readable = ["444", "555", "666", "777"]
|
|
let executable = ["111", "333", "555", "777"]
|
|
#endif
|
|
let writable = ["222", "333", "666", "777"]
|
|
for number in 0...7 {
|
|
let file = "\(number)\(number)\(number)"
|
|
XCTAssertEqual($0.isReadableFile(atPath: file), readable.contains(file), "'\(file)' failed readable check")
|
|
XCTAssertEqual($0.isWritableFile(atPath: file), writable.contains(file), "'\(file)' failed writable check")
|
|
XCTAssertEqual($0.isExecutableFile(atPath: file), executable.contains(file), "'\(file)' failed executable check")
|
|
#if os(Windows)
|
|
// Only writable files are deletable on Windows
|
|
XCTAssertEqual($0.isDeletableFile(atPath: file), writable.contains(file), "'\(file)' failed deletable check")
|
|
#else
|
|
XCTAssertTrue($0.isDeletableFile(atPath: file), "'\(file)' failed deletable check")
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
func testFileSystemAttributesAtPath() throws {
|
|
try FileManagerPlayground {
|
|
"Foo"
|
|
}.test {
|
|
let dict = try $0.attributesOfFileSystem(forPath: "Foo")
|
|
XCTAssertNotNil(dict[.systemSize])
|
|
XCTAssertThrowsError(try $0.attributesOfFileSystem(forPath: "does_not_exist")) {
|
|
XCTAssertEqual(($0 as? CocoaError)?.code, .fileReadNoSuchFile)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testCurrentWorkingDirectory() throws {
|
|
try FileManagerPlayground {
|
|
Directory("dir") {
|
|
"foo"
|
|
}
|
|
"bar"
|
|
}.test {
|
|
XCTAssertEqual(try $0.subpathsOfDirectory(atPath: ".").sorted(), ["bar", "dir", "dir/foo"])
|
|
XCTAssertTrue($0.changeCurrentDirectoryPath("dir"))
|
|
XCTAssertEqual(try $0.subpathsOfDirectory(atPath: "."), ["foo"])
|
|
XCTAssertFalse($0.changeCurrentDirectoryPath("foo"))
|
|
XCTAssertTrue($0.changeCurrentDirectoryPath(".."))
|
|
XCTAssertEqual(try $0.subpathsOfDirectory(atPath: ".").sorted(), ["bar", "dir", "dir/foo"])
|
|
XCTAssertFalse($0.changeCurrentDirectoryPath("does_not_exist"))
|
|
|
|
// Test get current directory path when it's parent directory was removed.
|
|
XCTAssertTrue($0.changeCurrentDirectoryPath("dir"))
|
|
#if os(Windows)
|
|
// Removing the current working directory fails on Windows because the directory is in use.
|
|
XCTAssertThrowsError(try $0.removeItem(atPath: $0.currentDirectoryPath)) {
|
|
XCTAssertEqual(($0 as? CocoaError)?.code, .fileWriteNoPermission)
|
|
}
|
|
#else
|
|
try $0.removeItem(atPath: $0.currentDirectoryPath)
|
|
XCTAssertEqual($0.currentDirectoryPath, "")
|
|
#endif
|
|
}
|
|
}
|
|
|
|
func testBooleanFileAttributes() throws {
|
|
#if canImport(Darwin)
|
|
try FileManagerPlayground {
|
|
"none"
|
|
File("immutable", attributes: [.immutable: true])
|
|
File("appendOnly", attributes: [.appendOnly: true])
|
|
File("immutable_appendOnly", attributes: [.immutable: true, .appendOnly: true])
|
|
}.test {
|
|
let tests: [(path: String, immutable: Bool, appendOnly: Bool)] = [
|
|
("none", false, false),
|
|
("immutable", true, false),
|
|
("appendOnly", false, true),
|
|
("immutable_appendOnly", true, true)
|
|
]
|
|
|
|
for test in tests {
|
|
let result = try $0.attributesOfItem(atPath: test.path)
|
|
XCTAssertEqual(result[.immutable] as? Bool, test.immutable, "Item at path '\(test.path)' did not provide expected result for immutable key")
|
|
XCTAssertEqual(result[.appendOnly] as? Bool, test.appendOnly, "Item at path '\(test.path)' did not provide expected result for appendOnly key")
|
|
|
|
XCTAssertNil(result[.busy], "Item at path '\(test.path)' has non-nil value for .busy attribute") // Should only be set when true
|
|
|
|
// Manually clean up attributes so removal does not fail
|
|
try $0.setAttributes([.immutable: false, .appendOnly: false], ofItemAtPath: test.path)
|
|
}
|
|
}
|
|
#else
|
|
throw XCTSkip("This test is not applicable on this platform")
|
|
#endif
|
|
}
|
|
|
|
func testMalformedModificationDateAttribute() throws {
|
|
let sentinelDate = Date(timeIntervalSince1970: 100)
|
|
try FileManagerPlayground {
|
|
File("foo", attributes: [.modificationDate: sentinelDate])
|
|
}.test {
|
|
XCTAssertEqual(try $0.attributesOfItem(atPath: "foo")[.modificationDate] as? Date, sentinelDate)
|
|
for value in [Double.infinity, -Double.infinity, Double.nan] {
|
|
// Malformed modification dates should be dropped instead of throwing or crashing
|
|
try $0.setAttributes([.modificationDate : Date(timeIntervalSince1970: value)], ofItemAtPath: "foo")
|
|
}
|
|
XCTAssertEqual(try $0.attributesOfItem(atPath: "foo")[.modificationDate] as? Date, sentinelDate)
|
|
}
|
|
}
|
|
|
|
func testImplicitlyConvertibleFileAttributes() throws {
|
|
try FileManagerPlayground {
|
|
File("foo", attributes: [.posixPermissions : UInt16(0o644)])
|
|
}.test {
|
|
let attributes = try $0.attributesOfItem(atPath: "foo")
|
|
|
|
// Ensure the unconventional UInt16 was accepted as input
|
|
#if os(Windows)
|
|
XCTAssertEqual(attributes[.posixPermissions] as? UInt, 0o600)
|
|
#else
|
|
XCTAssertEqual(attributes[.posixPermissions] as? UInt, 0o644)
|
|
#endif
|
|
|
|
#if FOUNDATION_FRAMEWORK
|
|
// Where we have NSNumber, ensure that we can get the value back as an unconventional Double value
|
|
XCTAssertEqual(attributes[.posixPermissions] as? Double, Double(0o644))
|
|
// Ensure that the file type can be converted to a String when it is an ObjC enum
|
|
XCTAssertEqual(attributes[.type] as? String, FileAttributeType.typeRegular.rawValue)
|
|
#endif
|
|
|
|
// Ensure that the file type can be converted to a FileAttributeType when it is an ObjC enum and in swift-foundation
|
|
XCTAssertEqual(attributes[.type] as? FileAttributeType, .typeRegular)
|
|
|
|
}
|
|
}
|
|
|
|
func testStandardizingPathAutomount() throws {
|
|
#if canImport(Darwin)
|
|
let tests = [
|
|
"/private/System" : "/private/System",
|
|
"/private/tmp" : "/tmp",
|
|
"/private/System/foo" : "/private/System/foo"
|
|
]
|
|
for (input, expected) in tests {
|
|
XCTAssertEqual(input.standardizingPath, expected, "Standardizing the path '\(input)' did not produce the expected result")
|
|
}
|
|
#else
|
|
throw XCTSkip("This test is not applicable to this platform")
|
|
#endif
|
|
}
|
|
|
|
func testResolveSymlinksViaGetAttrList() throws {
|
|
#if !canImport(Darwin)
|
|
throw XCTSkip("This test is not applicable on this platform")
|
|
#else
|
|
try FileManagerPlayground {
|
|
"destination"
|
|
}.test {
|
|
try $0.createSymbolicLink(atPath: "link", withDestinationPath: "destination")
|
|
let absolutePath = $0.currentDirectoryPath.appendingPathComponent("link")
|
|
let resolved = absolutePath._resolvingSymlinksInPath() // Call internal function to avoid path standardization
|
|
XCTAssertEqual(resolved, $0.currentDirectoryPath.appendingPathComponent("destination").withFileSystemRepresentation { String(cString: $0!) })
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if os(macOS) && FOUNDATION_FRAMEWORK
|
|
func testSpecialTrashDirectoryTruncation() throws {
|
|
try FileManagerPlayground {}.test {
|
|
if let trashURL = try? $0.url(for: .trashDirectory, in: .allDomainsMask, appropriateFor: nil, create: false) {
|
|
XCTAssertEqual(trashURL.pathComponents.last, ".Trash")
|
|
}
|
|
}
|
|
}
|
|
|
|
func testSpecialTrashDirectoryDuplication() throws {
|
|
try FileManagerPlayground {}.test { fileManager in
|
|
let trashURLs = fileManager.urls(for: .trashDirectory, in: .userDomainMask)
|
|
XCTAssertEqual(trashURLs.count, 1, "There should only be one trash directory for the user domain, found \(trashURLs)")
|
|
}
|
|
}
|
|
#endif
|
|
|
|
func testSearchPaths() throws {
|
|
func assertSearchPaths(_ directories: [FileManager.SearchPathDirectory], exists: Bool, file: StaticString = #filePath, line: UInt = #line) {
|
|
for directory in directories {
|
|
let paths = FileManager.default.urls(for: directory, in: .allDomainsMask)
|
|
XCTAssertEqual(!paths.isEmpty, exists, "Directory \(directory) produced an unexpected number of paths (expected to exist: \(exists), produced: \(paths))", file: file, line: line)
|
|
}
|
|
}
|
|
|
|
// Cross platform paths that always exist
|
|
assertSearchPaths([
|
|
.userDirectory,
|
|
.documentDirectory,
|
|
.autosavedInformationDirectory,
|
|
.autosavedInformationDirectory,
|
|
.desktopDirectory,
|
|
.cachesDirectory,
|
|
.applicationSupportDirectory,
|
|
.downloadsDirectory,
|
|
.moviesDirectory,
|
|
.musicDirectory,
|
|
.sharedPublicDirectory
|
|
], exists: true)
|
|
|
|
#if canImport(Darwin)
|
|
let isDarwin = true
|
|
#else
|
|
let isDarwin = false
|
|
#endif
|
|
|
|
// Darwin-only paths
|
|
assertSearchPaths([
|
|
.applicationDirectory,
|
|
.demoApplicationDirectory,
|
|
.developerApplicationDirectory,
|
|
.adminApplicationDirectory,
|
|
.libraryDirectory,
|
|
.developerDirectory,
|
|
.documentationDirectory,
|
|
.coreServiceDirectory,
|
|
.inputMethodsDirectory,
|
|
.preferencePanesDirectory,
|
|
.allApplicationsDirectory,
|
|
.allLibrariesDirectory,
|
|
.printerDescriptionDirectory
|
|
], exists: isDarwin)
|
|
|
|
#if os(macOS)
|
|
let isMacOS = true
|
|
#else
|
|
let isMacOS = false
|
|
#endif
|
|
|
|
#if FOUNDATION_FRAMEWORK
|
|
let isFramework = true
|
|
#else
|
|
let isFramework = false
|
|
#endif
|
|
|
|
#if os(Windows)
|
|
let isWindows = true
|
|
#else
|
|
let isWindows = false
|
|
#endif
|
|
|
|
// .trashDirectory is unavailable on watchOS/tvOS and only produces paths on macOS (the framework build) + non-Darwin
|
|
#if !os(watchOS) && !os(tvOS)
|
|
assertSearchPaths([.trashDirectory], exists: (isMacOS && isFramework) || (!isDarwin && !isWindows))
|
|
#endif
|
|
|
|
// .picturesDirectory does not exist in CI, though it does exist in user
|
|
// desktop scenarios.
|
|
#if !os(Windows)
|
|
assertSearchPaths([.picturesDirectory], exists: true)
|
|
#endif
|
|
|
|
// .applicationScriptsDirectory is only available on macOS and only produces paths in the framework build
|
|
#if os(macOS)
|
|
assertSearchPaths([.applicationScriptsDirectory], exists: isFramework)
|
|
#endif
|
|
|
|
// .itemReplacementDirectory never exists
|
|
assertSearchPaths([.itemReplacementDirectory], exists: false)
|
|
}
|
|
|
|
func testSearchPaths_XDGEnvironmentVariables() throws {
|
|
#if canImport(Darwin) || os(Windows)
|
|
throw XCTSkip("This test is not applicable on this platform")
|
|
#else
|
|
if let key = ProcessInfo.processInfo.environment.keys.first(where: { $0.starts(with: "XDG") }) {
|
|
throw XCTSkip("Skipping due to presence of '\(key)' environment variable which may affect this test")
|
|
}
|
|
|
|
try FileManagerPlayground {
|
|
Directory("TestPath") {}
|
|
}.test { fileManager in
|
|
func validate(_ key: String, suffix: String? = nil, directory: FileManager.SearchPathDirectory, domain: FileManager.SearchPathDomainMask, file: StaticString = #filePath, line: UInt = #line) {
|
|
let oldValue = ProcessInfo.processInfo.environment[key] ?? ""
|
|
var knownPath = fileManager.currentDirectoryPath.appendingPathComponent("TestPath")
|
|
setenv(key, knownPath, 1)
|
|
defer { setenv(key, oldValue, 1) }
|
|
if let suffix {
|
|
// The suffix is not stored in the environment variable, it is just applied to the expectation
|
|
knownPath = knownPath.appendingPathComponent(suffix)
|
|
}
|
|
let knownURL = URL(filePath: knownPath, directoryHint: .isDirectory)
|
|
let results = fileManager.urls(for: directory, in: domain)
|
|
XCTAssertTrue(results.contains(knownURL), "Results \(results.map(\.path)) did not contain known directory \(knownURL.path) for \(directory)/\(domain) while setting the \(key) environment variable", file: file, line: line)
|
|
}
|
|
|
|
validate("XDG_DATA_HOME", suffix: "Autosave Information", directory: .autosavedInformationDirectory, domain: .userDomainMask)
|
|
validate("HOME", suffix: ".local/share/Autosave Information", directory: .autosavedInformationDirectory, domain: .userDomainMask)
|
|
|
|
validate("XDG_CACHE_HOME", directory: .cachesDirectory, domain: .userDomainMask)
|
|
validate("HOME", suffix: ".cache", directory: .cachesDirectory, domain: .userDomainMask)
|
|
|
|
validate("XDG_DATA_HOME", directory: .applicationSupportDirectory, domain: .userDomainMask)
|
|
validate("HOME", suffix: ".local/share", directory: .applicationSupportDirectory, domain: .userDomainMask)
|
|
|
|
validate("HOME", directory: .userDirectory, domain: .localDomainMask)
|
|
}
|
|
#endif
|
|
}
|
|
|
|
func testGetSetAttributes() throws {
|
|
try FileManagerPlayground {
|
|
File("foo", contents: randomData())
|
|
}.test {
|
|
let attrs = try $0.attributesOfItem(atPath: "foo")
|
|
try $0.setAttributes(attrs, ofItemAtPath: "foo")
|
|
}
|
|
}
|
|
|
|
func testCurrentUserHomeDirectory() throws {
|
|
#if canImport(Darwin) && !os(macOS)
|
|
throw XCTSkip("This test is not applicable on this platform")
|
|
#else
|
|
let userName = ProcessInfo.processInfo.userName
|
|
XCTAssertEqual(FileManager.default.homeDirectory(forUser: userName), FileManager.default.homeDirectoryForCurrentUser)
|
|
#endif
|
|
}
|
|
|
|
func testAttributesOfItemAtPath() throws {
|
|
try FileManagerPlayground {
|
|
"file"
|
|
File("fileWithContents", contents: randomData())
|
|
Directory("directory") {
|
|
"file"
|
|
}
|
|
}.test {
|
|
do {
|
|
let attrs = try $0.attributesOfItem(atPath: "file")
|
|
XCTAssertEqual(attrs[.size] as? UInt, 0)
|
|
XCTAssertEqual(attrs[.type] as? FileAttributeType, FileAttributeType.typeRegular)
|
|
}
|
|
|
|
do {
|
|
let attrs = try $0.attributesOfItem(atPath: "fileWithContents")
|
|
XCTAssertGreaterThan(try XCTUnwrap(attrs[.size] as? UInt), 0)
|
|
XCTAssertEqual(attrs[.type] as? FileAttributeType, FileAttributeType.typeRegular)
|
|
}
|
|
|
|
do {
|
|
let attrs = try $0.attributesOfItem(atPath: "directory")
|
|
XCTAssertEqual(attrs[.type] as? FileAttributeType, FileAttributeType.typeDirectory)
|
|
}
|
|
|
|
do {
|
|
try $0.createSymbolicLink(atPath: "symlink", withDestinationPath: "file")
|
|
let attrs = try $0.attributesOfItem(atPath: "symlink")
|
|
XCTAssertEqual(attrs[.type] as? FileAttributeType, FileAttributeType.typeSymbolicLink)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testHomeDirectoryForNonExistantUser() throws {
|
|
#if canImport(Darwin) && !os(macOS)
|
|
throw XCTSkip("This test is not applicable on this platform")
|
|
#else
|
|
#if os(Windows)
|
|
let fallbackPath = URL(filePath: try XCTUnwrap(ProcessInfo.processInfo.environment["ALLUSERSPROFILE"]), directoryHint: .isDirectory)
|
|
#else
|
|
let fallbackPath = URL(filePath: "/var/empty", directoryHint: .isDirectory)
|
|
#endif
|
|
|
|
XCTAssertEqual(FileManager.default.homeDirectory(forUser: ""), fallbackPath)
|
|
XCTAssertEqual(FileManager.default.homeDirectory(forUser: UUID().uuidString), fallbackPath)
|
|
#endif
|
|
}
|
|
|
|
func testSearchPathsWithoutExpandingTilde() throws {
|
|
#if !canImport(Darwin)
|
|
throw XCTSkip("This test is not applicable for this platform")
|
|
#else
|
|
for path in _DarwinSearchPaths(for: .libraryDirectory, in: .userDomainMask, expandTilde: false) {
|
|
XCTAssertTrue(path.starts(with: "~/"), "Path '\(path)' did not start with ~/ as expected")
|
|
}
|
|
#endif
|
|
}
|
|
|
|
func testWindowsDirectoryCreationCrash() throws {
|
|
try FileManagerPlayground {
|
|
Directory("a\u{301}") {
|
|
|
|
}
|
|
}.test {
|
|
XCTAssertTrue($0.fileExists(atPath: "a\u{301}"))
|
|
let data = randomData()
|
|
XCTAssertTrue($0.createFile(atPath: "a\u{301}/test", contents: data))
|
|
XCTAssertTrue($0.fileExists(atPath: "a\u{301}/test"))
|
|
XCTAssertEqual($0.contents(atPath: "a\u{301}/test"), data)
|
|
}
|
|
}
|
|
}
|