From 1e12ddeb9d412de77de1b380eb25fdda444fc83f Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Thu, 12 Jun 2025 16:57:21 -0700 Subject: [PATCH 1/5] Convert FileManager and URL tests to swift-testing --- .../FileManager/FileManagerTests.swift | 929 +++++++------ ...rPlayground.swift => FilePlayground.swift} | 68 +- .../FoundationEssentialsTests/URLTests.swift | 1207 ++++++++--------- 3 files changed, 1120 insertions(+), 1084 deletions(-) rename Tests/FoundationEssentialsTests/FileManager/{FileManagerPlayground.swift => FilePlayground.swift} (64%) diff --git a/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift b/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift index 29c94c5fa..7d22915d2 100644 --- a/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift +++ b/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift @@ -10,16 +10,15 @@ // //===----------------------------------------------------------------------===// +import Testing #if canImport(TestSupport) import TestSupport -#endif // canImport(TestSupport) +#endif #if canImport(FoundationEssentials) @testable import FoundationEssentials -#endif - -#if FOUNDATION_FRAMEWORK +#else @testable import Foundation #endif @@ -179,22 +178,48 @@ final class CapturingFileManagerDelegate : FileManagerDelegate, Sendable { } #endif -final class FileManagerTests : XCTestCase { +@Suite("FileManager") +private struct FileManagerTests { + + private static var isUnixRoot: Bool { + #if !os(Windows) + getuid() == 0 + #else + false + #endif + } + + private static var isWindows: Bool { + #if os(Windows) + true + #else + false + #endif + } + + private static var isDarwin: Bool { + #if canImport(Darwin) + true + #else + false + #endif + } + private func randomData(count: Int = 10000) -> Data { Data((0 ..< count).map { _ in UInt8.random(in: .min ..< .max) }) } - func testContentsAtPath() throws { + @Test func contentsAtPath() async throws { let data = randomData() - try FileManagerPlayground { + try await FilePlayground { File("test", contents: data) }.test { - XCTAssertEqual($0.contents(atPath: "test"), data) + #expect($0.contents(atPath: "test") == data) } } - func testContentsEqualAtPaths() throws { - try FileManagerPlayground { + @Test func contentsEqualAtPaths() async throws { + try await FilePlayground { Directory("dir1") { Directory("dir2") { "Foo" @@ -229,18 +254,18 @@ final class FileManagerTests : XCTestCase { 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") + #expect($0.contentsEqual(atPath: "dir1", andPath: "dir1_copy")) + #expect(!$0.contentsEqual(atPath: "dir1/dir2", andPath: "dir1/dir3")) + #expect(!$0.contentsEqual(atPath: "dir1", andPath: "dir1_diffdata")) + #expect(!$0.contentsEqual(atPath: "symlinks/LinkToFoo", andPath: "symlinks/Foo"), "Symbolic link should not be equal to its destination") + #expect(!$0.contentsEqual(atPath: "symlinks/LinkToFoo", andPath: "EmptyFile"), "Symbolic link should not be equal to an empty file") + #expect(!$0.contentsEqual(atPath: "symlinks/LinkToFoo", andPath: "EmptyDirectory"), "Symbolic link should not be equal to an empty directory") + #expect(!$0.contentsEqual(atPath: "symlinks/EmptyDirectory", andPath: "EmptyFile"), "Empty directory should not be equal to empty file") } } - - func testDirectoryContentsAtPath() throws { - try FileManagerPlayground { + + @Test func directoryContentsAtPath() async throws { + try await FilePlayground { Directory("dir1") { Directory("dir2") { "Foo" @@ -250,18 +275,21 @@ final class FileManagerTests : XCTestCase { "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) + }.test { fileManager in + #expect(try fileManager.contentsOfDirectory(atPath: "dir1").sorted() == ["dir2", "dir3"]) + #expect(try fileManager.contentsOfDirectory(atPath: "dir1/dir2").sorted() == ["Bar", "Foo"]) + let contents = try fileManager.contentsOfDirectory(atPath: "dir1/dir3").sorted() + #expect(contents == ["Baz"]) + #expect { + try fileManager.contentsOfDirectory(atPath: "does_not_exist") + } throws: { + ($0 as? CocoaError)?.code == .fileReadNoSuchFile } } } - - func testSubpathsOfDirectoryAtPath() throws { - try FileManagerPlayground { + + @Test func subpathsOfDirectoryAtPath() async throws { + try await FilePlayground { Directory("dir1") { Directory("dir2") { "Foo" @@ -276,166 +304,187 @@ final class FileManagerTests : XCTestCase { 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) + }.test { fileManager in + #expect(try fileManager.subpathsOfDirectory(atPath: "dir1").sorted() == ["dir2", "dir2/Bar", "dir2/Foo", "dir3", "dir3/Baz"]) + #expect(try fileManager.subpathsOfDirectory(atPath: "dir1/dir2").sorted() == ["Bar", "Foo"]) + #expect(try fileManager.subpathsOfDirectory(atPath: "dir1/dir3").sorted() == ["Baz"]) + + #expect(try fileManager.subpathsOfDirectory(atPath: "symlinks").sorted() == ["Bar", "Foo", "Parent"]) + + #expect { + try fileManager.subpathsOfDirectory(atPath: "does_not_exist") + } throws: { + ($0 as? CocoaError)?.code == .fileReadNoSuchFile } - XCTAssertThrowsError(try $0.subpathsOfDirectory(atPath: "")) { + #expect { + try fileManager.subpathsOfDirectory(atPath: "") + } throws: { #if os(Windows) - XCTAssertEqual(($0 as? CocoaError)?.code, .fileReadInvalidFileName) + ($0 as? CocoaError)?.code == .fileReadInvalidFileName #else - XCTAssertEqual(($0 as? CocoaError)?.code, .fileReadNoSuchFile) + ($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 cwd = fileManager.currentDirectoryPath + #expect(cwd.last != "/") let paths = [cwd, "\(cwd)/", "\(cwd)//", ".", "./", ".//"] for path in paths { - XCTAssertEqual(try $0.subpathsOfDirectory(atPath: path).sorted(), fullContents) + let subpaths = try fileManager.subpathsOfDirectory(atPath: path).sorted() + #expect(subpaths == fullContents) } - + } } - - func testCreateDirectoryAtPath() throws { - try FileManagerPlayground { + + @Test func createDirectoryAtPath() async throws { + try await FilePlayground { "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)) - + }.test { fileManager in + try fileManager.createDirectory(atPath: "create_dir_test", withIntermediateDirectories: false) + #expect(try fileManager.contentsOfDirectory(atPath: ".").sorted() == ["create_dir_test", "preexisting_file"]) + try fileManager.createDirectory(atPath: "create_dir_test2/nested", withIntermediateDirectories: true) + #expect(try fileManager.contentsOfDirectory(atPath: "create_dir_test2") == ["nested"]) + try fileManager.createDirectory(atPath: "create_dir_test2/nested2", withIntermediateDirectories: true) + #expect(try fileManager.contentsOfDirectory(atPath: "create_dir_test2").sorted() == ["nested", "nested2"]) + #expect(throws: Never.self) { + try fileManager.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"]) + try fileManager.createDirectory(atPath: "create_dir_test3\\nested", withIntermediateDirectories: true) + #expect(try fileManager.contentsOfDirectory(atPath: "create_dir_test3") == ["nested"]) #endif - - XCTAssertThrowsError(try $0.createDirectory(atPath: "create_dir_test", withIntermediateDirectories: false)) { - XCTAssertEqual(($0 as? CocoaError)?.code, .fileWriteFileExists) + + #expect { + try fileManager.createDirectory(atPath: "create_dir_test", withIntermediateDirectories: false) + } throws: { + ($0 as? CocoaError)?.code == .fileWriteFileExists } - XCTAssertThrowsError(try $0.createDirectory(atPath: "create_dir_test4/nested", withIntermediateDirectories: false)) { - XCTAssertEqual(($0 as? CocoaError)?.code, .fileNoSuchFile) + #expect { + try fileManager.createDirectory(atPath: "create_dir_test4/nested", withIntermediateDirectories: false) + } throws: { + ($0 as? CocoaError)?.code == .fileNoSuchFile } - XCTAssertThrowsError(try $0.createDirectory(atPath: "preexisting_file", withIntermediateDirectories: false)) { - XCTAssertEqual(($0 as? CocoaError)?.code, .fileWriteFileExists) + #expect { + try fileManager.createDirectory(atPath: "preexisting_file", withIntermediateDirectories: false) + } throws: { + ($0 as? CocoaError)?.code == .fileWriteFileExists } - XCTAssertThrowsError(try $0.createDirectory(atPath: "preexisting_file", withIntermediateDirectories: true)) { - XCTAssertEqual(($0 as? CocoaError)?.code, .fileWriteFileExists) + #expect { + try fileManager.createDirectory(atPath: "preexisting_file", withIntermediateDirectories: true) + } throws: { + ($0 as? CocoaError)?.code == .fileWriteFileExists } } } - - func testLinkFileAtPathToPath() throws { - try FileManagerPlayground { + + @Test func linkFileAtPathToPath() async throws { + try await FilePlayground { "foo" }.test(captureDelegateCalls: true) { - XCTAssertTrue($0.delegateCaptures.isEmpty) + #expect($0.delegateCaptures.isEmpty) try $0.linkItem(atPath: "foo", toPath: "bar") - XCTAssertEqual($0.delegateCaptures.shouldLink, [.init("foo", "bar")]) + #expect($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")) + #expect($0.delegateCaptures.shouldProceedAfterLinkError == [.init("foo", "bar", code: .fileWriteNoPermission)]) + #expect(!$0.fileExists(atPath: "bar")) #else - XCTAssertEqual($0.delegateCaptures.shouldProceedAfterLinkError, []) - XCTAssertTrue($0.fileExists(atPath: "bar")) + #expect($0.delegateCaptures.shouldProceedAfterLinkError == []) + #expect($0.fileExists(atPath: "bar")) #endif } - - try FileManagerPlayground { + + try await FilePlayground { "foo" "bar" }.test(captureDelegateCalls: true) { - XCTAssertTrue($0.delegateCaptures.isEmpty) + #expect($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)]) + #expect($0.delegateCaptures.shouldLink == [.init("foo", "bar")]) + #expect($0.delegateCaptures.shouldProceedAfterLinkError == [.init("foo", "bar", code: .fileWriteFileExists)]) } } - - func testCopyFileAtPathToPath() throws { - try FileManagerPlayground { + + @Test func copyFileAtPathToPath() async throws { + try await FilePlayground { "foo" }.test(captureDelegateCalls: true) { - XCTAssertTrue($0.delegateCaptures.isEmpty) + #expect($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")) + #expect($0.delegateCaptures.shouldCopy == [.init("foo", "bar")]) + #expect($0.delegateCaptures.shouldProceedAfterCopyError == []) + #expect($0.fileExists(atPath: "bar")) } - - try FileManagerPlayground { + + try await FilePlayground { "foo" "bar" }.test(captureDelegateCalls: true) { - XCTAssertTrue($0.delegateCaptures.isEmpty) + #expect($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)]) + #expect($0.delegateCaptures.shouldCopy == [.init("foo", "bar")]) + #expect($0.delegateCaptures.shouldProceedAfterCopyError == [.init("foo", "bar", code: .fileWriteFileExists)]) } - - try FileManagerPlayground { + + try await FilePlayground { "foo" SymbolicLink("bar", destination: "foo") }.test(captureDelegateCalls: true) { - XCTAssertTrue($0.delegateCaptures.isEmpty) + #expect($0.delegateCaptures.isEmpty) try $0.copyItem(atPath: "bar", toPath: "copy") - XCTAssertEqual($0.delegateCaptures.shouldCopy, [.init("bar", "copy")]) - XCTAssertEqual($0.delegateCaptures.shouldProceedAfterCopyError, []) + #expect($0.delegateCaptures.shouldCopy == [.init("bar", "copy")]) + #expect($0.delegateCaptures.shouldProceedAfterCopyError == []) let copyDestination = try $0.destinationOfSymbolicLink(atPath: "copy") - XCTAssertEqual(copyDestination.lastPathComponent, "foo", "Copied symbolic link points at \(copyDestination) instead of foo") + #expect(copyDestination.lastPathComponent == "foo", "Copied symbolic link points at \(copyDestination) instead of foo") } - try FileManagerPlayground { + try await FilePlayground { Directory("dir") { "foo" } SymbolicLink("link", destination: "dir") }.test(captureDelegateCalls: true) { - XCTAssertTrue($0.delegateCaptures.isEmpty) + #expect($0.delegateCaptures.isEmpty) try $0.copyItem(atPath: "link", toPath: "copy") - XCTAssertEqual($0.delegateCaptures.shouldCopy, [.init("link", "copy")]) - XCTAssertEqual($0.delegateCaptures.shouldProceedAfterCopyError, []) + #expect($0.delegateCaptures.shouldCopy == [.init("link", "copy")]) + #expect($0.delegateCaptures.shouldProceedAfterCopyError == []) let copyDestination = try $0.destinationOfSymbolicLink(atPath: "copy") - XCTAssertEqual(copyDestination.lastPathComponent, "dir", "Copied symbolic link points at \(copyDestination) instead of foo") + #expect(copyDestination.lastPathComponent == "dir", "Copied symbolic link points at \(copyDestination) instead of foo") } } - - func testCreateSymbolicLinkAtPath() throws { - try FileManagerPlayground { + + @Test func createSymbolicLinkAtPath() async throws { + try await FilePlayground { "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) + }.test { fileManager in + try fileManager.createSymbolicLink(atPath: "bar", withDestinationPath: "foo") + #expect(try fileManager.destinationOfSymbolicLink(atPath: "bar") == "foo") + + try fileManager.createSymbolicLink(atPath: "dir_link", withDestinationPath: "dir") + #expect(try fileManager.destinationOfSymbolicLink(atPath: "dir_link") == "dir") + + #expect { + try fileManager.createSymbolicLink(atPath: "bar", withDestinationPath: "foo") + } throws: { + ($0 as? CocoaError)?.code == .fileWriteFileExists } - XCTAssertThrowsError(try $0.createSymbolicLink(atPath: "foo", withDestinationPath: "baz")) { - XCTAssertEqual(($0 as? CocoaError)?.code, .fileWriteFileExists) + #expect { + try fileManager.createSymbolicLink(atPath: "foo", withDestinationPath: "baz") + } throws: { + ($0 as? CocoaError)?.code == .fileWriteFileExists } - XCTAssertThrowsError(try $0.destinationOfSymbolicLink(atPath: "foo")) { - XCTAssertEqual(($0 as? CocoaError)?.code, .fileReadUnknown) + #expect { + try fileManager.destinationOfSymbolicLink(atPath: "foo") + } throws: { + ($0 as? CocoaError)?.code == .fileReadUnknown } } - try FileManagerPlayground { + try await FilePlayground { Directory("dir") { Directory("other_dir") { "file" @@ -446,120 +495,122 @@ final class FileManagerTests : XCTestCase { 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"]) - + #expect(try $0.destinationOfSymbolicLink(atPath: "dir/link") == "other_dir") + #expect(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"]) + #expect(try $0.destinationOfSymbolicLink(atPath: "dir/link2") == "other_dir") + #expect(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"]) + #expect(try $0.destinationOfSymbolicLink(atPath: "dir/link3") == absolute.withFileSystemRepresentation { String(cString: $0!) }) + #expect(try $0.contentsOfDirectory(atPath: "dir/link3") == ["file"]) } } } - - func testMoveItemAtPathToPath() throws { + + @Test func moveItemAtPathToPath() async throws { let data = randomData() - try FileManagerPlayground { + try await FilePlayground { Directory("dir") { File("foo", contents: data) "bar" } "other_file" }.test(captureDelegateCalls: true) { - XCTAssertTrue($0.delegateCaptures.isEmpty) + #expect($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) + #expect(try $0.subpathsOfDirectory(atPath: ".").sorted() == ["dir2", "dir2/bar", "dir2/foo", "other_file"]) + #expect($0.contents(atPath: "dir2/foo") == data) let rootDir = URL(fileURLWithPath: $0.currentDirectoryPath).path - XCTAssertEqual($0.delegateCaptures.shouldMove, [.init("\(rootDir)/dir", "\(rootDir)/dir2")]) + #expect($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)]) + #expect($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))) + #expect($0.delegateCaptures.shouldProceedAfterMoveError.contains(.init("\(rootDir)/dir2", "\(rootDir)/other_file", code: .fileWriteFileExists))) } } - - func testCopyItemAtPathToPath() throws { + + @Test func copyItemAtPathToPath() async throws { let data = randomData() - try FileManagerPlayground { + try await FilePlayground { Directory("dir", attributes: [.posixPermissions : 0o777]) { 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) + }.test(captureDelegateCalls: true) { fileManager in + #expect(fileManager.delegateCaptures.isEmpty) + try fileManager.copyItem(atPath: "dir", toPath: "dir2") + #expect(try fileManager.subpathsOfDirectory(atPath: ".").sorted() == ["dir", "dir/bar", "dir/foo", "dir2", "dir2/bar", "dir2/foo", "other_file"]) + #expect(fileManager.contents(atPath: "dir/foo") == data) + #expect(fileManager.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")]) + #expect(fileManager.delegateCaptures.shouldCopy == [.init("dir", "dir2"), .init("dir/bar", "dir2/bar"), .init("dir/foo", "dir2/foo")]) #else - var shouldCopy = $0.delegateCaptures.shouldCopy - XCTAssertEqual(shouldCopy.removeFirst(), .init("dir", "dir2")) - XCTAssertEqual(shouldCopy.sorted(), [.init("dir/foo", "dir2/foo"), .init("dir/bar", "dir2/bar")].sorted()) - + var shouldCopy = fileManager.delegateCaptures.shouldCopy + #expect(shouldCopy.removeFirst() == .init("dir", "dir2")) + #expect(shouldCopy.sorted() == [.init("dir/foo", "dir2/foo"), .init("dir/bar", "dir2/bar")].sorted()) + // Specifically for non-Windows (where copying directory metadata takes a special path) double check that the metadata was copied exactly - XCTAssertEqual(try $0.attributesOfItem(atPath: "dir2")[.posixPermissions] as? UInt, 0o777) + #expect(try fileManager.attributesOfItem(atPath: "dir2")[.posixPermissions] as? UInt == 0o777) #endif - XCTAssertThrowsError(try $0.copyItem(atPath: "does_not_exist", toPath: "dir3")) { - XCTAssertEqual(($0 as? CocoaError)?.code, .fileReadNoSuchFile) + #expect { + try fileManager.copyItem(atPath: "does_not_exist", toPath: "dir3") + } throws: { + ($0 as? CocoaError)?.code == .fileReadNoSuchFile } - - try $0.copyItem(atPath: "dir", toPath: "other_file") - XCTAssertTrue($0.delegateCaptures.shouldProceedAfterCopyError.contains(.init("dir", "other_file", code: .fileWriteFileExists))) + + try fileManager.copyItem(atPath: "dir", toPath: "other_file") + #expect(fileManager.delegateCaptures.shouldProceedAfterCopyError.contains(.init("dir", "other_file", code: .fileWriteFileExists))) } } - - func testRemoveItemAtPath() throws { - try FileManagerPlayground { + + @Test func removeItemAtPath() async throws { + try await FilePlayground { Directory("dir") { "foo" "bar" } "other" }.test(captureDelegateCalls: true) { - XCTAssertTrue($0.delegateCaptures.isEmpty) + #expect($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, []) + #expect(try $0.subpathsOfDirectory(atPath: ".").sorted() == ["dir", "dir/foo", "other"]) + #expect($0.delegateCaptures.shouldRemove == [.init("dir/bar")]) + #expect($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, []) - + #expect(try $0.subpathsOfDirectory(atPath: ".").sorted() == ["other"]) + #expect($0.delegateCaptures.shouldRemove == [.init("dir/bar"), .init("\(rootDir)/dir"), .init("\(rootDir)/dir/foo")]) + #expect($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, []) - + #expect(try $0.subpathsOfDirectory(atPath: ".").sorted() == []) + #expect($0.delegateCaptures.shouldRemove == [.init("dir/bar"), .init("\(rootDir)/dir"), .init("\(rootDir)/dir/foo"), .init("other")]) + #expect($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)]) + #expect($0.delegateCaptures.shouldRemove == [.init("dir/bar"), .init("\(rootDir)/dir"), .init("\(rootDir)/dir/foo"), .init("other"), .init("does_not_exist")]) + #expect($0.delegateCaptures.shouldProceedAfterRemoveError == [.init("does_not_exist", code: .fileNoSuchFile)]) } - try FileManagerPlayground { + try await FilePlayground { Directory("dir") { Directory("dir2") { "file" @@ -568,65 +619,67 @@ final class FileManagerTests : XCTestCase { }.test(captureDelegateCalls: true) { let rootDir = URL(fileURLWithPath: $0.currentDirectoryPath).path - XCTAssertTrue($0.delegateCaptures.isEmpty) + #expect($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, []) + #expect(try $0.subpathsOfDirectory(atPath: ".").sorted() == []) + #expect($0.delegateCaptures.shouldRemove == [.init("\(rootDir)/dir"), .init("\(rootDir)/dir/dir2"), .init("\(rootDir)/dir/dir2/file")]) + #expect($0.delegateCaptures.shouldProceedAfterRemoveError == []) } #if canImport(Darwin) // not supported on linux as the test depends on FileManager.removeItem calling removefile(3) - try FileManagerPlayground { - }.test { + try await FilePlayground { + }.test { fileManager in // Create hierarchy in which the leaf is a long path (length > PATH_MAX) - let rootDir = $0.currentDirectoryPath + let rootDir = fileManager.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)) + + try fileManager.createDirectory(atPath: aas, withIntermediateDirectories: true) + #expect(fileManager.changeCurrentDirectoryPath(aas)) + try fileManager.createDirectory(atPath: bbs, withIntermediateDirectories: true) + #expect(fileManager.changeCurrentDirectoryPath(bbs)) + try fileManager.createDirectory(atPath: ccs, withIntermediateDirectories: true) + #expect(fileManager.changeCurrentDirectoryPath(ccs)) + try fileManager.createDirectory(atPath: dds, withIntermediateDirectories: true) + #expect(fileManager.changeCurrentDirectoryPath(dds)) + try fileManager.createDirectory(atPath: ees, withIntermediateDirectories: true) + #expect(fileManager.changeCurrentDirectoryPath(ees)) + try fileManager.createDirectory(atPath: leaf, withIntermediateDirectories: true) + + #expect(fileManager.changeCurrentDirectoryPath(rootDir)) let fullPath = "\(aas)/\(bbs)/\(ccs)/\(dds)/\(ees)/\(leaf)" - XCTAssertThrowsError(try $0.removeItem(atPath: fullPath)) { + #expect { + try fileManager.removeItem(atPath: fullPath) + } throws: { let underlyingPosixError = ($0 as? CocoaError)?.underlying as? POSIXError - XCTAssertEqual(underlyingPosixError?.code, .ENAMETOOLONG, "removeItem didn't fail with ENAMETOOLONG; produced error: \($0)") + return underlyingPosixError?.code == .ENAMETOOLONG } - + // 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) + #expect(fileManager.changeCurrentDirectoryPath(aas)) + #expect(fileManager.changeCurrentDirectoryPath(bbs)) + #expect(fileManager.changeCurrentDirectoryPath(ccs)) + #expect(fileManager.changeCurrentDirectoryPath(dds)) + try fileManager.removeItem(atPath: ees) + #expect(fileManager.changeCurrentDirectoryPath("..")) + try fileManager.removeItem(atPath: dds) + #expect(fileManager.changeCurrentDirectoryPath("..")) + try fileManager.removeItem(atPath: ccs) + #expect(fileManager.changeCurrentDirectoryPath("..")) + try fileManager.removeItem(atPath: bbs) + #expect(fileManager.changeCurrentDirectoryPath("..")) + try fileManager.removeItem(atPath: aas) } #endif } - - func testFileExistsAtPath() throws { - try FileManagerPlayground { + + @Test func fileExistsAtPath() async throws { + try await FilePlayground { Directory("dir") { "foo" "bar" @@ -647,32 +700,26 @@ final class FileManagerTests : XCTestCase { 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")) + #expect($0.fileExists(atPath: "dir/foo", isDirectory: &isDir)) + #expect(!isDirBool()) + #expect($0.fileExists(atPath: "dir/bar", isDirectory: &isDir)) + #expect(!isDirBool()) + #expect($0.fileExists(atPath: "dir", isDirectory: &isDir)) + #expect(isDirBool()) + #expect($0.fileExists(atPath: "other", isDirectory: &isDir)) + #expect(!isDirBool()) + #expect($0.fileExists(atPath: "link_to_file", isDirectory: &isDir)) + #expect(!isDirBool()) + #expect($0.fileExists(atPath: "link_to_dir", isDirectory: &isDir)) + #expect(isDirBool()) + #expect(!$0.fileExists(atPath: "does_not_exist")) + #expect(!$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 { + + @Test(.disabled(if: isUnixRoot, "This test is not available when running as the root user - root users can always access anything")) + func fileAccessAtPath() async throws { + try await FilePlayground { File("000", attributes: [.posixPermissions: 0o000]) File("111", attributes: [.posixPermissions: 0o111]) File("222", attributes: [.posixPermissions: 0o222]) @@ -694,63 +741,67 @@ final class FileManagerTests : XCTestCase { 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") + #expect($0.isReadableFile(atPath: file) == readable.contains(file), "'\(file)' failed readable check") + #expect($0.isWritableFile(atPath: file) == writable.contains(file), "'\(file)' failed writable check") + #expect($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") + #expect($0.isDeletableFile(atPath: file) == writable.contains(file), "'\(file)' failed deletable check") #else - XCTAssertTrue($0.isDeletableFile(atPath: file), "'\(file)' failed deletable check") + #expect($0.isDeletableFile(atPath: file), "'\(file)' failed deletable check") #endif } } } - func testFileSystemAttributesAtPath() throws { - try FileManagerPlayground { + @Test func fileSystemAttributesAtPath() async throws { + try await FilePlayground { "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) + }.test { fileManager in + let dict = try fileManager.attributesOfFileSystem(forPath: "Foo") + #expect(dict[.systemSize] != nil) + #expect { + try fileManager.attributesOfFileSystem(forPath: "does_not_exist") + } throws: { + ($0 as? CocoaError)?.code == .fileReadNoSuchFile } } } - - func testCurrentWorkingDirectory() throws { - try FileManagerPlayground { + + @Test func currentWorkingDirectory() async throws { + try await FilePlayground { 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")) - + #expect(try $0.subpathsOfDirectory(atPath: ".").sorted() == ["bar", "dir", "dir/foo"]) + #expect($0.changeCurrentDirectoryPath("dir")) + #expect(try $0.subpathsOfDirectory(atPath: ".") == ["foo"]) + #expect(!$0.changeCurrentDirectoryPath("foo")) + #expect($0.changeCurrentDirectoryPath("..")) + #expect(try $0.subpathsOfDirectory(atPath: ".").sorted() == ["bar", "dir", "dir/foo"]) + #expect(!$0.changeCurrentDirectoryPath("does_not_exist")) + // Test get current directory path when it's parent directory was removed. - XCTAssertTrue($0.changeCurrentDirectoryPath("dir")) + #expect($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) + #expect { + try $0.removeItem(atPath: $0.currentDirectoryPath) + } throws: { + ($0 as? CocoaError)?.code == .fileWriteNoPermission } #else try $0.removeItem(atPath: $0.currentDirectoryPath) - XCTAssertEqual($0.currentDirectoryPath, "") + #expect($0.currentDirectoryPath == "") #endif } } - - func testBooleanFileAttributes() throws { - #if canImport(Darwin) - try FileManagerPlayground { + + @Test(.enabled(if: isDarwin, "This test is not applicable on this platform")) + func booleanFileAttributes() async throws { + try await FilePlayground { "none" File("immutable", attributes: [.immutable: true]) File("appendOnly", attributes: [.appendOnly: true]) @@ -762,132 +813,124 @@ final class FileManagerTests : XCTestCase { ("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 - + #expect(result[.immutable] as? Bool == test.immutable, "Item at path '\(test.path)' did not provide expected result for immutable key") + #expect(result[.appendOnly] as? Bool == test.appendOnly, "Item at path '\(test.path)' did not provide expected result for appendOnly key") + + #expect(result[.busy] == nil, "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 { + + @Test func malformedModificationDateAttribute() async throws { let sentinelDate = Date(timeIntervalSince1970: 100) - try FileManagerPlayground { + try await FilePlayground { File("foo", attributes: [.modificationDate: sentinelDate]) }.test { - XCTAssertEqual(try $0.attributesOfItem(atPath: "foo")[.modificationDate] as? Date, sentinelDate) + #expect(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) + #expect(try $0.attributesOfItem(atPath: "foo")[.modificationDate] as? Date == sentinelDate) } } - - func testRoundtripModificationDate() throws { - try FileManagerPlayground { + + @Test func roundtripModificationDate() async throws { + try await FilePlayground { "foo" }.test { // Precision of modification dates is dependent not only on the platform, but on the file system used // Ensure that roundtripping supports at least millisecond-level precision, but some file systems may support more up to nanosecond precision let date = Date(timeIntervalSince1970: 10.003) try $0.setAttributes([.modificationDate : date], ofItemAtPath: "foo") - let readValue = try XCTUnwrap($0.attributesOfItem(atPath: "foo")[.modificationDate], "No value provided for file modification date") - let readDate = try XCTUnwrap(readValue as? Date, "File modification date was not a date (found type \(type(of: readValue)))") - XCTAssertEqual(readDate.timeIntervalSince1970, date.timeIntervalSince1970, accuracy: 0.0005, "File modification date (\(readDate.timeIntervalSince1970)) does not match expected modification date (\(date.timeIntervalSince1970))") + let readValue = try #require($0.attributesOfItem(atPath: "foo")[.modificationDate], "No value provided for file modification date") + let possibleDate = readValue as? Date + let readDate = try #require(possibleDate, "File modification date was not a date (found type \(type(of: readValue)))") + #expect(abs(readDate.timeIntervalSince1970 - date.timeIntervalSince1970) <= 0.0005, "File modification date (\(readDate.timeIntervalSince1970)) does not match expected modification date (\(date.timeIntervalSince1970))") } } - - func testImplicitlyConvertibleFileAttributes() throws { - try FileManagerPlayground { + + @Test func implicitlyConvertibleFileAttributes() async throws { + try await FilePlayground { 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) + #expect(attributes[.posixPermissions] as? UInt == 0o600) #else - XCTAssertEqual(attributes[.posixPermissions] as? UInt, 0o644) + #expect(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)) + #expect(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) + #expect(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) - + #expect(attributes[.type] as? FileAttributeType == .typeRegular) + } } - - func testStandardizingPathAutomount() throws { - #if canImport(Darwin) + + @Test(.enabled(if: isDarwin, "This test is not applicable on this platform")) + func standardizingPathAutomount() async throws { 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") + #expect(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 { + + @Test(.enabled(if: isDarwin, "This test is not applicable on this platform")) + func resolveSymlinksViaGetAttrList() async throws { + try await FilePlayground { "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!) }) + #expect(resolved == $0.currentDirectoryPath.appendingPathComponent("destination").withFileSystemRepresentation { String(cString: $0!) }) } - #endif } - + #if os(macOS) && FOUNDATION_FRAMEWORK - func testSpecialTrashDirectoryTruncation() throws { - try FileManagerPlayground {}.test { + @Test func specialTrashDirectoryTruncation() async throws { + try await FilePlayground {}.test { if let trashURL = try? $0.url(for: .trashDirectory, in: .allDomainsMask, appropriateFor: nil, create: false) { - XCTAssertEqual(trashURL.pathComponents.last, ".Trash") + #expect(trashURL.pathComponents.last == ".Trash") } } } - - func testSpecialTrashDirectoryDuplication() throws { - try FileManagerPlayground {}.test { fileManager in + + @Test func specialTrashDirectoryDuplication() async throws { + try await FilePlayground {}.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)") + #expect(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) { + + @Test func searchPaths() async throws { + func assertSearchPaths(_ directories: [FileManager.SearchPathDirectory], exists: Bool, sourceLocation: SourceLocation = #_sourceLocation) { 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) + #expect(!paths.isEmpty == exists, "Directory \(directory) produced an unexpected number of paths (expected to exist: \(exists), produced: \(paths))", sourceLocation: sourceLocation) } } - + // Cross platform paths that always exist assertSearchPaths([ .userDirectory, @@ -902,13 +945,13 @@ final class FileManagerTests : XCTestCase { .musicDirectory, .sharedPublicDirectory ], exists: true) - + #if canImport(Darwin) let isDarwin = true #else let isDarwin = false #endif - + // Darwin-only paths assertSearchPaths([ .applicationDirectory, @@ -925,13 +968,13 @@ final class FileManagerTests : XCTestCase { .allLibrariesDirectory, .printerDescriptionDirectory ], exists: isDarwin) - + #if os(macOS) let isMacOS = true #else let isMacOS = false #endif - + #if FOUNDATION_FRAMEWORK let isFramework = true #else @@ -943,7 +986,7 @@ final class FileManagerTests : XCTestCase { #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)) @@ -954,28 +997,26 @@ final class FileManagerTests : XCTestCase { #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 { + + #if canImport(Darwin) || os(Windows) + @Test(.disabled("This test is not applicable on this platform")) + #else + @Test(.disabled(if: ProcessInfo.processInfo.environment.keys.first(where: { $0.starts(with: "XDG") }), "Skipping due to presence of XDG environment variables which may affect this test")) + #endif + func searchPaths_XDGEnvironmentVariables() async throws { + try await FilePlayground { Directory("TestPath") {} }.test { fileManager in - func validate(_ key: String, suffix: String? = nil, directory: FileManager.SearchPathDirectory, domain: FileManager.SearchPathDomainMask, file: StaticString = #filePath, line: UInt = #line) { + func validate(_ key: String, suffix: String? = nil, directory: FileManager.SearchPathDirectory, domain: FileManager.SearchPathDomainMask, sourceLocation: SourceLocation = #_sourceLocation) { let oldValue = ProcessInfo.processInfo.environment[key] ?? "" var knownPath = fileManager.currentDirectoryPath.appendingPathComponent("TestPath") setenv(key, knownPath, 1) @@ -986,7 +1027,7 @@ final class FileManagerTests : XCTestCase { } 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) + #expect(results.contains(knownURL), "Results \(results.map(\.path)) did not contain known directory \(knownURL.path) for \(directory)/\(domain) while setting the \(key) environment variable", sourceLocation: sourceLocation) } validate("XDG_DATA_HOME", suffix: "Autosave Information", directory: .autosavedInformationDirectory, domain: .userDomainMask) @@ -994,17 +1035,16 @@ final class FileManagerTests : XCTestCase { 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 { + + @Test func getSetAttributes() async throws { + try await FilePlayground { File("foo", contents: randomData()) }.test { let attrs = try $0.attributesOfItem(atPath: "foo") @@ -1012,17 +1052,15 @@ final class FileManagerTests : XCTestCase { } } - func testCurrentUserHomeDirectory() throws { - #if canImport(Darwin) && !os(macOS) - throw XCTSkip("This test is not applicable on this platform") - #else + #if !canImport(Darwin) || os(macOS) + @Test func currentUserHomeDirectory() async throws { let userName = ProcessInfo.processInfo.userName - XCTAssertEqual(FileManager.default.homeDirectory(forUser: userName), FileManager.default.homeDirectoryForCurrentUser) - #endif + #expect(FileManager.default.homeDirectory(forUser: userName) == FileManager.default.homeDirectoryForCurrentUser) } - - func testAttributesOfItemAtPath() throws { - try FileManagerPlayground { + #endif + + @Test func attributesOfItemAtPath() async throws { + try await FilePlayground { "file" File("fileWithContents", contents: randomData()) Directory("directory") { @@ -1031,147 +1069,148 @@ final class FileManagerTests : XCTestCase { }.test { do { let attrs = try $0.attributesOfItem(atPath: "file") - XCTAssertEqual(attrs[.size] as? UInt, 0) - XCTAssertEqual(attrs[.type] as? FileAttributeType, FileAttributeType.typeRegular) + #expect(attrs[.size] as? UInt == 0) + #expect(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) + XCTAssertGreaterThan(try #require(attrs[.size] as? UInt), 0) + #expect(attrs[.type] as? FileAttributeType == FileAttributeType.typeRegular) } - + do { let attrs = try $0.attributesOfItem(atPath: "directory") - XCTAssertEqual(attrs[.type] as? FileAttributeType, FileAttributeType.typeDirectory) + #expect(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) + #expect(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 - XCTAssertNil(FileManager.default.homeDirectory(forUser: "")) - XCTAssertNil(FileManager.default.homeDirectory(forUser: UUID().uuidString)) - #endif + + #if !canImport(Darwin) || os(macOS) + @Test func homeDirectoryForNonExistantUser() async throws { + #expect(FileManager.default.homeDirectory(forUser: "") == nil) + #expect(FileManager.default.homeDirectory(forUser: UUID().uuidString) == nil) } - - func testSearchPathsWithoutExpandingTilde() throws { - #if !canImport(Darwin) - throw XCTSkip("This test is not applicable for this platform") - #else + #endif + + #if canImport(Darwin) + @Test func SearchPathsWithoutExpandingTilde() async throws { for path in _DarwinSearchPaths(for: .libraryDirectory, in: .userDomainMask, expandTilde: false) { - XCTAssertTrue(path.starts(with: "~/"), "Path '\(path)' did not start with ~/ as expected") + #expect(path.starts(with: "~/"), "Path '\(path)' did not start with ~/ as expected") } - #endif } + #endif - func testWindowsDirectoryCreationCrash() throws { - try FileManagerPlayground { + @Test func windowsDirectoryCreationCrash() async throws { + try await FilePlayground { Directory("a\u{301}") { } }.test { - XCTAssertTrue($0.fileExists(atPath: "a\u{301}")) + #expect($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) + #expect($0.createFile(atPath: "a\u{301}/test", contents: data)) + #expect($0.fileExists(atPath: "a\u{301}/test")) + #expect($0.contents(atPath: "a\u{301}/test") == data) } } /// Tests that Foundation can correctly handle "long paths" (paths of 260 to 32767 chacters long) on Windows. - func testWindowsLongPathSupport() throws { - #if !os(Windows) - throw XCTSkip("This test is not applicable for this platform") - #else + @Test(.enabled(if: isWindows, "This test is not applicable on this platform")) + func windowsLongPathSupport() async throws { // Create a directory with the absolute maximum path _component_ length of 255; // this will guarantee the full playground path is well over 260 characters. // Throw some Unicode in there for good measure, since only wide-character APIs support it. let dirName = String(repeating: UUID().uuidString, count: 7) + "你好!" - XCTAssertEqual(dirName.count, 255) - XCTAssertEqual(dirName.utf16.count, 255) + #expect(dirName.count == 255) + #expect(dirName.utf16.count == 255) - try FileManagerPlayground { + try await FilePlayground { Directory(dirName) { } - }.test { + }.test { fileManager in // Call every function that can call into withNTPathRepresentation with an overlong path and ensure it succeeds. let fileName = UUID().uuidString - let cwd = try XCTUnwrap($0.currentDirectoryPath) + let cwd = fileManager.currentDirectoryPath - XCTAssertTrue($0.createFile(atPath: dirName + "/" + fileName, contents: nil)) + #expect(fileManager.createFile(atPath: dirName + "/" + fileName, contents: nil)) let dirURL = URL(filePath: dirName, directoryHint: .checkFileSystem) - XCTAssertTrue(dirURL.hasDirectoryPath) + #expect(dirURL.hasDirectoryPath) let fileURL = URL(filePath: dirName + "/" + fileName, directoryHint: .checkFileSystem) - XCTAssertFalse(fileURL.hasDirectoryPath) + #expect(!fileURL.hasDirectoryPath) - XCTAssertTrue($0.fileExists(atPath: dirName + "/" + fileName)) - XCTAssertTrue($0.isReadableFile(atPath: dirName + "/" + fileName)) - XCTAssertTrue($0.isWritableFile(atPath: dirName + "/" + fileName)) + #expect(fileManager.fileExists(atPath: dirName + "/" + fileName)) + #expect(fileManager.isReadableFile(atPath: dirName + "/" + fileName)) + #expect(fileManager.isWritableFile(atPath: dirName + "/" + fileName)) // SHGetFileInfoW is documented to be limited to MAX_PATH, but appears to support long paths anyways (or at least does for SHGFI_EXETYPE). // https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shgetfileinfow - XCTAssertNoThrow(try Data().write(to: URL(fileURLWithPath: dirName + "/" + fileName + ".bat"))) - XCTAssertTrue($0.isExecutableFile(atPath: dirName + "/" + fileName + ".bat")) - XCTAssertFalse($0.isExecutableFile(atPath: dirName + "/" + fileName)) + #expect(throws: Never.self) { try Data().write(to: URL(fileURLWithPath: dirName + "/" + fileName + ".bat")) } + #expect(fileManager.isExecutableFile(atPath: dirName + "/" + fileName + ".bat")) + #expect(!fileManager.isExecutableFile(atPath: dirName + "/" + fileName)) - XCTAssertNoThrow(try $0.attributesOfItem(atPath: dirName + "/" + fileName)) - XCTAssertNoThrow(try $0.setAttributes([.modificationDate: Date()], ofItemAtPath: dirName + "/" + fileName)) - XCTAssertNoThrow(try $0.attributesOfFileSystem(forPath: dirName + "/" + fileName)) + #expect(throws: Never.self) { try fileManager.attributesOfItem(atPath: dirName + "/" + fileName) } + #expect(throws: Never.self) { try fileManager.setAttributes([.modificationDate: Date()], ofItemAtPath: dirName + "/" + fileName) } + #expect(throws: Never.self) { try fileManager.attributesOfFileSystem(forPath: dirName + "/" + fileName) } - XCTAssertNoThrow(try Data(contentsOf: URL(fileURLWithPath: dirName + "/" + fileName))) + #expect(throws: Never.self) { try Data(contentsOf: URL(fileURLWithPath: dirName + "/" + fileName)) } - XCTAssertNoThrow(try Data("hello".utf8).write(to: URL(fileURLWithPath: dirName + "/" + fileName))) - XCTAssertNoThrow(try Data("hello".utf8).write(to: URL(fileURLWithPath: dirName + "/" + fileName), options: .atomic)) + #expect(throws: Never.self) { try Data("hello".utf8).write(to: URL(fileURLWithPath: dirName + "/" + fileName)) } + #expect(throws: Never.self) { try Data("hello".utf8).write(to: URL(fileURLWithPath: dirName + "/" + fileName), options: .atomic) } - XCTAssertNoThrow(try Data("hello".utf8).write(to: URL(fileURLWithPath: dirName + "/" + fileName + ".v2"))) - XCTAssertTrue($0.contentsEqual(atPath: dirName + "/" + fileName, andPath: dirName + "/" + fileName + ".v2")) + #expect(throws: Never.self) { try Data("hello".utf8).write(to: URL(fileURLWithPath: dirName + "/" + fileName + ".v2")) } + #expect(fileManager.contentsEqual(atPath: dirName + "/" + fileName, andPath: dirName + "/" + fileName + ".v2")) - XCTAssertEqual(try $0.subpathsOfDirectory(atPath: dirName).sorted(), [ + #expect(try fileManager.subpathsOfDirectory(atPath: dirName).sorted() == [ fileName, fileName + ".bat", fileName + ".v2" ]) - XCTAssertNoThrow(try $0.createDirectory(at: URL(fileURLWithPath: dirName + "/" + "subdir1"), withIntermediateDirectories: false)) + #expect(throws: Never.self) { try fileManager.createDirectory(at: URL(fileURLWithPath: dirName + "/" + "subdir1"), withIntermediateDirectories: false) } // SHCreateDirectoryExW's path argument is limited to 248 characters, and the \\?\ prefix doesn't help. // https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shcreatedirectoryexw - XCTAssertThrowsError(try $0.createDirectory(at: URL(fileURLWithPath: dirName + "/" + "subdir2" + "/" + "subdir3"), withIntermediateDirectories: true)) + #expect(throws: (any Error).self) { + try fileManager.createDirectory(at: URL(fileURLWithPath: dirName + "/" + "subdir2" + "/" + "subdir3"), withIntermediateDirectories: true) + } // SetCurrentDirectory seems to be limited to MAX_PATH unconditionally, counter to the documentation. // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setcurrentdirectory // https://github.com/MicrosoftDocs/feedback/issues/1441 - XCTAssertFalse($0.changeCurrentDirectoryPath(dirName + "/" + "subdir1")) - - XCTAssertNoThrow(try $0.createSymbolicLink(atPath: dirName + "/" + "lnk", withDestinationPath: fileName)) - XCTAssertNoThrow(try $0.createSymbolicLink(atPath: dirName + "/" + "lnk2", withDestinationPath: cwd + "/" + dirName + "/" + fileName)) - XCTAssertEqual(try $0.destinationOfSymbolicLink(atPath: dirName + "/" + "lnk"), fileName) - XCTAssertEqual(try $0.destinationOfSymbolicLink(atPath: dirName + "/" + "lnk2"), cwd + "\\" + dirName + "\\" + fileName) - - XCTAssertEqual((cwd + "/" + dirName + "/" + "lnk").resolvingSymlinksInPath, (cwd + "/" + dirName + "/" + fileName).resolvingSymlinksInPath) - - XCTAssertNoThrow(try $0.createDirectory(at: URL(fileURLWithPath: dirName + "/" + "subdir2"), withIntermediateDirectories: false)) - XCTAssertNoThrow(try $0.createDirectory(at: URL(fileURLWithPath: dirName + "/" + "subdir2" + "/" + "subdir3"), withIntermediateDirectories: false)) - XCTAssertNoThrow(try Data().write(to: URL(fileURLWithPath: dirName + "/" + "subdir2" + "/" + "subdir3" + "/" + "somefile"))) - XCTAssertNoThrow(try Data().write(to: URL(fileURLWithPath: dirName + "/" + "subdir2" + "/" + "subdir3" + "/" + "somefile2"))) - XCTAssertNoThrow(try $0.moveItem(atPath: dirName + "/" + "subdir2" + "/" + "subdir3" + "/" + "somefile2", toPath: dirName + "/" + "subdir2" + "/" + "subdir3" + "/" + "somefile3")) - XCTAssertNoThrow(try $0.moveItem(atPath: dirName + "/" + "subdir2" + "/" + "subdir3", toPath: dirName + "/" + "subdir2" + "/" + "subdir3.delete")) - XCTAssertNoThrow(try $0.linkItem(atPath: dirName + "/" + "subdir2" + "/" + "subdir3.delete", toPath: dirName + "/" + "subdir2" + "/" + "subdir3.delete.lnk")) - XCTAssertNoThrow(try $0.linkItem(atPath: dirName + "/" + "subdir2", toPath: dirName + "/" + "subdir2.lnk")) - XCTAssertNoThrow(try $0.removeItem(atPath: dirName + "/" + "subdir2" + "/" + "subdir3.delete" + "/" + "somefile3")) - XCTAssertNoThrow(try $0.removeItem(atPath: dirName + "/" + "subdir2")) + #expect(!fileManager.changeCurrentDirectoryPath(dirName + "/" + "subdir1")) + + #expect(throws: Never.self) { try fileManager.createSymbolicLink(atPath: dirName + "/" + "lnk", withDestinationPath: fileName) } + #expect(throws: Never.self) { try fileManager.createSymbolicLink(atPath: dirName + "/" + "lnk2", withDestinationPath: cwd + "/" + dirName + "/" + fileName) } + do { + let dest = try fileManager.destinationOfSymbolicLink(atPath: dirName + "/" + "lnk") + #expect(dest == fileName) + } + do { + let dest = try fileManager.destinationOfSymbolicLink(atPath: dirName + "/" + "lnk2") + #expect(dest == cwd + "\\" + dirName + "\\" + fileName) + } + + #expect((cwd + "/" + dirName + "/" + "lnk").resolvingSymlinksInPath == (cwd + "/" + dirName + "/" + fileName).resolvingSymlinksInPath) + + #expect(throws: Never.self) { try fileManager.createDirectory(at: URL(fileURLWithPath: dirName + "/" + "subdir2"), withIntermediateDirectories: false) } + #expect(throws: Never.self) { try fileManager.createDirectory(at: URL(fileURLWithPath: dirName + "/" + "subdir2" + "/" + "subdir3"), withIntermediateDirectories: false) } + #expect(throws: Never.self) { try Data().write(to: URL(fileURLWithPath: dirName + "/" + "subdir2" + "/" + "subdir3" + "/" + "somefile")) } + #expect(throws: Never.self) { try Data().write(to: URL(fileURLWithPath: dirName + "/" + "subdir2" + "/" + "subdir3" + "/" + "somefile2")) } + #expect(throws: Never.self) { try fileManager.moveItem(atPath: dirName + "/" + "subdir2" + "/" + "subdir3" + "/" + "somefile2", toPath: dirName + "/" + "subdir2" + "/" + "subdir3" + "/" + "somefile3") } + #expect(throws: Never.self) { try fileManager.moveItem(atPath: dirName + "/" + "subdir2" + "/" + "subdir3", toPath: dirName + "/" + "subdir2" + "/" + "subdir3.delete") } + #expect(throws: Never.self) { try fileManager.linkItem(atPath: dirName + "/" + "subdir2" + "/" + "subdir3.delete", toPath: dirName + "/" + "subdir2" + "/" + "subdir3.delete.lnk") } + #expect(throws: Never.self) { try fileManager.linkItem(atPath: dirName + "/" + "subdir2", toPath: dirName + "/" + "subdir2.lnk") } + #expect(throws: Never.self) { try fileManager.removeItem(atPath: dirName + "/" + "subdir2" + "/" + "subdir3.delete" + "/" + "somefile3") } + #expect(throws: Never.self) { try fileManager.removeItem(atPath: dirName + "/" + "subdir2") } } - #endif } } diff --git a/Tests/FoundationEssentialsTests/FileManager/FileManagerPlayground.swift b/Tests/FoundationEssentialsTests/FileManager/FilePlayground.swift similarity index 64% rename from Tests/FoundationEssentialsTests/FileManager/FileManagerPlayground.swift rename to Tests/FoundationEssentialsTests/FileManager/FilePlayground.swift index 26a96919f..12138740e 100644 --- a/Tests/FoundationEssentialsTests/FileManager/FileManagerPlayground.swift +++ b/Tests/FoundationEssentialsTests/FileManager/FilePlayground.swift @@ -10,20 +10,18 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport -#endif +import Testing -#if FOUNDATION_FRAMEWORK -@testable import Foundation -#else +#if canImport(FoundationEssentials) @testable import FoundationEssentials +#else +@testable import Foundation #endif private protocol Buildable { func build(in path: String, using fileManager: FileManager) throws } - + struct File : ExpressibleByStringLiteral, Buildable { private let name: String private let attributes: [FileAttributeKey : Any]? @@ -65,9 +63,9 @@ struct SymbolicLink : Buildable { struct Directory : Buildable { fileprivate let name: String private let attributes: [FileAttributeKey : Any]? - private let contents: [FileManagerPlayground.Item] + private let contents: [FilePlayground.Item] - init(_ name: String, attributes: [FileAttributeKey : Any]? = nil, @FileManagerPlayground.DirectoryBuilder _ contentsClosure: () -> [FileManagerPlayground.Item]) { + init(_ name: String, attributes: [FileAttributeKey : Any]? = nil, @FilePlayground.DirectoryBuilder _ contentsClosure: () -> [FilePlayground.Item]) { self.name = name self.attributes = attributes self.contents = contentsClosure() @@ -82,7 +80,29 @@ struct Directory : Buildable { } } -struct FileManagerPlayground { +@globalActor +actor CurrentWorkingDirectoryActor: GlobalActor { + static let shared = CurrentWorkingDirectoryActor() + + private init() {} + + @CurrentWorkingDirectoryActor + static func withCurrentWorkingDirectory( + _ path: String, + fileManager: FileManager = .default, + sourceLocation: SourceLocation = #_sourceLocation, + body: @CurrentWorkingDirectoryActor () throws -> Void // Must be synchronous to prevent suspension points within body which could introduce a change in the CWD + ) throws { + let previousCWD = fileManager.currentDirectoryPath + try #require(fileManager.changeCurrentDirectoryPath(path), "Failed to change CWD to '\(path)'", sourceLocation: sourceLocation) + defer { + #expect(fileManager.changeCurrentDirectoryPath(previousCWD), "Failed to change CWD back to the original directory '\(previousCWD)'", sourceLocation: sourceLocation) + } + try body() + } +} + +struct FilePlayground { enum Item : Buildable { case file(File) case directory(Directory) @@ -119,25 +139,23 @@ struct FileManagerPlayground { private let directory: Directory init(@DirectoryBuilder _ contentsClosure: () -> [Item]) { - self.directory = Directory("FileManagerPlayground_\(UUID().uuidString)", contentsClosure) + self.directory = Directory("FilePlayground_\(UUID().uuidString)", contentsClosure) } - func test(captureDelegateCalls: Bool = false, file: StaticString = #filePath, line: UInt = #line, _ tester: (FileManager) throws -> Void) throws { + func test(captureDelegateCalls: Bool = false, sourceLocation: SourceLocation = #_sourceLocation, _ tester: sending (FileManager) throws -> Void) async throws { let capturingDelegate = CapturingFileManagerDelegate() - try withExtendedLifetime(capturingDelegate) { - let fileManager = FileManager() - let tempDir = String.temporaryDirectoryPath - try directory.build(in: tempDir, using: fileManager) - let previousCWD = fileManager.currentDirectoryPath - if captureDelegateCalls { - // Add the delegate after the call to `build` to ensure that the builder doesn't mutate the delegate - fileManager.delegate = capturingDelegate - } - let createdDir = tempDir.appendingPathComponent(directory.name) - XCTAssertTrue(fileManager.changeCurrentDirectoryPath(createdDir), "Failed to change CWD to the newly created playground directory", file: file, line: line) + let fileManager = FileManager() + let tempDir = String.temporaryDirectoryPath + try directory.build(in: tempDir, using: fileManager) + if captureDelegateCalls { + // Add the delegate after the call to `build` to ensure that the builder doesn't mutate the delegate + fileManager.delegate = capturingDelegate + } + let createdDir = tempDir.appendingPathComponent(directory.name) + try await CurrentWorkingDirectoryActor.withCurrentWorkingDirectory(createdDir, fileManager: fileManager, sourceLocation: sourceLocation) { try tester(fileManager) - XCTAssertTrue(fileManager.changeCurrentDirectoryPath(previousCWD), "Failed to change CWD back to the original directory", file: file, line: line) - try fileManager.removeItem(atPath: createdDir) } + try fileManager.removeItem(atPath: createdDir) + _fixLifetime(capturingDelegate) // Ensure capturingDelegate lives beyond the tester body } } diff --git a/Tests/FoundationEssentialsTests/URLTests.swift b/Tests/FoundationEssentialsTests/URLTests.swift index c8eb77fb7..7a02872df 100644 --- a/Tests/FoundationEssentialsTests/URLTests.swift +++ b/Tests/FoundationEssentialsTests/URLTests.swift @@ -10,106 +10,102 @@ // //===----------------------------------------------------------------------===// - -#if canImport(TestSupport) -import TestSupport -#endif // canImport(TestSupport) +import Testing #if canImport(FoundationEssentials) @testable import FoundationEssentials -#endif - -#if FOUNDATION_FRAMEWORK +#else @testable import Foundation #endif -private func checkBehavior(_ result: T, new: T, old: T, file: StaticString = #filePath, line: UInt = #line) { +private func checkBehavior(_ result: T, new: T, old: T, sourceLocation: SourceLocation = #_sourceLocation) { #if FOUNDATION_FRAMEWORK if foundation_swift_url_enabled() { - XCTAssertEqual(result, new, file: file, line: line) + #expect(result == new, sourceLocation: sourceLocation) } else { - XCTAssertEqual(result, old, file: file, line: line) + #expect(result == old, sourceLocation: sourceLocation) } #else - XCTAssertEqual(result, new, file: file, line: line) + #expect(result == new, sourceLocation: sourceLocation) #endif } -final class URLTests : XCTestCase { +@Suite("URL") +private struct URLTests { - func testURLBasics() throws { + @Test func basics() throws { let string = "https://username:password@example.com:80/path/path?query=value&q=v#fragment" - let url = try XCTUnwrap(URL(string: string)) - - XCTAssertEqual(url.scheme, "https") - XCTAssertEqual(url.user(), "username") - XCTAssertEqual(url.password(), "password") - XCTAssertEqual(url.host(), "example.com") - XCTAssertEqual(url.port, 80) - XCTAssertEqual(url.path(), "/path/path") - XCTAssertEqual(url.relativePath, "/path/path") - XCTAssertEqual(url.query(), "query=value&q=v") - XCTAssertEqual(url.fragment(), "fragment") - XCTAssertEqual(url.absoluteString, string) - XCTAssertEqual(url.absoluteURL, url) - XCTAssertEqual(url.relativeString, string) - XCTAssertNil(url.baseURL) + let url = try #require(URL(string: string)) + + #expect(url.scheme == "https") + #expect(url.user() == "username") + #expect(url.password() == "password") + #expect(url.host() == "example.com") + #expect(url.port == 80) + #expect(url.path() == "/path/path") + #expect(url.relativePath == "/path/path") + #expect(url.query() == "query=value&q=v") + #expect(url.fragment() == "fragment") + #expect(url.absoluteString == string) + #expect(url.absoluteURL == url) + #expect(url.relativeString == string) + #expect(url.baseURL == nil) let baseString = "https://user:pass@base.example.com:8080/base/" - let baseURL = try XCTUnwrap(URL(string: baseString)) - let absoluteURLWithBase = try XCTUnwrap(URL(string: string, relativeTo: baseURL)) + let baseURL = try #require(URL(string: baseString)) + let absoluteURLWithBase = try #require(URL(string: string, relativeTo: baseURL)) // The URL is already absolute, so .baseURL is nil, and the components are unchanged - XCTAssertEqual(absoluteURLWithBase.scheme, "https") - XCTAssertEqual(absoluteURLWithBase.user(), "username") - XCTAssertEqual(absoluteURLWithBase.password(), "password") - XCTAssertEqual(absoluteURLWithBase.host(), "example.com") - XCTAssertEqual(absoluteURLWithBase.port, 80) - XCTAssertEqual(absoluteURLWithBase.path(), "/path/path") - XCTAssertEqual(absoluteURLWithBase.relativePath, "/path/path") - XCTAssertEqual(absoluteURLWithBase.query(), "query=value&q=v") - XCTAssertEqual(absoluteURLWithBase.fragment(), "fragment") - XCTAssertEqual(absoluteURLWithBase.absoluteString, string) - XCTAssertEqual(absoluteURLWithBase.absoluteURL, url) - XCTAssertEqual(absoluteURLWithBase.relativeString, string) - XCTAssertNil(absoluteURLWithBase.baseURL) - XCTAssertEqual(absoluteURLWithBase.absoluteURL, url) + #expect(absoluteURLWithBase.scheme == "https") + #expect(absoluteURLWithBase.user() == "username") + #expect(absoluteURLWithBase.password() == "password") + #expect(absoluteURLWithBase.host() == "example.com") + #expect(absoluteURLWithBase.port == 80) + #expect(absoluteURLWithBase.path() == "/path/path") + #expect(absoluteURLWithBase.relativePath == "/path/path") + #expect(absoluteURLWithBase.query() == "query=value&q=v") + #expect(absoluteURLWithBase.fragment() == "fragment") + #expect(absoluteURLWithBase.absoluteString == string) + #expect(absoluteURLWithBase.absoluteURL == url) + #expect(absoluteURLWithBase.relativeString == string) + #expect(absoluteURLWithBase.baseURL == nil) + #expect(absoluteURLWithBase.absoluteURL == url) let relativeString = "relative/path?query#fragment" - let relativeURL = try XCTUnwrap(URL(string: relativeString)) - - XCTAssertNil(relativeURL.scheme) - XCTAssertNil(relativeURL.user()) - XCTAssertNil(relativeURL.password()) - XCTAssertNil(relativeURL.host()) - XCTAssertNil(relativeURL.port) - XCTAssertEqual(relativeURL.path(), "relative/path") - XCTAssertEqual(relativeURL.relativePath, "relative/path") - XCTAssertEqual(relativeURL.query(), "query") - XCTAssertEqual(relativeURL.fragment(), "fragment") - XCTAssertEqual(relativeURL.absoluteString, relativeString) - XCTAssertEqual(relativeURL.absoluteURL, relativeURL) - XCTAssertEqual(relativeURL.relativeString, relativeString) - XCTAssertNil(relativeURL.baseURL) - - let relativeURLWithBase = try XCTUnwrap(URL(string: relativeString, relativeTo: baseURL)) - - XCTAssertEqual(relativeURLWithBase.scheme, baseURL.scheme) - XCTAssertEqual(relativeURLWithBase.user(), baseURL.user()) - XCTAssertEqual(relativeURLWithBase.password(), baseURL.password()) - XCTAssertEqual(relativeURLWithBase.host(), baseURL.host()) - XCTAssertEqual(relativeURLWithBase.port, baseURL.port) - XCTAssertEqual(relativeURLWithBase.path(), "/base/relative/path") - XCTAssertEqual(relativeURLWithBase.relativePath, "relative/path") - XCTAssertEqual(relativeURLWithBase.query(), "query") - XCTAssertEqual(relativeURLWithBase.fragment(), "fragment") - XCTAssertEqual(relativeURLWithBase.absoluteString, "https://user:pass@base.example.com:8080/base/relative/path?query#fragment") - XCTAssertEqual(relativeURLWithBase.absoluteURL, URL(string: "https://user:pass@base.example.com:8080/base/relative/path?query#fragment")) - XCTAssertEqual(relativeURLWithBase.relativeString, relativeString) - XCTAssertEqual(relativeURLWithBase.baseURL, baseURL) + let relativeURL = try #require(URL(string: relativeString)) + + #expect(relativeURL.scheme == nil) + #expect(relativeURL.user() == nil) + #expect(relativeURL.password() == nil) + #expect(relativeURL.host() == nil) + #expect(relativeURL.port == nil) + #expect(relativeURL.path() == "relative/path") + #expect(relativeURL.relativePath == "relative/path") + #expect(relativeURL.query() == "query") + #expect(relativeURL.fragment() == "fragment") + #expect(relativeURL.absoluteString == relativeString) + #expect(relativeURL.absoluteURL == relativeURL) + #expect(relativeURL.relativeString == relativeString) + #expect(relativeURL.baseURL == nil) + + let relativeURLWithBase = try #require(URL(string: relativeString, relativeTo: baseURL)) + + #expect(relativeURLWithBase.scheme == baseURL.scheme) + #expect(relativeURLWithBase.user() == baseURL.user()) + #expect(relativeURLWithBase.password() == baseURL.password()) + #expect(relativeURLWithBase.host() == baseURL.host()) + #expect(relativeURLWithBase.port == baseURL.port) + #expect(relativeURLWithBase.path() == "/base/relative/path") + #expect(relativeURLWithBase.relativePath == "relative/path") + #expect(relativeURLWithBase.query() == "query") + #expect(relativeURLWithBase.fragment() == "fragment") + #expect(relativeURLWithBase.absoluteString == "https://user:pass@base.example.com:8080/base/relative/path?query#fragment") + #expect(relativeURLWithBase.absoluteURL == URL(string: "https://user:pass@base.example.com:8080/base/relative/path?query#fragment")) + #expect(relativeURLWithBase.relativeString == relativeString) + #expect(relativeURLWithBase.baseURL == baseURL) } - func testURLResolvingAgainstBase() throws { + @Test func resolvingAgainstBase() throws { let base = URL(string: "http://a/b/c/d;p?q") let tests = [ // RFC 3986 5.4.1. Normal Examples @@ -175,14 +171,13 @@ final class URLTests : XCTestCase { continue } - let url = URL(stringOrEmpty: test.key, relativeTo: base) - XCTAssertNotNil(url, "Got nil url for string: \(test.key)") - XCTAssertEqual(url?.absoluteString, test.value, "Failed test for string: \(test.key)") + let url = try #require(URL(stringOrEmpty: test.key, relativeTo: base), "Got nil url for string: \(test.key)") + #expect(url.absoluteString == test.value, "Failed test for string: \(test.key)") } } - func testURLPathAPIsResolveAgainstBase() throws { - try XCTSkipIf(!foundation_swift_url_enabled()) + @Test(.enabled(if: foundation_swift_url_enabled())) + func URLPathAPIsResolveAgainstBase() throws { // Borrowing the same test cases from RFC 3986, but checking paths let base = URL(string: "http://a/b/c/d;p?q") let tests = [ @@ -237,41 +232,41 @@ final class URLTests : XCTestCase { ] for test in tests { let url = URL(stringOrEmpty: test.key, relativeTo: base)! - XCTAssertEqual(url.absolutePath(), test.value) + #expect(url.absolutePath() == test.value) if (url.hasDirectoryPath && url.absolutePath().count > 1) { // The trailing slash is stripped in .path for file system compatibility - XCTAssertEqual(String(url.absolutePath().dropLast()), url.path) + #expect(String(url.absolutePath().dropLast()) == url.path) } else { - XCTAssertEqual(url.absolutePath(), url.path) + #expect(url.absolutePath() == url.path) } } } - func testURLPathComponentsPercentEncodedSlash() throws { - try XCTSkipIf(!foundation_swift_url_enabled()) + + @Test(.enabled(if: foundation_swift_url_enabled())) + func URLPathComponentsPercentEncodedSlash() throws { + var url = try #require(URL(string: "https://example.com/https%3A%2F%2Fexample.com")) + #expect(url.pathComponents == ["/", "https://example.com"]) - var url = try XCTUnwrap(URL(string: "https://example.com/https%3A%2F%2Fexample.com")) - XCTAssertEqual(url.pathComponents, ["/", "https://example.com"]) + url = try #require(URL(string: "https://example.com/https:%2f%2fexample.com")) + #expect(url.pathComponents == ["/", "https://example.com"]) - url = try XCTUnwrap(URL(string: "https://example.com/https:%2f%2fexample.com")) - XCTAssertEqual(url.pathComponents, ["/", "https://example.com"]) + url = try #require(URL(string: "https://example.com/https:%2F%2Fexample.com%2Fpath")) + #expect(url.pathComponents == ["/", "https://example.com/path"]) - url = try XCTUnwrap(URL(string: "https://example.com/https:%2F%2Fexample.com%2Fpath")) - XCTAssertEqual(url.pathComponents, ["/", "https://example.com/path"]) + url = try #require(URL(string: "https://example.com/https:%2F%2Fexample.com/path")) + #expect(url.pathComponents == ["/", "https://example.com", "path"]) - url = try XCTUnwrap(URL(string: "https://example.com/https:%2F%2Fexample.com/path")) - XCTAssertEqual(url.pathComponents, ["/", "https://example.com", "path"]) + url = try #require(URL(string: "https://example.com/https%3A%2F%2Fexample.com%2Fpath%3Fquery%23fragment")) + #expect(url.pathComponents == ["/", "https://example.com/path?query#fragment"]) - url = try XCTUnwrap(URL(string: "https://example.com/https%3A%2F%2Fexample.com%2Fpath%3Fquery%23fragment")) - XCTAssertEqual(url.pathComponents, ["/", "https://example.com/path?query#fragment"]) - - url = try XCTUnwrap(URL(string: "https://example.com/https%3A%2F%2Fexample.com%2Fpath?query#fragment")) - XCTAssertEqual(url.pathComponents, ["/", "https://example.com/path"]) + url = try #require(URL(string: "https://example.com/https%3A%2F%2Fexample.com%2Fpath?query#fragment")) + #expect(url.pathComponents == ["/", "https://example.com/path"]) } - func testURLRootlessPath() throws { - try XCTSkipIf(!foundation_swift_url_enabled()) - + + @Test(.enabled(if: foundation_swift_url_enabled())) + func URLRootlessPath() throws { let paths = ["", "path"] let queries = [nil, "query"] let fragments = [nil, "fragment"] @@ -282,87 +277,87 @@ final class URLTests : XCTestCase { let queryString = query != nil ? "?\(query!)" : "" let fragmentString = fragment != nil ? "#\(fragment!)" : "" let urlString = "scheme:\(path)\(queryString)\(fragmentString)" - let url = try XCTUnwrap(URL(string: urlString)) - XCTAssertEqual(url.absoluteString, urlString) - XCTAssertEqual(url.scheme, "scheme") - XCTAssertNil(url.host()) - XCTAssertEqual(url.path(), path) - XCTAssertEqual(url.query(), query) - XCTAssertEqual(url.fragment(), fragment) + let url = try #require(URL(string: urlString)) + #expect(url.absoluteString == urlString) + #expect(url.scheme == "scheme") + #expect(url.host() == nil) + #expect(url.path() == path) + #expect(url.query() == query) + #expect(url.fragment() == fragment) } } } } - func testURLNonSequentialIPLiteralAndPort() { + @Test func nonSequentialIPLiteralAndPort() { let urlString = "https://[fe80::3221:5634:6544]invalid:433/" let url = URL(string: urlString) - XCTAssertNil(url) + #expect(url == nil) } - func testURLFilePathInitializer() throws { + @Test func filePathInitializer() throws { let directory = URL(filePath: "/some/directory", directoryHint: .isDirectory) - XCTAssertTrue(directory.hasDirectoryPath) + #expect(directory.hasDirectoryPath) let notDirectory = URL(filePath: "/some/file", directoryHint: .notDirectory) - XCTAssertFalse(notDirectory.hasDirectoryPath) + #expect(!notDirectory.hasDirectoryPath) // directoryHint defaults to .inferFromPath let directoryAgain = URL(filePath: "/some/directory.framework/") - XCTAssertTrue(directoryAgain.hasDirectoryPath) + #expect(directoryAgain.hasDirectoryPath) let notDirectoryAgain = URL(filePath: "/some/file") - XCTAssertFalse(notDirectoryAgain.hasDirectoryPath) + #expect(!notDirectoryAgain.hasDirectoryPath) // Test .checkFileSystem by creating a directory let tempDirectory = URL.temporaryDirectory let urlBeforeCreation = URL(filePath: "\(tempDirectory.path)/tmp-dir", directoryHint: .checkFileSystem) - XCTAssertFalse(urlBeforeCreation.hasDirectoryPath) + #expect(!urlBeforeCreation.hasDirectoryPath) try FileManager.default.createDirectory( at: URL(filePath: "\(tempDirectory.path)/tmp-dir"), withIntermediateDirectories: true ) let urlAfterCreation = URL(filePath: "\(tempDirectory.path)/tmp-dir", directoryHint: .checkFileSystem) - XCTAssertTrue(urlAfterCreation.hasDirectoryPath) + #expect(urlAfterCreation.hasDirectoryPath) try FileManager.default.removeItem(at: URL(filePath: "\(tempDirectory.path)/tmp-dir")) } #if os(Windows) - func testURLWindowsDriveLetterPath() throws { + @Test func windowsDriveLetterPath() throws { var url = URL(filePath: #"C:\test\path"#, directoryHint: .notDirectory) // .absoluteString and .path() use the RFC 8089 URL path - XCTAssertEqual(url.absoluteString, "file:///C:/test/path") - XCTAssertEqual(url.path(), "/C:/test/path") + #expect(url.absoluteString == "file:///C:/test/path") + #expect(url.path() == "/C:/test/path") // .path and .fileSystemPath() strip the leading slash - XCTAssertEqual(url.path, "C:/test/path") - XCTAssertEqual(url.fileSystemPath(), "C:/test/path") + #expect(url.path == "C:/test/path") + #expect(url.fileSystemPath() == "C:/test/path") url = URL(filePath: #"C:\"#, directoryHint: .isDirectory) - XCTAssertEqual(url.absoluteString, "file:///C:/") - XCTAssertEqual(url.path(), "/C:/") - XCTAssertEqual(url.path, "C:/") - XCTAssertEqual(url.fileSystemPath(), "C:/") + #expect(url.absoluteString == "file:///C:/") + #expect(url.path() == "/C:/") + #expect(url.path == "C:/") + #expect(url.fileSystemPath() == "C:/") url = URL(filePath: #"C:\\\"#, directoryHint: .isDirectory) - XCTAssertEqual(url.absoluteString, "file:///C:///") - XCTAssertEqual(url.path(), "/C:///") - XCTAssertEqual(url.path, "C:/") - XCTAssertEqual(url.fileSystemPath(), "C:/") + #expect(url.absoluteString == "file:///C:///") + #expect(url.path() == "/C:///") + #expect(url.path == "C:/") + #expect(url.fileSystemPath() == "C:/") url = URL(filePath: #"\C:\"#, directoryHint: .isDirectory) - XCTAssertEqual(url.absoluteString, "file:///C:/") - XCTAssertEqual(url.path(), "/C:/") - XCTAssertEqual(url.path, "C:/") - XCTAssertEqual(url.fileSystemPath(), "C:/") + #expect(url.absoluteString == "file:///C:/") + #expect(url.path() == "/C:/") + #expect(url.path == "C:/") + #expect(url.fileSystemPath() == "C:/") let base = URL(filePath: #"\d:\path\"#, directoryHint: .isDirectory) url = URL(filePath: #"%43:\fake\letter"#, directoryHint: .notDirectory, relativeTo: base) // ":" is encoded to "%3A" in the first path segment so it's not mistaken as the scheme separator - XCTAssertEqual(url.relativeString, "%2543%3A/fake/letter") - XCTAssertEqual(url.path(), "/d:/path/%2543%3A/fake/letter") - XCTAssertEqual(url.path, "d:/path/%43:/fake/letter") - XCTAssertEqual(url.fileSystemPath(), "d:/path/%43:/fake/letter") + #expect(url.relativeString == "%2543%3A/fake/letter") + #expect(url.path() == "/d:/path/%2543%3A/fake/letter") + #expect(url.path == "d:/path/%43:/fake/letter") + #expect(url.fileSystemPath() == "d:/path/%43:/fake/letter") let cwd = URL.currentDirectory() var iter = cwd.path().utf8.makeIterator() @@ -371,105 +366,45 @@ final class URLTests : XCTestCase { iter.next() == ._colon { let path = #"\\?\"# + "\(Unicode.Scalar(driveLetter))" + #":\"# url = URL(filePath: path, directoryHint: .isDirectory) - XCTAssertEqual(url.path.last, "/") - XCTAssertEqual(url.fileSystemPath().last, "/") + #expect(url.path.last == "/") + #expect(url.fileSystemPath().last == "/") } } #endif - func testURLFilePathRelativeToBase() throws { - try FileManagerPlayground { - Directory("dir") { - "Foo" - "Bar" - } - }.test { - let currentDirectoryPath = $0.currentDirectoryPath - let baseURL = URL(filePath: currentDirectoryPath, directoryHint: .isDirectory) - let relativePath = "dir" - - let url1 = URL(filePath: relativePath, directoryHint: .isDirectory, relativeTo: baseURL) - - let url2 = URL(filePath: relativePath, directoryHint: .checkFileSystem, relativeTo: baseURL) - XCTAssertEqual(url1, url2, "\(url1) was not equal to \(url2)") - - // directoryHint is `.inferFromPath` by default - let url3 = URL(filePath: relativePath + "/", relativeTo: baseURL) - XCTAssertEqual(url1, url3, "\(url1) was not equal to \(url3)") - } - } - - func testURLFilePathDoesNotFollowLastSymlink() throws { - try FileManagerPlayground { - Directory("dir") { - "Foo" - SymbolicLink("symlink", destination: "../dir") - } - }.test { - let currentDirectoryPath = $0.currentDirectoryPath - let baseURL = URL(filePath: currentDirectoryPath, directoryHint: .isDirectory) - - let dirURL = baseURL.appending(path: "dir", directoryHint: .checkFileSystem) - XCTAssertTrue(dirURL.hasDirectoryPath) - - var symlinkURL = dirURL.appending(path: "symlink", directoryHint: .notDirectory) - - // FileManager uses stat(), which will follow the symlink to the directory. - - #if FOUNDATION_FRAMEWORK - var isDirectory: ObjCBool = false - XCTAssertTrue(FileManager.default.fileExists(atPath: symlinkURL.path, isDirectory: &isDirectory)) - XCTAssertTrue(isDirectory.boolValue) - #else - var isDirectory = false - XCTAssertTrue(FileManager.default.fileExists(atPath: symlinkURL.path, isDirectory: &isDirectory)) - XCTAssertTrue(isDirectory) - #endif - - // URL uses lstat(), which will not follow the symlink at the end of the path. - // Check that URL(filePath:) and .appending(path:) preserve this behavior. - - symlinkURL = URL(filePath: symlinkURL.path, directoryHint: .checkFileSystem) - XCTAssertFalse(symlinkURL.hasDirectoryPath) - - symlinkURL = dirURL.appending(path: "symlink", directoryHint: .checkFileSystem) - XCTAssertFalse(symlinkURL.hasDirectoryPath) - } - } - - func testURLRelativeDotDotResolution() throws { + @Test func relativeDotDotResolution() throws { let baseURL = URL(filePath: "/docs/src/") var result = URL(filePath: "../images/foo.png", relativeTo: baseURL) - XCTAssertEqual(result.path, "/docs/images/foo.png") + #expect(result.path == "/docs/images/foo.png") result = URL(filePath: "/../images/foo.png", relativeTo: baseURL) - XCTAssertEqual(result.path, "/../images/foo.png") + #expect(result.path == "/../images/foo.png") } - func testAppendFamily() throws { + @Test func appendFamily() throws { let base = URL(string: "https://www.example.com")! // Appending path - XCTAssertEqual( - base.appending(path: "/api/v2").absoluteString, + #expect( + base.appending(path: "/api/v2").absoluteString == "https://www.example.com/api/v2" ) var testAppendPath = base testAppendPath.append(path: "/api/v3") - XCTAssertEqual( - testAppendPath.absoluteString, + #expect( + testAppendPath.absoluteString == "https://www.example.com/api/v3" ) // Appending component - XCTAssertEqual( - base.appending(component: "AC/DC").absoluteString, + #expect( + base.appending(component: "AC/DC").absoluteString == "https://www.example.com/AC%2FDC" ) var testAppendComponent = base testAppendComponent.append(component: "AC/DC") - XCTAssertEqual( - testAppendComponent.absoluteString, + #expect( + testAppendComponent.absoluteString == "https://www.example.com/AC%2FDC" ) @@ -478,26 +413,26 @@ final class URLTests : XCTestCase { URLQueryItem(name: "id", value: "42"), URLQueryItem(name: "color", value: "blue") ] - XCTAssertEqual( - base.appending(queryItems: queryItems).absoluteString, + #expect( + base.appending(queryItems: queryItems).absoluteString == "https://www.example.com?id=42&color=blue" ) var testAppendQueryItems = base testAppendQueryItems.append(queryItems: queryItems) - XCTAssertEqual( - testAppendQueryItems.absoluteString, + #expect( + testAppendQueryItems.absoluteString == "https://www.example.com?id=42&color=blue" ) // Appending components - XCTAssertEqual( - base.appending(components: "api", "artist", "AC/DC").absoluteString, + #expect( + base.appending(components: "api", "artist", "AC/DC").absoluteString == "https://www.example.com/api/artist/AC%2FDC" ) var testAppendComponents = base testAppendComponents.append(components: "api", "artist", "AC/DC") - XCTAssertEqual( - testAppendComponents.absoluteString, + #expect( + testAppendComponents.absoluteString == "https://www.example.com/api/artist/AC%2FDC" ) @@ -509,28 +444,28 @@ final class URLTests : XCTestCase { URLQueryItem(name: "color", value: "blue") ]) .appending(components: "get", "products") - XCTAssertEqual( - chained.absoluteString, + #expect( + chained.absoluteString == "https://www.example.com/api/v2/get/products?magic=42&color=blue" ) } - func testAppendFamilyDirectoryHint() throws { + @Test func appendFamilyDirectoryHint() throws { // Make sure directoryHint values are propagated correctly let base = URL(string: "file:///var/mobile")! // Appending path var url = base.appending(path: "/folder/item", directoryHint: .isDirectory) - XCTAssertTrue(url.hasDirectoryPath) + #expect(url.hasDirectoryPath) url = base.appending(path: "folder/item", directoryHint: .notDirectory) - XCTAssertFalse(url.hasDirectoryPath) + #expect(!url.hasDirectoryPath) url = base.appending(path: "/folder/item.framework/") - XCTAssertTrue(url.hasDirectoryPath) + #expect(url.hasDirectoryPath) url = base.appending(path: "/folder/item") - XCTAssertFalse(url.hasDirectoryPath) + #expect(!url.hasDirectoryPath) try runDirectoryHintCheckFilesystemTest { $0.appending(path: "/folder/item", directoryHint: .checkFileSystem) @@ -538,16 +473,16 @@ final class URLTests : XCTestCase { // Appending component url = base.appending(component: "AC/DC", directoryHint: .isDirectory) - XCTAssertTrue(url.hasDirectoryPath) + #expect(url.hasDirectoryPath) url = base.appending(component: "AC/DC", directoryHint: .notDirectory) - XCTAssertFalse(url.hasDirectoryPath) + #expect(!url.hasDirectoryPath) url = base.appending(component: "AC/DC/", directoryHint: .isDirectory) - XCTAssertTrue(url.hasDirectoryPath) + #expect(url.hasDirectoryPath) url = base.appending(component: "AC/DC") - XCTAssertFalse(url.hasDirectoryPath) + #expect(!url.hasDirectoryPath) try runDirectoryHintCheckFilesystemTest { $0.appending(component: "AC/DC", directoryHint: .checkFileSystem) @@ -555,16 +490,16 @@ final class URLTests : XCTestCase { // Appending components url = base.appending(components: "api", "v2", "AC/DC", directoryHint: .isDirectory) - XCTAssertTrue(url.hasDirectoryPath) + #expect(url.hasDirectoryPath) url = base.appending(components: "api", "v2", "AC/DC", directoryHint: .notDirectory) - XCTAssertFalse(url.hasDirectoryPath) + #expect(!url.hasDirectoryPath) url = base.appending(components: "api", "v2", "AC/DC/", directoryHint: .isDirectory) - XCTAssertTrue(url.hasDirectoryPath) + #expect(url.hasDirectoryPath) url = base.appending(components: "api", "v2", "AC/DC") - XCTAssertFalse(url.hasDirectoryPath) + #expect(!url.hasDirectoryPath) try runDirectoryHintCheckFilesystemTest { $0.appending(components: "api", "v2", "AC/DC", directoryHint: .checkFileSystem) @@ -574,40 +509,38 @@ final class URLTests : XCTestCase { private func runDirectoryHintCheckFilesystemTest(_ builder: (URL) -> URL) throws { let tempDirectory = URL.temporaryDirectory // We should not have directory path before it's created - XCTAssertFalse(builder(tempDirectory).hasDirectoryPath) + #expect(!builder(tempDirectory).hasDirectoryPath) // Create the folder try FileManager.default.createDirectory( at: builder(tempDirectory), withIntermediateDirectories: true ) - XCTAssertTrue(builder(tempDirectory).hasDirectoryPath) + #expect(builder(tempDirectory).hasDirectoryPath) try FileManager.default.removeItem(at: builder(tempDirectory)) } - func testURLEncodingInvalidCharacters() throws { - let urlStrings = [ - " ", - "path space", - "/absolute path space", - "scheme:path space", - "scheme://host/path space", - "scheme://host/path space?query space#fragment space", - "scheme://user space:pass space@host/", - "unsafe\"<>%{}\\|^~[]`##", - "http://example.com/unsafe\"<>%{}\\|^~[]`##", - "mailto:\"Your Name\" ", - "[This is not a valid URL without encoding.]", - "Encoding a relative path! 😎", - ] - for urlString in urlStrings { - var url = URL(string: urlString, encodingInvalidCharacters: true) - XCTAssertNotNil(url, "Expected a percent-encoded url for string \(urlString)") - url = URL(string: urlString, encodingInvalidCharacters: false) - XCTAssertNil(url, "Expected to fail strict url parsing for string \(urlString)") - } + @Test(arguments: [ + " ", + "path space", + "/absolute path space", + "scheme:path space", + "scheme://host/path space", + "scheme://host/path space?query space#fragment space", + "scheme://user space:pass space@host/", + "unsafe\"<>%{}\\|^~[]`##", + "http://example.com/unsafe\"<>%{}\\|^~[]`##", + "mailto:\"Your Name\" ", + "[This is not a valid URL without encoding.]", + "Encoding a relative path! 😎", + ]) + func URLEncodingInvalidCharacters(urlString: String) throws { + var url = URL(string: urlString, encodingInvalidCharacters: true) + #expect(url != nil, "Expected a percent-encoded url for string \(urlString)") + url = URL(string: urlString, encodingInvalidCharacters: false) + #expect(url == nil, "Expected to fail strict url parsing for string \(urlString)") } - func testURLAppendingPathDoesNotEncodeColon() throws { + @Test func appendingPathDoesNotEncodeColon() throws { let baseURL = URL(string: "file:///var/mobile/")! let url = URL(string: "relative", relativeTo: baseURL)! let component = "no:slash" @@ -615,55 +548,55 @@ final class URLTests : XCTestCase { // Make sure we don't encode ":" since `component` is not the first path segment var appended = url.appending(path: component, directoryHint: .notDirectory) - XCTAssertEqual(appended.absoluteString, "file:///var/mobile/relative/no:slash") - XCTAssertEqual(appended.relativePath, "relative/no:slash") + #expect(appended.absoluteString == "file:///var/mobile/relative/no:slash") + #expect(appended.relativePath == "relative/no:slash") appended = url.appending(path: slashComponent, directoryHint: .notDirectory) - XCTAssertEqual(appended.absoluteString, "file:///var/mobile/relative/with:slash") - XCTAssertEqual(appended.relativePath, "relative/with:slash") + #expect(appended.absoluteString == "file:///var/mobile/relative/with:slash") + #expect(appended.relativePath == "relative/with:slash") appended = url.appending(component: component, directoryHint: .notDirectory) - XCTAssertEqual(appended.absoluteString, "file:///var/mobile/relative/no:slash") - XCTAssertEqual(appended.relativePath, "relative/no:slash") + #expect(appended.absoluteString == "file:///var/mobile/relative/no:slash") + #expect(appended.relativePath == "relative/no:slash") // .appending(component:) should explicitly treat slashComponent as a single // path component, meaning "/" should be encoded to "%2F" before appending. // However, the old behavior didn't do this for file URLs, so we maintain the // old behavior to prevent breakage. appended = url.appending(component: slashComponent, directoryHint: .notDirectory) - XCTAssertEqual(appended.absoluteString, "file:///var/mobile/relative/with:slash") - XCTAssertEqual(appended.relativePath, "relative/with:slash") + #expect(appended.absoluteString == "file:///var/mobile/relative/with:slash") + #expect(appended.relativePath == "relative/with:slash") appended = url.appendingPathComponent(component, isDirectory: false) - XCTAssertEqual(appended.absoluteString, "file:///var/mobile/relative/no:slash") - XCTAssertEqual(appended.relativePath, "relative/no:slash") + #expect(appended.absoluteString == "file:///var/mobile/relative/no:slash") + #expect(appended.relativePath == "relative/no:slash") // Test deprecated API, which acts like `appending(path:)` appended = url.appendingPathComponent(slashComponent, isDirectory: false) - XCTAssertEqual(appended.absoluteString, "file:///var/mobile/relative/with:slash") - XCTAssertEqual(appended.relativePath, "relative/with:slash") + #expect(appended.absoluteString == "file:///var/mobile/relative/with:slash") + #expect(appended.relativePath == "relative/with:slash") } - func testURLDeletingLastPathComponent() throws { + @Test func deletingLastPathComponent() throws { var absolute = URL(filePath: "/absolute/path", directoryHint: .notDirectory) // Note: .relativePath strips the trailing slash for compatibility - XCTAssertEqual(absolute.relativePath, "/absolute/path") - XCTAssertFalse(absolute.hasDirectoryPath) + #expect(absolute.relativePath == "/absolute/path") + #expect(!absolute.hasDirectoryPath) absolute.deleteLastPathComponent() - XCTAssertEqual(absolute.relativePath, "/absolute") - XCTAssertTrue(absolute.hasDirectoryPath) + #expect(absolute.relativePath == "/absolute") + #expect(absolute.hasDirectoryPath) absolute.deleteLastPathComponent() - XCTAssertEqual(absolute.relativePath, "/") - XCTAssertTrue(absolute.hasDirectoryPath) + #expect(absolute.relativePath == "/") + #expect(absolute.hasDirectoryPath) // The old .deleteLastPathComponent() implementation appends ".." to the // root directory "/", resulting in "/../". This resolves back to "/". // The new implementation simply leaves "/" as-is. absolute.deleteLastPathComponent() checkBehavior(absolute.relativePath, new: "/", old: "/..") - XCTAssertTrue(absolute.hasDirectoryPath) + #expect(absolute.hasDirectoryPath) absolute.append(path: "absolute", directoryHint: .isDirectory) checkBehavior(absolute.path, new: "/absolute", old: "/../absolute") @@ -673,114 +606,114 @@ final class URLTests : XCTestCase { absolute = URL(filePath: "/absolute", directoryHint: .isDirectory) var relative = URL(filePath: "relative/path", directoryHint: .notDirectory, relativeTo: absolute) - XCTAssertEqual(relative.relativePath, "relative/path") - XCTAssertFalse(relative.hasDirectoryPath) - XCTAssertEqual(relative.path, "/absolute/relative/path") + #expect(relative.relativePath == "relative/path") + #expect(!relative.hasDirectoryPath) + #expect(relative.path == "/absolute/relative/path") relative.deleteLastPathComponent() - XCTAssertEqual(relative.relativePath, "relative") - XCTAssertTrue(relative.hasDirectoryPath) - XCTAssertEqual(relative.path, "/absolute/relative") + #expect(relative.relativePath == "relative") + #expect(relative.hasDirectoryPath) + #expect(relative.path == "/absolute/relative") relative.deleteLastPathComponent() - XCTAssertEqual(relative.relativePath, ".") - XCTAssertTrue(relative.hasDirectoryPath) - XCTAssertEqual(relative.path, "/absolute") + #expect(relative.relativePath == ".") + #expect(relative.hasDirectoryPath) + #expect(relative.path == "/absolute") relative.deleteLastPathComponent() - XCTAssertEqual(relative.relativePath, "..") - XCTAssertTrue(relative.hasDirectoryPath) - XCTAssertEqual(relative.path, "/") + #expect(relative.relativePath == "..") + #expect(relative.hasDirectoryPath) + #expect(relative.path == "/") relative.deleteLastPathComponent() - XCTAssertEqual(relative.relativePath, "../..") - XCTAssertTrue(relative.hasDirectoryPath) + #expect(relative.relativePath == "../..") + #expect(relative.hasDirectoryPath) checkBehavior(relative.path, new:"/", old: "/..") relative.append(path: "path", directoryHint: .isDirectory) - XCTAssertEqual(relative.relativePath, "../../path") - XCTAssertTrue(relative.hasDirectoryPath) + #expect(relative.relativePath == "../../path") + #expect(relative.hasDirectoryPath) checkBehavior(relative.path, new: "/path", old: "/../path") relative.deleteLastPathComponent() - XCTAssertEqual(relative.relativePath, "../..") - XCTAssertTrue(relative.hasDirectoryPath) + #expect(relative.relativePath == "../..") + #expect(relative.hasDirectoryPath) checkBehavior(relative.path, new: "/", old: "/..") relative = URL(filePath: "", relativeTo: absolute) - XCTAssertEqual(relative.relativePath, ".") - XCTAssertTrue(relative.hasDirectoryPath) - XCTAssertEqual(relative.path, "/absolute") + #expect(relative.relativePath == ".") + #expect(relative.hasDirectoryPath) + #expect(relative.path == "/absolute") relative.deleteLastPathComponent() - XCTAssertEqual(relative.relativePath, "..") - XCTAssertTrue(relative.hasDirectoryPath) - XCTAssertEqual(relative.path, "/") + #expect(relative.relativePath == "..") + #expect(relative.hasDirectoryPath) + #expect(relative.path == "/") relative.deleteLastPathComponent() - XCTAssertEqual(relative.relativePath, "../..") - XCTAssertTrue(relative.hasDirectoryPath) + #expect(relative.relativePath == "../..") + #expect(relative.hasDirectoryPath) checkBehavior(relative.path, new: "/", old: "/..") relative = URL(filePath: "relative/./", relativeTo: absolute) // According to RFC 3986, "." and ".." segments should not be removed // until the path is resolved against the base URL (when calling .path) checkBehavior(relative.relativePath, new: "relative/.", old: "relative") - XCTAssertTrue(relative.hasDirectoryPath) - XCTAssertEqual(relative.path, "/absolute/relative") + #expect(relative.hasDirectoryPath) + #expect(relative.path == "/absolute/relative") relative.deleteLastPathComponent() checkBehavior(relative.relativePath, new: "relative/..", old: ".") - XCTAssertTrue(relative.hasDirectoryPath) - XCTAssertEqual(relative.path, "/absolute") + #expect(relative.hasDirectoryPath) + #expect(relative.path == "/absolute") relative = URL(filePath: "relative/.", directoryHint: .isDirectory, relativeTo: absolute) checkBehavior(relative.relativePath, new: "relative/.", old: "relative") - XCTAssertTrue(relative.hasDirectoryPath) - XCTAssertEqual(relative.path, "/absolute/relative") + #expect(relative.hasDirectoryPath) + #expect(relative.path == "/absolute/relative") relative.deleteLastPathComponent() checkBehavior(relative.relativePath, new: "relative/..", old: ".") - XCTAssertTrue(relative.hasDirectoryPath) - XCTAssertEqual(relative.path, "/absolute") + #expect(relative.hasDirectoryPath) + #expect(relative.path == "/absolute") relative = URL(filePath: "relative/..", relativeTo: absolute) - XCTAssertEqual(relative.relativePath, "relative/..") + #expect(relative.relativePath == "relative/..") checkBehavior(relative.hasDirectoryPath, new: true, old: false) - XCTAssertEqual(relative.path, "/absolute") + #expect(relative.path == "/absolute") relative.deleteLastPathComponent() - XCTAssertEqual(relative.relativePath, "relative/../..") - XCTAssertTrue(relative.hasDirectoryPath) - XCTAssertEqual(relative.path, "/") + #expect(relative.relativePath == "relative/../..") + #expect(relative.hasDirectoryPath) + #expect(relative.path == "/") relative = URL(filePath: "relative/..", directoryHint: .isDirectory, relativeTo: absolute) - XCTAssertEqual(relative.relativePath, "relative/..") - XCTAssertTrue(relative.hasDirectoryPath) - XCTAssertEqual(relative.path, "/absolute") + #expect(relative.relativePath == "relative/..") + #expect(relative.hasDirectoryPath) + #expect(relative.path == "/absolute") relative.deleteLastPathComponent() - XCTAssertEqual(relative.relativePath, "relative/../..") - XCTAssertTrue(relative.hasDirectoryPath) - XCTAssertEqual(relative.path, "/") + #expect(relative.relativePath == "relative/../..") + #expect(relative.hasDirectoryPath) + #expect(relative.path == "/") - var url = try XCTUnwrap(URL(string: "scheme://host.with.no.path")) - XCTAssertTrue(url.path().isEmpty) + var url = try #require(URL(string: "scheme://host.with.no.path")) + #expect(url.path().isEmpty) url.deleteLastPathComponent() - XCTAssertEqual(url.absoluteString, "scheme://host.with.no.path") - XCTAssertTrue(url.path().isEmpty) + #expect(url.absoluteString == "scheme://host.with.no.path") + #expect(url.path().isEmpty) let unusedBase = URL(string: "base://url") - url = try XCTUnwrap(URL(string: "scheme://host.with.no.path", relativeTo: unusedBase)) - XCTAssertEqual(url.absoluteString, "scheme://host.with.no.path") - XCTAssertTrue(url.path().isEmpty) + url = try #require(URL(string: "scheme://host.with.no.path", relativeTo: unusedBase)) + #expect(url.absoluteString == "scheme://host.with.no.path") + #expect(url.path().isEmpty) url.deleteLastPathComponent() - XCTAssertEqual(url.absoluteString, "scheme://host.with.no.path") - XCTAssertTrue(url.path().isEmpty) + #expect(url.absoluteString == "scheme://host.with.no.path") + #expect(url.path().isEmpty) - var schemeRelative = try XCTUnwrap(URL(string: "scheme:relative/path")) + var schemeRelative = try #require(URL(string: "scheme:relative/path")) // Bug in the old implementation where a relative path is not recognized checkBehavior(schemeRelative.relativePath, new: "relative/path", old: "") @@ -788,111 +721,111 @@ final class URLTests : XCTestCase { checkBehavior(schemeRelative.relativePath, new: "relative", old: "") schemeRelative.deleteLastPathComponent() - XCTAssertEqual(schemeRelative.relativePath, "") + #expect(schemeRelative.relativePath == "") schemeRelative.deleteLastPathComponent() - XCTAssertEqual(schemeRelative.relativePath, "") + #expect(schemeRelative.relativePath == "") } - func testURLFilePathDropsTrailingSlashes() throws { + @Test func filePathDropsTrailingSlashes() throws { var url = URL(filePath: "/path/slashes///") - XCTAssertEqual(url.path(), "/path/slashes///") + #expect(url.path() == "/path/slashes///") // TODO: Update this once .fileSystemPath uses backslashes for Windows - XCTAssertEqual(url.fileSystemPath(), "/path/slashes") + #expect(url.fileSystemPath() == "/path/slashes") url = URL(filePath: "/path/slashes/") - XCTAssertEqual(url.path(), "/path/slashes/") - XCTAssertEqual(url.fileSystemPath(), "/path/slashes") + #expect(url.path() == "/path/slashes/") + #expect(url.fileSystemPath() == "/path/slashes") url = URL(filePath: "/path/slashes") - XCTAssertEqual(url.path(), "/path/slashes") - XCTAssertEqual(url.fileSystemPath(), "/path/slashes") + #expect(url.path() == "/path/slashes") + #expect(url.fileSystemPath() == "/path/slashes") } - func testURLNotDirectoryHintStripsTrailingSlash() throws { + @Test func notDirectoryHintStripsTrailingSlash() throws { // Supply a path with a trailing slash but say it's not a direcotry var url = URL(filePath: "/path/", directoryHint: .notDirectory) - XCTAssertFalse(url.hasDirectoryPath) - XCTAssertEqual(url.path(), "/path") + #expect(!url.hasDirectoryPath) + #expect(url.path() == "/path") url = URL(fileURLWithPath: "/path/", isDirectory: false) - XCTAssertFalse(url.hasDirectoryPath) - XCTAssertEqual(url.path(), "/path") + #expect(!url.hasDirectoryPath) + #expect(url.path() == "/path") url = URL(filePath: "/path///", directoryHint: .notDirectory) - XCTAssertFalse(url.hasDirectoryPath) - XCTAssertEqual(url.path(), "/path") + #expect(!url.hasDirectoryPath) + #expect(url.path() == "/path") url = URL(fileURLWithPath: "/path///", isDirectory: false) - XCTAssertFalse(url.hasDirectoryPath) - XCTAssertEqual(url.path(), "/path") + #expect(!url.hasDirectoryPath) + #expect(url.path() == "/path") // With .checkFileSystem, don't modify the path for a non-existent file url = URL(filePath: "/my/non/existent/path/", directoryHint: .checkFileSystem) - XCTAssertTrue(url.hasDirectoryPath) - XCTAssertEqual(url.path(), "/my/non/existent/path/") + #expect(url.hasDirectoryPath) + #expect(url.path() == "/my/non/existent/path/") url = URL(fileURLWithPath: "/my/non/existent/path/") - XCTAssertTrue(url.hasDirectoryPath) - XCTAssertEqual(url.path(), "/my/non/existent/path/") + #expect(url.hasDirectoryPath) + #expect(url.path() == "/my/non/existent/path/") url = URL(filePath: "/my/non/existent/path", directoryHint: .checkFileSystem) - XCTAssertFalse(url.hasDirectoryPath) - XCTAssertEqual(url.path(), "/my/non/existent/path") + #expect(!url.hasDirectoryPath) + #expect(url.path() == "/my/non/existent/path") url = URL(fileURLWithPath: "/my/non/existent/path") - XCTAssertFalse(url.hasDirectoryPath) - XCTAssertEqual(url.path(), "/my/non/existent/path") + #expect(!url.hasDirectoryPath) + #expect(url.path() == "/my/non/existent/path") } - func testURLHostRetainsIDNAEncoding() throws { + @Test func hostRetainsIDNAEncoding() throws { let url = URL(string: "ftp://user:password@*.xn--poema-9qae5a.com.br:4343/cat.txt")! - XCTAssertEqual(url.host, "*.xn--poema-9qae5a.com.br") + #expect(url.host == "*.xn--poema-9qae5a.com.br") } - func testURLHostIPLiteralCompatibility() throws { + @Test func hostIPLiteralCompatibility() throws { var url = URL(string: "http://[::]")! - XCTAssertEqual(url.host, "::") - XCTAssertEqual(url.host(), "::") + #expect(url.host == "::") + #expect(url.host() == "::") url = URL(string: "https://[::1]:433/")! - XCTAssertEqual(url.host, "::1") - XCTAssertEqual(url.host(), "::1") + #expect(url.host == "::1") + #expect(url.host() == "::1") url = URL(string: "https://[2001:db8::]/")! - XCTAssertEqual(url.host, "2001:db8::") - XCTAssertEqual(url.host(), "2001:db8::") + #expect(url.host == "2001:db8::") + #expect(url.host() == "2001:db8::") url = URL(string: "https://[2001:db8::]:433")! - XCTAssertEqual(url.host, "2001:db8::") - XCTAssertEqual(url.host(), "2001:db8::") + #expect(url.host == "2001:db8::") + #expect(url.host() == "2001:db8::") url = URL(string: "http://[fe80::a%25en1]")! - XCTAssertEqual(url.absoluteString, "http://[fe80::a%25en1]") - XCTAssertEqual(url.host, "fe80::a%en1") - XCTAssertEqual(url.host(percentEncoded: true), "fe80::a%25en1") - XCTAssertEqual(url.host(percentEncoded: false), "fe80::a%en1") + #expect(url.absoluteString == "http://[fe80::a%25en1]") + #expect(url.host == "fe80::a%en1") + #expect(url.host(percentEncoded: true) == "fe80::a%25en1") + #expect(url.host(percentEncoded: false) == "fe80::a%en1") url = URL(string: "http://[fe80::a%en1]")! - XCTAssertEqual(url.absoluteString, "http://[fe80::a%25en1]") - XCTAssertEqual(url.host, "fe80::a%en1") - XCTAssertEqual(url.host(percentEncoded: true), "fe80::a%25en1") - XCTAssertEqual(url.host(percentEncoded: false), "fe80::a%en1") + #expect(url.absoluteString == "http://[fe80::a%25en1]") + #expect(url.host == "fe80::a%en1") + #expect(url.host(percentEncoded: true) == "fe80::a%25en1") + #expect(url.host(percentEncoded: false) == "fe80::a%en1") url = URL(string: "http://[fe80::a%100%CustomZone]")! - XCTAssertEqual(url.absoluteString, "http://[fe80::a%25100%25CustomZone]") - XCTAssertEqual(url.host, "fe80::a%100%CustomZone") - XCTAssertEqual(url.host(percentEncoded: true), "fe80::a%25100%25CustomZone") - XCTAssertEqual(url.host(percentEncoded: false), "fe80::a%100%CustomZone") + #expect(url.absoluteString == "http://[fe80::a%25100%25CustomZone]") + #expect(url.host == "fe80::a%100%CustomZone") + #expect(url.host(percentEncoded: true) == "fe80::a%25100%25CustomZone") + #expect(url.host(percentEncoded: false) == "fe80::a%100%CustomZone") // Make sure an IP-literal with invalid characters `{` and `}` // returns `nil` even if we can percent-encode the zone-ID. let invalid = URL(string: "http://[{Invalid}%100%EncodableZone]") - XCTAssertNil(invalid) + #expect(invalid == nil) } #if !os(Windows) - func testURLTildeFilePath() throws { + @Test func tildeFilePath() throws { func urlIsAbsolute(_ url: URL) -> Bool { if url.relativePath.utf8.first == ._slash { return true @@ -905,34 +838,34 @@ final class URLTests : XCTestCase { // "~" must either be expanded to an absolute path or resolved against a base URL var url = URL(filePath: "~") - XCTAssertTrue(urlIsAbsolute(url)) + #expect(urlIsAbsolute(url)) url = URL(filePath: "~", directoryHint: .isDirectory) - XCTAssertTrue(urlIsAbsolute(url)) - XCTAssertEqual(url.path().utf8.last, ._slash) + #expect(urlIsAbsolute(url)) + #expect(url.path().utf8.last == ._slash) url = URL(filePath: "~/") - XCTAssertTrue(urlIsAbsolute(url)) - XCTAssertEqual(url.path().utf8.last, ._slash) + #expect(urlIsAbsolute(url)) + #expect(url.path().utf8.last == ._slash) } #endif // !os(Windows) - func testURLPathExtensions() throws { + @Test func pathExtensions() throws { var url = URL(filePath: "/path", directoryHint: .notDirectory) url.appendPathExtension("foo") - XCTAssertEqual(url.path(), "/path.foo") + #expect(url.path() == "/path.foo") url.deletePathExtension() - XCTAssertEqual(url.path(), "/path") + #expect(url.path() == "/path") url = URL(filePath: "/path", directoryHint: .isDirectory) url.appendPathExtension("foo") - XCTAssertEqual(url.path(), "/path.foo/") + #expect(url.path() == "/path.foo/") url.deletePathExtension() - XCTAssertEqual(url.path(), "/path/") + #expect(url.path() == "/path/") url = URL(filePath: "/path/", directoryHint: .inferFromPath) url.appendPathExtension("foo") - XCTAssertEqual(url.path(), "/path.foo/") + #expect(url.path() == "/path.foo/") url.append(path: "/////") url.deletePathExtension() // Old behavior only searches the last empty component, so the extension isn't actually removed @@ -940,161 +873,161 @@ final class URLTests : XCTestCase { url = URL(filePath: "/tmp/x") url.appendPathExtension("") - XCTAssertEqual(url.path(), "/tmp/x") - XCTAssertEqual(url, url.deletingPathExtension().appendingPathExtension(url.pathExtension)) + #expect(url.path() == "/tmp/x") + #expect(url == url.deletingPathExtension().appendingPathExtension(url.pathExtension)) url = URL(filePath: "/tmp/x.") url.deletePathExtension() - XCTAssertEqual(url.path(), "/tmp/x.") + #expect(url.path() == "/tmp/x.") } - func testURLAppendingToEmptyPath() throws { + @Test func appendingToEmptyPath() throws { let baseURL = URL(filePath: "/base/directory", directoryHint: .isDirectory) let emptyPathURL = URL(filePath: "", relativeTo: baseURL) let url = emptyPathURL.appending(path: "main.swift") - XCTAssertEqual(url.relativePath, "./main.swift") - XCTAssertEqual(url.path, "/base/directory/main.swift") + #expect(url.relativePath == "./main.swift") + #expect(url.path == "/base/directory/main.swift") - var example = try XCTUnwrap(URL(string: "https://example.com")) - XCTAssertEqual(example.host(), "example.com") - XCTAssertTrue(example.path().isEmpty) + var example = try #require(URL(string: "https://example.com")) + #expect(example.host() == "example.com") + #expect(example.path().isEmpty) // Appending to an empty path should add a slash if an authority exists // The appended path should never become part of the host example.append(path: "foo") - XCTAssertEqual(example.host(), "example.com") - XCTAssertEqual(example.path(), "/foo") - XCTAssertEqual(example.absoluteString, "https://example.com/foo") + #expect(example.host() == "example.com") + #expect(example.path() == "/foo") + #expect(example.absoluteString == "https://example.com/foo") // Maintain old behavior, where appending an empty path // to an empty host does not add a slash, but appending // an empty path to a non-empty host does - example = try XCTUnwrap(URL(string: "https://example.com")) + example = try #require(URL(string: "https://example.com")) example.append(path: "") - XCTAssertEqual(example.host(), "example.com") - XCTAssertEqual(example.path(), "/") - XCTAssertEqual(example.absoluteString, "https://example.com/") + #expect(example.host() == "example.com") + #expect(example.path() == "/") + #expect(example.absoluteString == "https://example.com/") - var emptyHost = try XCTUnwrap(URL(string: "scheme://")) - XCTAssertNil(emptyHost.host()) - XCTAssertTrue(emptyHost.path().isEmpty) + var emptyHost = try #require(URL(string: "scheme://")) + #expect(emptyHost.host() == nil) + #expect(emptyHost.path().isEmpty) emptyHost.append(path: "") - XCTAssertNil(emptyHost.host()) - XCTAssertTrue(emptyHost.path().isEmpty) + #expect(emptyHost.host() == nil) + #expect(emptyHost.path().isEmpty) emptyHost.append(path: "foo") - XCTAssertTrue(emptyHost.host()?.isEmpty ?? true) + #expect(emptyHost.host()?.isEmpty ?? true) // Old behavior failed to append correctly to an empty host // Modern parsers agree that "foo" relative to "scheme://" is "scheme:///foo" checkBehavior(emptyHost.path(), new: "/foo", old: "") checkBehavior(emptyHost.absoluteString, new: "scheme:///foo", old: "scheme://") - var schemeOnly = try XCTUnwrap(URL(string: "scheme:")) - XCTAssertTrue(schemeOnly.host()?.isEmpty ?? true) - XCTAssertTrue(schemeOnly.path().isEmpty) + var schemeOnly = try #require(URL(string: "scheme:")) + #expect(schemeOnly.host()?.isEmpty ?? true) + #expect(schemeOnly.path().isEmpty) schemeOnly.append(path: "foo") - XCTAssertTrue(schemeOnly.host()?.isEmpty ?? true) + #expect(schemeOnly.host()?.isEmpty ?? true) // Old behavior appends to the string, but is missing the path checkBehavior(schemeOnly.path(), new: "foo", old: "") - XCTAssertEqual(schemeOnly.absoluteString, "scheme:foo") + #expect(schemeOnly.absoluteString == "scheme:foo") } - func testURLEmptySchemeCompatibility() throws { - var url = try XCTUnwrap(URL(string: ":memory:")) - XCTAssertEqual(url.scheme, "") + @Test func emptySchemeCompatibility() throws { + var url = try #require(URL(string: ":memory:")) + #expect(url.scheme == "") - let base = try XCTUnwrap(URL(string: "://home")) - XCTAssertEqual(base.host(), "home") + let base = try #require(URL(string: "://home")) + #expect(base.host() == "home") - url = try XCTUnwrap(URL(string: "/path", relativeTo: base)) - XCTAssertEqual(url.scheme, "") - XCTAssertEqual(url.host(), "home") - XCTAssertEqual(url.path, "/path") - XCTAssertEqual(url.absoluteString, "://home/path") - XCTAssertEqual(url.absoluteURL.scheme, "") + url = try #require(URL(string: "/path", relativeTo: base)) + #expect(url.scheme == "") + #expect(url.host() == "home") + #expect(url.path == "/path") + #expect(url.absoluteString == "://home/path") + #expect(url.absoluteURL.scheme == "") } - func testURLComponentsPercentEncodedUnencodedProperties() throws { + @Test func componentsPercentEncodedUnencodedProperties() throws { var comp = URLComponents() comp.user = "%25" - XCTAssertEqual(comp.user, "%25") - XCTAssertEqual(comp.percentEncodedUser, "%2525") + #expect(comp.user == "%25") + #expect(comp.percentEncodedUser == "%2525") comp.password = "%25" - XCTAssertEqual(comp.password, "%25") - XCTAssertEqual(comp.percentEncodedPassword, "%2525") + #expect(comp.password == "%25") + #expect(comp.percentEncodedPassword == "%2525") // Host behavior differs since the addition of IDNA-encoding comp.host = "%25" - XCTAssertEqual(comp.host, "%") - XCTAssertEqual(comp.percentEncodedHost, "%25") + #expect(comp.host == "%") + #expect(comp.percentEncodedHost == "%25") comp.path = "%25" - XCTAssertEqual(comp.path, "%25") - XCTAssertEqual(comp.percentEncodedPath, "%2525") + #expect(comp.path == "%25") + #expect(comp.percentEncodedPath == "%2525") comp.query = "%25" - XCTAssertEqual(comp.query, "%25") - XCTAssertEqual(comp.percentEncodedQuery, "%2525") + #expect(comp.query == "%25") + #expect(comp.percentEncodedQuery == "%2525") comp.fragment = "%25" - XCTAssertEqual(comp.fragment, "%25") - XCTAssertEqual(comp.percentEncodedFragment, "%2525") + #expect(comp.fragment == "%25") + #expect(comp.percentEncodedFragment == "%2525") comp.queryItems = [URLQueryItem(name: "name", value: "a%25b")] - XCTAssertEqual(comp.queryItems, [URLQueryItem(name: "name", value: "a%25b")]) - XCTAssertEqual(comp.percentEncodedQueryItems, [URLQueryItem(name: "name", value: "a%2525b")]) - XCTAssertEqual(comp.query, "name=a%25b") - XCTAssertEqual(comp.percentEncodedQuery, "name=a%2525b") + #expect(comp.queryItems == [URLQueryItem(name: "name", value: "a%25b")]) + #expect(comp.percentEncodedQueryItems == [URLQueryItem(name: "name", value: "a%2525b")]) + #expect(comp.query == "name=a%25b") + #expect(comp.percentEncodedQuery == "name=a%2525b") } - func testURLPercentEncodedProperties() throws { + @Test func percentEncodedProperties() throws { var url = URL(string: "https://%3Auser:%3Apassword@%3A.com/%3Apath?%3Aquery=%3A#%3Afragment")! - XCTAssertEqual(url.user(), "%3Auser") - XCTAssertEqual(url.user(percentEncoded: false), ":user") + #expect(url.user() == "%3Auser") + #expect(url.user(percentEncoded: false) == ":user") - XCTAssertEqual(url.password(), "%3Apassword") - XCTAssertEqual(url.password(percentEncoded: false), ":password") + #expect(url.password() == "%3Apassword") + #expect(url.password(percentEncoded: false) == ":password") - XCTAssertEqual(url.host(), "%3A.com") - XCTAssertEqual(url.host(percentEncoded: false), ":.com") + #expect(url.host() == "%3A.com") + #expect(url.host(percentEncoded: false) == ":.com") - XCTAssertEqual(url.path(), "/%3Apath") - XCTAssertEqual(url.path(percentEncoded: false), "/:path") + #expect(url.path() == "/%3Apath") + #expect(url.path(percentEncoded: false) == "/:path") - XCTAssertEqual(url.query(), "%3Aquery=%3A") - XCTAssertEqual(url.query(percentEncoded: false), ":query=:") + #expect(url.query() == "%3Aquery=%3A") + #expect(url.query(percentEncoded: false) == ":query=:") - XCTAssertEqual(url.fragment(), "%3Afragment") - XCTAssertEqual(url.fragment(percentEncoded: false), ":fragment") + #expect(url.fragment() == "%3Afragment") + #expect(url.fragment(percentEncoded: false) == ":fragment") // Lowercase input url = URL(string: "https://%3auser:%3apassword@%3a.com/%3apath?%3aquery=%3a#%3afragment")! - XCTAssertEqual(url.user(), "%3auser") - XCTAssertEqual(url.user(percentEncoded: false), ":user") + #expect(url.user() == "%3auser") + #expect(url.user(percentEncoded: false) == ":user") - XCTAssertEqual(url.password(), "%3apassword") - XCTAssertEqual(url.password(percentEncoded: false), ":password") + #expect(url.password() == "%3apassword") + #expect(url.password(percentEncoded: false) == ":password") - XCTAssertEqual(url.host(), "%3a.com") - XCTAssertEqual(url.host(percentEncoded: false), ":.com") + #expect(url.host() == "%3a.com") + #expect(url.host(percentEncoded: false) == ":.com") - XCTAssertEqual(url.path(), "/%3apath") - XCTAssertEqual(url.path(percentEncoded: false), "/:path") + #expect(url.path() == "/%3apath") + #expect(url.path(percentEncoded: false) == "/:path") - XCTAssertEqual(url.query(), "%3aquery=%3a") - XCTAssertEqual(url.query(percentEncoded: false), ":query=:") + #expect(url.query() == "%3aquery=%3a") + #expect(url.query(percentEncoded: false) == ":query=:") - XCTAssertEqual(url.fragment(), "%3afragment") - XCTAssertEqual(url.fragment(percentEncoded: false), ":fragment") + #expect(url.fragment() == "%3afragment") + #expect(url.fragment(percentEncoded: false) == ":fragment") } - func testURLComponentsUppercasePercentEncoding() throws { + @Test func componentsUppercasePercentEncoding() throws { // Always use uppercase percent-encoding when unencoded components are assigned var comp = URLComponents() comp.scheme = "https" @@ -1103,18 +1036,16 @@ final class URLTests : XCTestCase { comp.path = "?path" comp.query = "#query" comp.fragment = "#fragment" - XCTAssertEqual(comp.percentEncodedUser, "%3Fuser") - XCTAssertEqual(comp.percentEncodedPassword, "%3Fpassword") - XCTAssertEqual(comp.percentEncodedPath, "%3Fpath") - XCTAssertEqual(comp.percentEncodedQuery, "%23query") - XCTAssertEqual(comp.percentEncodedFragment, "%23fragment") + #expect(comp.percentEncodedUser == "%3Fuser") + #expect(comp.percentEncodedPassword == "%3Fpassword") + #expect(comp.percentEncodedPath == "%3Fpath") + #expect(comp.percentEncodedQuery == "%23query") + #expect(comp.percentEncodedFragment == "%23fragment") } - - func testURLComponentsRangeCombinations() throws { - // This brute forces many combinations and takes a long time. - // Skip this for automated testing purposes and test manually when needed. - try XCTSkipIf(true) - + + // This brute forces many combinations and takes a long time. + @Test(.disabled("Disabled in automated testing - enable manually when needed")) + func URLComponentsRangeCombinations() throws { let schemes = [nil, "a", "aa"] let users = [nil, "b", "bb"] let passwords = [nil, "c", "cc"] @@ -1145,16 +1076,16 @@ final class URLTests : XCTestCase { } func validateRanges(_ comp: URLComponents, scheme: String?, user: String?, password: String?, host: String?, port: Int?, path: String, query: String?, fragment: String?) throws { - let string = try XCTUnwrap(comp.string) + let string = try #require(comp.string) if let scheme { - let range = try XCTUnwrap(comp.rangeOfScheme) - XCTAssertTrue(string[range] == scheme) + let range = try #require(comp.rangeOfScheme) + #expect(string[range] == scheme) } else { - XCTAssertNil(comp.rangeOfScheme) + #expect(comp.rangeOfScheme == nil) } if let user { - let range = try XCTUnwrap(comp.rangeOfUser) - XCTAssertTrue(string[range] == user) + let range = try #require(comp.rangeOfUser) + #expect(string[range] == user) } else { // Even if we set comp.user = nil, a non-nil password // implies that user exists as the empty string. @@ -1163,17 +1094,17 @@ final class URLTests : XCTestCase { comp.rangeOfUser?.isEmpty ?? false && comp.password != nil ) - XCTAssertTrue(comp.rangeOfUser == nil || isEmptyUserWithPassword) + #expect(comp.rangeOfUser == nil || isEmptyUserWithPassword) } if let password { - let range = try XCTUnwrap(comp.rangeOfPassword) - XCTAssertTrue(string[range] == password) + let range = try #require(comp.rangeOfPassword) + #expect(string[range] == password) } else { - XCTAssertNil(comp.rangeOfPassword) + #expect(comp.rangeOfPassword == nil) } if let host { - let range = try XCTUnwrap(comp.rangeOfHost) - XCTAssertTrue(string[range] == host) + let range = try #require(comp.rangeOfHost) + #expect(string[range] == host) } else { // Even if we set comp.host = nil, any non-nil authority component // implies that host exists as the empty string. @@ -1182,28 +1113,28 @@ final class URLTests : XCTestCase { comp.rangeOfHost?.isEmpty ?? false && (user != nil || password != nil || port != nil) ) - XCTAssertTrue(comp.rangeOfHost == nil || isEmptyHostWithAuthorityComponent) + #expect(comp.rangeOfHost == nil || isEmptyHostWithAuthorityComponent) } if let port { - let range = try XCTUnwrap(comp.rangeOfPort) - XCTAssertTrue(string[range] == String(port)) + let range = try #require(comp.rangeOfPort) + #expect(string[range] == String(port)) } else { - XCTAssertNil(comp.rangeOfPort) + #expect(comp.rangeOfPort == nil) } // rangeOfPath should never be nil. - let pathRange = try XCTUnwrap(comp.rangeOfPath) - XCTAssertTrue(string[pathRange] == path) + let pathRange = try #require(comp.rangeOfPath) + #expect(string[pathRange] == path) if let query { - let range = try XCTUnwrap(comp.rangeOfQuery) - XCTAssertTrue(string[range] == query) + let range = try #require(comp.rangeOfQuery) + #expect(string[range] == query) } else { - XCTAssertNil(comp.rangeOfQuery) + #expect(comp.rangeOfQuery == nil) } if let fragment { - let range = try XCTUnwrap(comp.rangeOfFragment) - XCTAssertTrue(string[range] == fragment) + let range = try #require(comp.rangeOfFragment) + #expect(string[range] == fragment) } else { - XCTAssertNil(comp.rangeOfFragment) + #expect(comp.rangeOfFragment == nil) } } @@ -1222,8 +1153,8 @@ final class URLTests : XCTestCase { comp.fragment = fragment try validateRanges(comp, scheme: scheme, user: user, password: password, host: host, port: port, path: path, query: query, fragment: fragment) - let string = try XCTUnwrap(comp.string) - let fullComponents = URLComponents(string: string)! + let string = try #require(comp.string) + let fullComponents = try #require(URLComponents(string: string)) // Get the ranges directly from URLParseInfo @@ -1270,8 +1201,8 @@ final class URLTests : XCTestCase { comp.scheme = nil try validateRanges(comp, scheme: nil, user: user, password: password, host: host, port: port, path: path, query: query, fragment: fragment) - let stringWithoutScheme = try XCTUnwrap(comp.string) - comp = URLComponents(string: stringWithoutScheme)! + let stringWithoutScheme = try #require(comp.string) + comp = try #require(URLComponents(string: stringWithoutScheme)) comp.scheme = scheme try validateRanges(comp, scheme: scheme, user: user, password: password, host: host, port: port, path: path, query: query, fragment: fragment) @@ -1285,8 +1216,8 @@ final class URLTests : XCTestCase { comp.user = nil try validateRanges(comp, scheme: scheme, user: nil, password: password, host: expectedHost, port: port, path: path, query: query, fragment: fragment) - let stringWithoutUser = try XCTUnwrap(comp.string) - comp = URLComponents(string: stringWithoutUser)! + let stringWithoutUser = try #require(comp.string) + comp = try #require(URLComponents(string: stringWithoutUser)) comp.user = user try validateRanges(comp, scheme: scheme, user: user, password: password, host: host, port: port, path: path, query: query, fragment: fragment) @@ -1300,8 +1231,8 @@ final class URLTests : XCTestCase { comp.password = nil try validateRanges(comp, scheme: scheme, user: expectedUser, password: nil, host: host, port: port, path: path, query: query, fragment: fragment) - let stringWithoutPassword = try XCTUnwrap(comp.string) - comp = URLComponents(string: stringWithoutPassword)! + let stringWithoutPassword = try #require(comp.string) + comp = try #require(URLComponents(string: stringWithoutPassword)) comp.password = password try validateRanges(comp, scheme: scheme, user: user, password: password, host: host, port: port, path: path, query: query, fragment: fragment) @@ -1309,8 +1240,8 @@ final class URLTests : XCTestCase { comp.host = nil try validateRanges(comp, scheme: scheme, user: user, password: password, host: nil, port: port, path: path, query: query, fragment: fragment) - let stringWithoutHost = try XCTUnwrap(comp.string) - comp = URLComponents(string: stringWithoutHost)! + let stringWithoutHost = try #require(comp.string) + comp = try #require(URLComponents(string: stringWithoutHost)) comp.host = host try validateRanges(comp, scheme: scheme, user: user, password: password, host: host, port: port, path: path, query: query, fragment: fragment) @@ -1324,8 +1255,8 @@ final class URLTests : XCTestCase { comp.port = nil try validateRanges(comp, scheme: scheme, user: user, password: password, host: expectedHost, port: nil, path: path, query: query, fragment: fragment) - let stringWithoutPort = try XCTUnwrap(comp.string) - comp = URLComponents(string: stringWithoutPort)! + let stringWithoutPort = try #require(comp.string) + comp = try #require(URLComponents(string: stringWithoutPort)) comp.port = port try validateRanges(comp, scheme: scheme, user: user, password: password, host: host, port: port, path: path, query: query, fragment: fragment) @@ -1333,8 +1264,8 @@ final class URLTests : XCTestCase { comp.path = "" try validateRanges(comp, scheme: scheme, user: user, password: password, host: host, port: port, path: "", query: query, fragment: fragment) - let stringWithoutPath = try XCTUnwrap(comp.string) - comp = URLComponents(string: stringWithoutPath)! + let stringWithoutPath = try #require(comp.string) + comp = try #require(URLComponents(string: stringWithoutPath)) comp.path = path try validateRanges(comp, scheme: scheme, user: user, password: password, host: host, port: port, path: path, query: query, fragment: fragment) @@ -1342,8 +1273,8 @@ final class URLTests : XCTestCase { comp.query = nil try validateRanges(comp, scheme: scheme, user: user, password: password, host: host, port: port, path: path, query: nil, fragment: fragment) - let stringWithoutQuery = try XCTUnwrap(comp.string) - comp = URLComponents(string: stringWithoutQuery)! + let stringWithoutQuery = try #require(comp.string) + comp = try #require(URLComponents(string: stringWithoutQuery)) comp.query = query try validateRanges(comp, scheme: scheme, user: user, password: password, host: host, port: port, path: path, query: query, fragment: fragment) @@ -1351,242 +1282,290 @@ final class URLTests : XCTestCase { comp.fragment = nil try validateRanges(comp, scheme: scheme, user: user, password: password, host: host, port: port, path: path, query: query, fragment: nil) - let stringWithoutFragment = try XCTUnwrap(comp.string) - comp = URLComponents(string: stringWithoutFragment)! + let stringWithoutFragment = try #require(comp.string) + comp = try #require(URLComponents(string: stringWithoutFragment)) comp.fragment = fragment try validateRanges(comp, scheme: scheme, user: user, password: password, host: host, port: port, path: path, query: query, fragment: fragment) } } - func testURLComponentsEncodesFirstPathColon() throws { + @Test func componentsEncodesFirstPathColon() throws { let path = "first:segment:with:colons/second:segment:with:colons" var comp = URLComponents() comp.path = path - guard let compString = comp.string else { - XCTFail("compString was nil") - return - } - guard let slashIndex = compString.firstIndex(of: "/") else { - XCTFail("Could not find slashIndex") - return - } + let compString = try #require(comp.string) + let slashIndex = try #require(compString.firstIndex(of: "/")) let firstSegment = compString[.. Date: Thu, 12 Jun 2025 19:27:43 -0700 Subject: [PATCH 2/5] Fix Linux/Windows build failures --- .../FileManager/FileManagerTests.swift | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift b/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift index 7d22915d2..35fb07b8c 100644 --- a/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift +++ b/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift @@ -774,27 +774,27 @@ private struct FileManagerTests { "foo" } "bar" - }.test { - #expect(try $0.subpathsOfDirectory(atPath: ".").sorted() == ["bar", "dir", "dir/foo"]) - #expect($0.changeCurrentDirectoryPath("dir")) - #expect(try $0.subpathsOfDirectory(atPath: ".") == ["foo"]) - #expect(!$0.changeCurrentDirectoryPath("foo")) - #expect($0.changeCurrentDirectoryPath("..")) - #expect(try $0.subpathsOfDirectory(atPath: ".").sorted() == ["bar", "dir", "dir/foo"]) - #expect(!$0.changeCurrentDirectoryPath("does_not_exist")) + }.test { fileManager in + #expect(try fileManager.subpathsOfDirectory(atPath: ".").sorted() == ["bar", "dir", "dir/foo"]) + #expect(fileManager.changeCurrentDirectoryPath("dir")) + #expect(try fileManager.subpathsOfDirectory(atPath: ".") == ["foo"]) + #expect(!fileManager.changeCurrentDirectoryPath("foo")) + #expect(fileManager.changeCurrentDirectoryPath("..")) + #expect(try fileManager.subpathsOfDirectory(atPath: ".").sorted() == ["bar", "dir", "dir/foo"]) + #expect(!fileManager.changeCurrentDirectoryPath("does_not_exist")) // Test get current directory path when it's parent directory was removed. - #expect($0.changeCurrentDirectoryPath("dir")) + #expect(fileManager.changeCurrentDirectoryPath("dir")) #if os(Windows) // Removing the current working directory fails on Windows because the directory is in use. #expect { - try $0.removeItem(atPath: $0.currentDirectoryPath) + try fileManager.removeItem(atPath: $0.currentDirectoryPath) } throws: { ($0 as? CocoaError)?.code == .fileWriteNoPermission } #else - try $0.removeItem(atPath: $0.currentDirectoryPath) - #expect($0.currentDirectoryPath == "") + try fileManager.removeItem(atPath: fileManager.currentDirectoryPath) + #expect(fileManager.currentDirectoryPath == "") #endif } } @@ -1010,7 +1010,7 @@ private struct FileManagerTests { #if canImport(Darwin) || os(Windows) @Test(.disabled("This test is not applicable on this platform")) #else - @Test(.disabled(if: ProcessInfo.processInfo.environment.keys.first(where: { $0.starts(with: "XDG") }), "Skipping due to presence of XDG environment variables which may affect this test")) + @Test(.disabled(if: ProcessInfo.processInfo.environment.keys.contains(where: { $0.starts(with: "XDG") }), "Skipping due to presence of XDG environment variables which may affect this test")) #endif func searchPaths_XDGEnvironmentVariables() async throws { try await FilePlayground { From 2c561472bcdbd7d95bc9e80c515c73f2063fbc0f Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Thu, 12 Jun 2025 21:19:35 -0700 Subject: [PATCH 3/5] Fix Windows build failures --- .../FileManager/FileManagerTests.swift | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift b/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift index 35fb07b8c..82eacfe6c 100644 --- a/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift +++ b/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift @@ -788,7 +788,7 @@ private struct FileManagerTests { #if os(Windows) // Removing the current working directory fails on Windows because the directory is in use. #expect { - try fileManager.removeItem(atPath: $0.currentDirectoryPath) + try fileManager.removeItem(atPath: fileManager.currentDirectoryPath) } throws: { ($0 as? CocoaError)?.code == .fileWriteNoPermission } @@ -1007,11 +1007,8 @@ private struct FileManagerTests { assertSearchPaths([.itemReplacementDirectory], exists: false) } - #if canImport(Darwin) || os(Windows) - @Test(.disabled("This test is not applicable on this platform")) - #else + #if !canImport(Darwin) && !os(Windows) @Test(.disabled(if: ProcessInfo.processInfo.environment.keys.contains(where: { $0.starts(with: "XDG") }), "Skipping due to presence of XDG environment variables which may affect this test")) - #endif func searchPaths_XDGEnvironmentVariables() async throws { try await FilePlayground { Directory("TestPath") {} @@ -1042,6 +1039,7 @@ private struct FileManagerTests { validate("HOME", directory: .userDirectory, domain: .localDomainMask) } } + #endif @Test func getSetAttributes() async throws { try await FilePlayground { From 28459f8e42a2055a058fbbfa2f89fb691eee8e31 Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Fri, 13 Jun 2025 09:18:31 -0700 Subject: [PATCH 4/5] Remove URL prefix from other test functions --- Tests/FoundationEssentialsTests/URLTests.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Tests/FoundationEssentialsTests/URLTests.swift b/Tests/FoundationEssentialsTests/URLTests.swift index 7a02872df..8c49e3bcf 100644 --- a/Tests/FoundationEssentialsTests/URLTests.swift +++ b/Tests/FoundationEssentialsTests/URLTests.swift @@ -177,7 +177,7 @@ private struct URLTests { } @Test(.enabled(if: foundation_swift_url_enabled())) - func URLPathAPIsResolveAgainstBase() throws { + func pathAPIsResolveAgainstBase() throws { // Borrowing the same test cases from RFC 3986, but checking paths let base = URL(string: "http://a/b/c/d;p?q") let tests = [ @@ -244,7 +244,7 @@ private struct URLTests { @Test(.enabled(if: foundation_swift_url_enabled())) - func URLPathComponentsPercentEncodedSlash() throws { + func pathComponentsPercentEncodedSlash() throws { var url = try #require(URL(string: "https://example.com/https%3A%2F%2Fexample.com")) #expect(url.pathComponents == ["/", "https://example.com"]) @@ -266,7 +266,7 @@ private struct URLTests { @Test(.enabled(if: foundation_swift_url_enabled())) - func URLRootlessPath() throws { + func rootlessPath() throws { let paths = ["", "path"] let queries = [nil, "query"] let fragments = [nil, "fragment"] @@ -533,7 +533,7 @@ private struct URLTests { "[This is not a valid URL without encoding.]", "Encoding a relative path! 😎", ]) - func URLEncodingInvalidCharacters(urlString: String) throws { + func encodingInvalidCharacters(urlString: String) throws { var url = URL(string: urlString, encodingInvalidCharacters: true) #expect(url != nil, "Expected a percent-encoded url for string \(urlString)") url = URL(string: urlString, encodingInvalidCharacters: false) @@ -1045,7 +1045,7 @@ private struct URLTests { // This brute forces many combinations and takes a long time. @Test(.disabled("Disabled in automated testing - enable manually when needed")) - func URLComponentsRangeCombinations() throws { + func componentsRangeCombinations() throws { let schemes = [nil, "a", "aa"] let users = [nil, "b", "bb"] let passwords = [nil, "c", "cc"] From 92f55bb33798ceb9ae3cb297be43df19dd26b3c7 Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Fri, 13 Jun 2025 11:33:54 -0700 Subject: [PATCH 5/5] Fix Windows build failures --- .../FileManager/FileManagerTests.swift | 2 +- .../FoundationEssentialsTests/FileManager/FilePlayground.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift b/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift index 82eacfe6c..4d59b9e66 100644 --- a/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift +++ b/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift @@ -774,7 +774,7 @@ private struct FileManagerTests { "foo" } "bar" - }.test { fileManager in + }.test { (fileManager) throws in #expect(try fileManager.subpathsOfDirectory(atPath: ".").sorted() == ["bar", "dir", "dir/foo"]) #expect(fileManager.changeCurrentDirectoryPath("dir")) #expect(try fileManager.subpathsOfDirectory(atPath: ".") == ["foo"]) diff --git a/Tests/FoundationEssentialsTests/FileManager/FilePlayground.swift b/Tests/FoundationEssentialsTests/FileManager/FilePlayground.swift index 12138740e..38fb4355e 100644 --- a/Tests/FoundationEssentialsTests/FileManager/FilePlayground.swift +++ b/Tests/FoundationEssentialsTests/FileManager/FilePlayground.swift @@ -156,6 +156,6 @@ struct FilePlayground { try tester(fileManager) } try fileManager.removeItem(atPath: createdDir) - _fixLifetime(capturingDelegate) // Ensure capturingDelegate lives beyond the tester body + extendLifetime(capturingDelegate) // Ensure capturingDelegate lives beyond the tester body } }