diff --git a/Tests/BasicsTests/FileSystem/FileSystemTests.swift b/Tests/BasicsTests/FileSystem/FileSystemTests.swift index 32c6ddb121d..270fcc968f8 100644 --- a/Tests/BasicsTests/FileSystem/FileSystemTests.swift +++ b/Tests/BasicsTests/FileSystem/FileSystemTests.swift @@ -9,77 +9,84 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import Foundation +import TSCTestSupport +import Testing @testable import Basics -import TSCTestSupport -import XCTest -final class FileSystemTests: XCTestCase { - func testStripFirstLevelComponent() throws { +struct FileSystemTests { + @Test + func stripFirstLevelComponent() throws { let fileSystem = InMemoryFileSystem() let rootPath = AbsolutePath("/root") try fileSystem.createDirectory(rootPath) - let totalDirectories = Int.random(in: 0 ..< 100) - for index in 0 ..< totalDirectories { + let totalDirectories = Int.random(in: 0..<100) + for index in 0.. String { -// return "Path '\(pa) \(exists ? "should exists, but doesn't" : "should not exist, but does.")" -// } - -// try withKnownIssue { -// try fs.createDirectory(pathUnderTest, recursive: recursive) - -// for (p, shouldExist) in expectedFiles { -// let expectedPath = AbsolutePath(p) -// #expect(fs.exists(expectedPath) == shouldExist, "\(errorMessage(expectedPath, shouldExist))") -// } -// } when: { -// expectError -// } -// } - - -// @Test( -// arguments: [ -// "/", -// "/tmp", -// "/tmp/", -// "/something/ws", -// "/something/ws/", -// "/what/is/this", -// "/what/is/this/", -// ] -// ) -// func callingCreateDirectoryOnAnExistingDirectoryIsSuccessful(path: String) async throws { -// let root = AbsolutePath(path) -// let fs = InMemoryFileSystem() - -// #expect(throws: Never.self) { -// try fs.createDirectory(root, recursive: true) -// } - -// #expect(throws: Never.self) { -// try fs.createDirectory(root.appending("more"), recursive: true) -// } -// } - -// struct writeFileContentsTests { - -// @Test -// func testWriteFileContentsSuccessful() async throws { -// // GIVEN we have a filesytstem -// let fs = InMemoryFileSystem() -// // and a path -// let pathUnderTest = AbsolutePath("/myFile.zip") -// let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) - -// // WHEN we write contents to the file -// try fs.writeFileContents(pathUnderTest, bytes: expectedContents) - -// // THEN we expect the file to exist -// #expect(fs.exists(pathUnderTest), "Path \(pathUnderTest.pathString) does not exists when it should") -// } - -// @Test -// func testWritingAFileWithANonExistingParentDirectoryFails() async throws { -// // GIVEN we have a filesytstem -// let fs = InMemoryFileSystem() -// // and a path -// let pathUnderTest = AbsolutePath("/tmp/myFile.zip") -// let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) - -// // WHEN we write contents to the file -// // THEn we expect an error to occus -// try withKnownIssue { -// try fs.writeFileContents(pathUnderTest, bytes: expectedContents) -// } - -// // AND we expect the file to not exist -// #expect(!fs.exists(pathUnderTest), "Path \(pathUnderTest.pathString) does exists when it should not") -// } - -// @Test -// func errorOccursWhenWritingToRootDirectory() async throws { -// // GIVEN we have a filesytstem -// let fs = InMemoryFileSystem() -// // and a path -// let pathUnderTest = AbsolutePath("/") -// let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) - -// // WHEN we write contents to the file -// // THEN we expect an error to occur -// try withKnownIssue { -// try fs.writeFileContents(pathUnderTest, bytes: expectedContents) -// } - -// } - -// @Test -// func testErrorOccursIfParentIsNotADirectory() async throws { -// // GIVEN we have a filesytstem -// let fs = InMemoryFileSystem() -// // AND an existing file -// let aFile = AbsolutePath("/foo") -// try fs.writeFileContents(aFile, bytes: "") - -// // AND a the path under test that has an existing file as a parent -// let pathUnderTest = aFile.appending("myFile") -// let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) - -// // WHEN we write contents to the file -// // THEN we expect an error to occur -// withKnownIssue { -// try fs.writeFileContents(pathUnderTest, bytes: expectedContents) -// } - -// } -// } - - -// struct testReadFileContentsTests { -// @Test -// func readingAFileThatDoesNotExistsRaisesAnError()async throws { -// // GIVEN we have a filesystem -// let fs = InMemoryFileSystem() - -// // WHEN we read a non-existing file -// // THEN an error occurs -// try withKnownIssue { -// let _ = try fs.readFileContents("/file/does/not/exists") -// } -// } - -// @Test -// func readingExistingFileReturnsExpectedContents() async throws { -// // GIVEN we have a filesytstem -// let fs = InMemoryFileSystem() -// // AND a file a path -// let pathUnderTest = AbsolutePath("/myFile.zip") -// let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) -// try fs.writeFileContents(pathUnderTest, bytes: expectedContents) - -// // WHEN we read contents if the file -// let actualContents = try fs.readFileContents(pathUnderTest) - -// // THEN the actual contents should match the expected to match the -// #expect(actualContents == expectedContents, "Actual is not as expected") -// } - -// @Test -// func readingADirectoryFailsWithAnError() async throws { -// // GIVEN we have a filesytstem -// let fs = InMemoryFileSystem() -// // AND a file a path -// let pathUnderTest = AbsolutePath("/myFile.zip") -// let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) -// try fs.writeFileContents(pathUnderTest, bytes: expectedContents) - -// // WHEN we read the contents of a directory -// // THEN we expect a failure to occur -// withKnownIssue { -// let _ = try fs.readFileContents(pathUnderTest.parentDirectory) -// } -// } -// } -// } +struct InMemoryFileSystemTests { + @Test( + arguments: [ + ( + path: "/", + recurvise: true, + expectedFiles: [ + (p: "/", shouldExist: true) + ], + expectError: false + ), + ( + path: "/tmp", + recurvise: true, + expectedFiles: [ + (p: "/", shouldExist: true), + (p: "/tmp", shouldExist: true), + ], + expectError: false + ), + ( + path: "/tmp/ws", + recurvise: true, + expectedFiles: [ + (p: "/", shouldExist: true), + (p: "/tmp", shouldExist: true), + (p: "/tmp/ws", shouldExist: true), + ], + expectError: false + ), + ( + path: "/tmp/ws", + recurvise: false, + expectedFiles: [ + (p: "/", shouldExist: true), + (p: "/tmp", shouldExist: true), + (p: "/tmp/ws", shouldExist: true), + ], + expectError: true + ), + ] + ) + func creatingDirectoryCreatesInternalFiles( + path: String, + recursive: Bool, + expectedFiles: [(String, Bool)], + expectError: Bool + ) async throws { + let fs = InMemoryFileSystem() + let pathUnderTest = AbsolutePath(path) + + func errorMessage(_ pa: AbsolutePath, _ exists: Bool) -> String { + return + "Path '\(pa) \(exists ? "should exists, but doesn't" : "should not exist, but does.")" + } + + try withKnownIssue { + try fs.createDirectory(pathUnderTest, recursive: recursive) + + for (p, shouldExist) in expectedFiles { + let expectedPath = AbsolutePath(p) + #expect( + fs.exists(expectedPath) == shouldExist, + "\(errorMessage(expectedPath, shouldExist))") + } + } when: { + expectError + } + } + + + @Test( + arguments: [ + "/", + "/tmp", + "/tmp/", + "/something/ws", + "/something/ws/", + "/what/is/this", + "/what/is/this/", + ] + ) + func callingCreateDirectoryOnAnExistingDirectoryIsSuccessful(path: String) async throws { + let root = AbsolutePath(path) + let fs = InMemoryFileSystem() + + #expect(throws: Never.self) { + try fs.createDirectory(root, recursive: true) + } + + #expect(throws: Never.self) { + try fs.createDirectory(root.appending("more"), recursive: true) + } + } + + struct writeFileContentsTests { + + @Test + func testWriteFileContentsSuccessful() async throws { + // GIVEN we have a filesytstem + let fs = InMemoryFileSystem() + // and a path + let pathUnderTest = AbsolutePath("/myFile.zip") + let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) + + // WHEN we write contents to the file + try fs.writeFileContents(pathUnderTest, bytes: expectedContents) + + // THEN we expect the file to exist + #expect( + fs.exists(pathUnderTest), + "Path \(pathUnderTest.pathString) does not exists when it should") + } + + @Test + func testWritingAFileWithANonExistingParentDirectoryFails() async throws { + // GIVEN we have a filesytstem + let fs = InMemoryFileSystem() + // and a path + let pathUnderTest = AbsolutePath("/tmp/myFile.zip") + let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) + + // WHEN we write contents to the file + // THEn we expect an error to occus + try withKnownIssue { + try fs.writeFileContents(pathUnderTest, bytes: expectedContents) + } + + // AND we expect the file to not exist + #expect( + !fs.exists(pathUnderTest), + "Path \(pathUnderTest.pathString) does exists when it should not") + } + + @Test + func errorOccursWhenWritingToRootDirectory() async throws { + // GIVEN we have a filesytstem + let fs = InMemoryFileSystem() + // and a path + let pathUnderTest = AbsolutePath("/") + let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) + + // WHEN we write contents to the file + // THEN we expect an error to occur + try withKnownIssue { + try fs.writeFileContents(pathUnderTest, bytes: expectedContents) + } + + } + + @Test + func testErrorOccursIfParentIsNotADirectory() async throws { + // GIVEN we have a filesytstem + let fs = InMemoryFileSystem() + // AND an existing file + let aFile = AbsolutePath("/foo") + try fs.writeFileContents(aFile, bytes: "") + + // AND a the path under test that has an existing file as a parent + let pathUnderTest = aFile.appending("myFile") + let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) + + // WHEN we write contents to the file + // THEN we expect an error to occur + withKnownIssue { + try fs.writeFileContents(pathUnderTest, bytes: expectedContents) + } + + } + } + + + struct testReadFileContentsTests { + @Test + func readingAFileThatDoesNotExistsRaisesAnError() async throws { + // GIVEN we have a filesystem + let fs = InMemoryFileSystem() + + // WHEN we read a non-existing file + // THEN an error occurs + try withKnownIssue { + let _ = try fs.readFileContents("/file/does/not/exists") + } + } + + @Test + func readingExistingFileReturnsExpectedContents() async throws { + // GIVEN we have a filesytstem + let fs = InMemoryFileSystem() + // AND a file a path + let pathUnderTest = AbsolutePath("/myFile.zip") + let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) + try fs.writeFileContents(pathUnderTest, bytes: expectedContents) + + // WHEN we read contents if the file + let actualContents = try fs.readFileContents(pathUnderTest) + + // THEN the actual contents should match the expected to match the + #expect(actualContents == expectedContents, "Actual is not as expected") + } + + @Test + func readingADirectoryFailsWithAnError() async throws { + // GIVEN we have a filesytstem + let fs = InMemoryFileSystem() + // AND a file a path + let pathUnderTest = AbsolutePath("/myFile.zip") + let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) + try fs.writeFileContents(pathUnderTest, bytes: expectedContents) + + // WHEN we read the contents of a directory + // THEN we expect a failure to occur + withKnownIssue { + let _ = try fs.readFileContents(pathUnderTest.parentDirectory) + } + } + } +} diff --git a/Tests/BasicsTests/FileSystem/PathShimTests.swift b/Tests/BasicsTests/FileSystem/PathShimTests.swift index 6f0af62a76f..3dc752c2837 100644 --- a/Tests/BasicsTests/FileSystem/PathShimTests.swift +++ b/Tests/BasicsTests/FileSystem/PathShimTests.swift @@ -12,64 +12,65 @@ import Basics import Foundation -import XCTest +import Testing -class PathShimTests: XCTestCase { - func testRescursiveDirectoryCreation() { - // For the tests we'll need a temporary directory. +struct PathShimTests { + @Test + func rescursiveDirectoryCreation() { try! withTemporaryDirectory(removeTreeOnDeinit: true) { path in // Create a directory under several ancestor directories. let dirPath = path.appending(components: "abc", "def", "ghi", "mno", "pqr") try! makeDirectories(dirPath) // Check that we were able to actually create the directory. - XCTAssertTrue(localFileSystem.isDirectory(dirPath)) + #expect(localFileSystem.isDirectory(dirPath)) // Check that there's no error if we try to create the directory again. - try! makeDirectories(dirPath) + #expect(throws: Never.self) { + try! makeDirectories(dirPath) + } } } } -class WalkTests: XCTestCase { - func testNonRecursive() throws { - #if os(Android) +struct WalkTests { + @Test + func nonRecursive() throws { +#if os(Android) let root = "/system" var expected: [AbsolutePath] = [ "\(root)/usr", "\(root)/bin", "\(root)/etc", ] - #elseif os(Windows) + let expectedCount = 3 +#elseif os(Windows) let root = ProcessInfo.processInfo.environment["SystemRoot"]! var expected: [AbsolutePath] = [ "\(root)/System32", "\(root)/SysWOW64", ] - #else + let expectedCount = (root as NSString).pathComponents.count + 2 +#else let root = "" var expected: [AbsolutePath] = [ "/usr", "/bin", "/sbin", ] - #endif + let expectedCount = 2 +#endif for x in try walk(AbsolutePath(validating: "\(root)/"), recursively: false) { if let i = expected.firstIndex(of: x) { expected.remove(at: i) } - #if os(Android) - XCTAssertEqual(3, x.components.count) - #elseif os(Windows) - XCTAssertEqual((root as NSString).pathComponents.count + 2, x.components.count) - #else - XCTAssertEqual(2, x.components.count) - #endif + #expect(x.components.count == expectedCount, "Actual is not as expected") } - XCTAssertEqual(expected.count, 0) + #expect(expected.count == 0) } - func testRecursive() { + @Test + func recursive() { let root = AbsolutePath(#file).parentDirectory.parentDirectory.parentDirectory.parentDirectory .appending(component: "Sources") var expected = [ @@ -82,6 +83,6 @@ class WalkTests: XCTestCase { expected.remove(at: i) } } - XCTAssertEqual(expected, []) + #expect(expected == []) } } diff --git a/Tests/BasicsTests/FileSystem/PathTests.swift b/Tests/BasicsTests/FileSystem/PathTests.swift index 85679ebd3e1..5cf741f1a34 100644 --- a/Tests/BasicsTests/FileSystem/PathTests.swift +++ b/Tests/BasicsTests/FileSystem/PathTests.swift @@ -6,13 +6,11 @@ See http://swift.org/LICENSE.txt for license information See http://swift.org/CONTRIBUTORS.txt for Swift project authors -*/ + */ import Basics import Foundation -import XCTest - -import _InternalTestSupport // for XCTSkipOnWindows() +import Testing #if os(Windows) private var windows: Bool { true } @@ -20,362 +18,687 @@ private var windows: Bool { true } private var windows: Bool { false } #endif -class PathTests: XCTestCase { - func testBasics() { - XCTAssertEqual(AbsolutePath("/").pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/a").pathString, windows ? #"\a"# : "/a") - XCTAssertEqual(AbsolutePath("/a/b/c").pathString, windows ? #"\a\b\c"# : "/a/b/c") - XCTAssertEqual(RelativePath(".").pathString, ".") - XCTAssertEqual(RelativePath("a").pathString, "a") - XCTAssertEqual(RelativePath("a/b/c").pathString, windows ? #"a\b\c"# : "a/b/c") - XCTAssertEqual(RelativePath("~").pathString, "~") // `~` is not special - } - func testStringInitialization() throws { - let abs1 = AbsolutePath("/") - let abs2 = AbsolutePath(abs1, ".") - XCTAssertEqual(abs1, abs2) - let rel3 = "." - let abs3 = try AbsolutePath(abs2, validating: rel3) - XCTAssertEqual(abs2, abs3) - let base = AbsolutePath("/base/path") - let abs4 = AbsolutePath("/a/b/c", relativeTo: base) - XCTAssertEqual(abs4, AbsolutePath("/a/b/c")) - let abs5 = AbsolutePath("./a/b/c", relativeTo: base) - XCTAssertEqual(abs5, AbsolutePath("/base/path/a/b/c")) - let abs6 = AbsolutePath("~/bla", relativeTo: base) // `~` isn't special - XCTAssertEqual(abs6, AbsolutePath("/base/path/~/bla")) - } +struct PathTests { + struct AbsolutePathTests { + private func pathStringIsSetCorrectlyTestImplementation(path: String, expected: String, label: String) { + let actual = AbsolutePath(path).pathString - func testStringLiteralInitialization() { - let abs = AbsolutePath("/") - XCTAssertEqual(abs.pathString, windows ? #"\"# : "/") - let rel1 = RelativePath(".") - XCTAssertEqual(rel1.pathString, ".") - let rel2 = RelativePath("~") - XCTAssertEqual(rel2.pathString, "~") // `~` is not special - } + #expect(actual == expected, "\(label): Actual is not as expected. Path is: \(path)") + } - func testRepeatedPathSeparators() throws { - try XCTSkipOnWindows(because: "all assertions fail") + @Test( + arguments: [ + (path: "/", expected: (windows ? #"\"# : "/"), label: "Basics"), + (path: "/a", expected: (windows ? #"\a"# : "/a"), label: "Basics"), + (path: "/a/b/c", expected: (windows ? #"\a\b\c"# : "/a/b/c"), label: "Basics"), + ] + ) + func pathStringIsSetCorrectly(path: String, expected: String, label: String) { + pathStringIsSetCorrectlyTestImplementation( + path: path, + expected: expected, + label: label + ) + } - XCTAssertEqual(AbsolutePath("/ab//cd//ef").pathString, windows ? #"\ab\cd\ef"# : "/ab/cd/ef") - XCTAssertEqual(AbsolutePath("/ab///cd//ef").pathString, windows ? #"\ab\cd\ef"# : "/ab/cd/ef") - XCTAssertEqual(RelativePath("ab//cd//ef").pathString, windows ? #"ab\cd\ef"# : "ab/cd/ef") - XCTAssertEqual(RelativePath("ab//cd///ef").pathString, windows ? #"ab\cd\ef"# : "ab/cd/ef") - } + @Test( + arguments: [ + (path: "/ab/cd/ef/", expected: (windows ? #"\ab\cd\ef"# : "/ab/cd/ef"), label: "Trailing path seperator"), + (path: "/ab/cd/ef//", expected: (windows ? #"\ab\cd\ef"# : "/ab/cd/ef"), label: "Trailing path seperator"), + (path: "/ab/cd/ef///", expected: (windows ? #"\ab\cd\ef"# : "/ab/cd/ef"), label: "Trailing path seperator"), + (path: "/ab//cd//ef", expected: (windows ? #"\ab\cd\ef"# : "/ab/cd/ef"), label: "repeated path seperators"), + (path: "/ab///cd//ef", expected: (windows ? #"\ab\cd\ef"# : "/ab/cd/ef"), label: "repeated path seperators"), + (path: "/ab/././cd//ef", expected: "/ab/cd/ef", label: "dot path component"), + (path: "/ab/./cd//ef/.", expected: "/ab/cd/ef", label: "dot path component"), + (path: "/..", expected: (windows ? #"\"# : "/"), label: "dot dot path component"), + (path: "/../../../../..", expected: (windows ? #"\"# : "/"), label: "dot dot path component"), + (path: "/abc/..", expected: (windows ? #"\"# : "/"), label: "dot dot path component"), + (path: "/abc/../..", expected: (windows ? #"\"# : "/"), label: "dot dot path component"), + (path: "/../abc", expected: (windows ? #"\abc"# : "/abc"), label: "dot dot path component"), + (path: "/../abc/..", expected: (windows ? #"\"# : "/"), label: "dot dot path component"), + (path: "/../abc/../def", expected: (windows ? #"\def"# : "/def"), label: "dot dot path component"), + (path: "///", expected: (windows ? #"\"# : "/"), label: "combinations and edge cases"), + (path: "/./", expected: (windows ? #"\"# : "/"), label: "combinations and edge cases"), + ] + ) + func pathStringIsSetCorrectlySkipOnWindows(path: String, expected: String, label: String) { + withKnownIssue("https://github.com/swiftlang/swift-package-manager/issues/8511: Path \(path) is not properly") { + pathStringIsSetCorrectlyTestImplementation( + path: path, + expected: expected, + label: label + ) + } when :{ + ProcessInfo.hostOperatingSystem == .windows + } + } - func testTrailingPathSeparators() throws { - try XCTSkipOnWindows(because: "trailing path seperator is not removed from pathString") + @Test + func stringInitialization() throws { + let abs1 = AbsolutePath("/") + let abs2 = AbsolutePath(abs1, ".") + #expect(abs1 == abs2) + let rel3 = "." + let abs3 = try AbsolutePath(abs2, validating: rel3) + #expect(abs2 == abs3) + let base = AbsolutePath("/base/path") + let abs4 = AbsolutePath("/a/b/c", relativeTo: base) + #expect(abs4 == AbsolutePath("/a/b/c")) + let abs5 = AbsolutePath("./a/b/c", relativeTo: base) + #expect(abs5 == AbsolutePath("/base/path/a/b/c")) + let abs6 = AbsolutePath("~/bla", relativeTo: base) // `~` isn't special + #expect(abs6 == AbsolutePath("/base/path/~/bla")) + } - XCTAssertEqual(AbsolutePath("/ab/cd/ef/").pathString, windows ? #"\ab\cd\ef"# : "/ab/cd/ef") - XCTAssertEqual(AbsolutePath("/ab/cd/ef//").pathString, windows ? #"\ab\cd\ef"# : "/ab/cd/ef") - XCTAssertEqual(RelativePath("ab/cd/ef/").pathString, windows ? #"ab\cd\ef"# : "ab/cd/ef") - XCTAssertEqual(RelativePath("ab/cd/ef//").pathString, windows ? #"ab\cd\ef"# : "ab/cd/ef") - } + struct AbsolutePathDirectoryNameAtributeReturnsExpectedValue { + private func testImplementation(path: String, expected: String) throws { + let actual = AbsolutePath(path).dirname + + #expect(actual == expected, "Actual is not as expected. Path is: \(path)") + } + + @Test( + arguments: [ + (path: "/", expected: (windows ? #"\"# : "/")), + (path: "/a", expected: (windows ? #"\"# : "/")), + (path: "/ab/c//d/", expected: (windows ? #"\ab\c"# : "/ab/c")), + ] + ) + func absolutePathDirectoryNameAttributeAllPlatforms(path: String, expected: String) throws { + try testImplementation(path: path, expected: expected) + } + + @Test( + arguments: [ + (path: "/./a", expected: (windows ? #"\"# : "/")), + (path: "/../..", expected: (windows ? #"\"# : "/")), + ] + ) + func absolutePathDirectoryNameAttributeFailsOnWindows(path: String, expected: String) throws { + try withKnownIssue("https://github.com/swiftlang/swift-package-manager/issues/8511: Path \(path) is not properly") { + try testImplementation(path: path, expected: expected) + } when: { + ProcessInfo.hostOperatingSystem == .windows + } + } + } - func testDotPathComponents() throws { - try XCTSkipOnWindows() + struct AbsolutePathBaseNameAttributeReturnsExpectedValue { + private func testImplementation(path: String, expected: String) throws { + let actual = AbsolutePath(path).basename + + #expect(actual == expected, "Actual is not as expected: \(path)") + } + + @Test( + arguments: [ + (path: "/", expected: (windows ? #"\"# : "/")), + (path: "/a", expected: "a"), + (path: "/./a", expected: "a"), + ] + ) + func absolutePathBaseNameExtractionAllPlatforms(path: String, expected: String) throws { + try testImplementation(path: path, expected: expected) + } + + @Test( + arguments: [ + (path: "/../..", expected: "/"), + ] + ) + func absolutePathBaseNameExtractionFailsOnWindows(path: String, expected: String) throws { + try withKnownIssue("https://github.com/swiftlang/swift-package-manager/issues/8511: Path \(path) is not properly") { + try testImplementation(path: path, expected: expected) + } when: { + ProcessInfo.hostOperatingSystem == .windows + } + } + } - XCTAssertEqual(AbsolutePath("/ab/././cd//ef").pathString, "/ab/cd/ef") - XCTAssertEqual(AbsolutePath("/ab/./cd//ef/.").pathString, "/ab/cd/ef") - XCTAssertEqual(RelativePath("ab/./cd/././ef").pathString, "ab/cd/ef") - XCTAssertEqual(RelativePath("ab/./cd/ef/.").pathString, "ab/cd/ef") - } + struct AbsolutePathBasenameWithoutExtAttributeReturnsExpectedValue { + private func testImplementation(path: String, expected: String) throws { + let actual = AbsolutePath(path).basenameWithoutExt + + #expect(actual == expected, "Actual is not as expected. Path is: \(path)") + + } + + @Test( + arguments: [ + (path: "/", expected: (windows ? #"\"# : "/")), + (path: "/a", expected: "a"), + (path: "/./a", expected: "a"), + (path: "/a.txt", expected: "a"), + (path: "/./a.txt", expected: "a"), + ] + ) + func absolutePathBaseNameWithoutExt(path: String, expected: String) throws { + try testImplementation(path: path, expected: expected) + } + + @Test( + arguments: [ + (path: "/../..", expected: "/"), + ] + ) + func absolutePathBaseNameWithoutExtFailedOnWindows(path: String, expected: String) throws { + try withKnownIssue("https://github.com/swiftlang/swift-package-manager/issues/8511: Path \(path) is not handled properly") { + try testImplementation(path: path, expected: expected) + } when: { + ProcessInfo.hostOperatingSystem == .windows + } + } + } - func testDotDotPathComponents() throws { - try XCTSkipOnWindows() - - XCTAssertEqual(AbsolutePath("/..").pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/../../../../..").pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/abc/..").pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/abc/../..").pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/../abc").pathString, windows ? #"\abc"# : "/abc") - XCTAssertEqual(AbsolutePath("/../abc/..").pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/../abc/../def").pathString, windows ? #"\def"# : "/def") - XCTAssertEqual(RelativePath("..").pathString, "..") - XCTAssertEqual(RelativePath("../..").pathString, "../..") - XCTAssertEqual(RelativePath(".././..").pathString, "../..") - XCTAssertEqual(RelativePath("../abc/..").pathString, "..") - XCTAssertEqual(RelativePath("../abc/.././").pathString, "..") - XCTAssertEqual(RelativePath("abc/..").pathString, ".") - } + struct AbsolutePathParentDirectoryAttributeReturnsExpectedValue { + private func testImplementation(path: String, numParentDirectoryCalls: Int, expected: String) throws { + let pathUnderTest = AbsolutePath(path) + let expectedPath = AbsolutePath(expected) + try #require(numParentDirectoryCalls >= 1, "Test configuration Error.") + + var actual = pathUnderTest + for _ in 0 ..< numParentDirectoryCalls { + actual = actual.parentDirectory + } + #expect(actual == expectedPath) + } + @Test( + arguments: [ + (path: "/", numParentDirectoryCalls: 1, expected: "/"), + (path: "/", numParentDirectoryCalls: 2, expected: "/"), + (path: "/bar", numParentDirectoryCalls: 1, expected: "/"), + ] + ) + func absolutePathParentDirectoryAttributeReturnsAsExpected(path: String, numParentDirectoryCalls: Int, expected: String) throws { + try testImplementation(path: path, numParentDirectoryCalls: numParentDirectoryCalls, expected: expected) + } + + @Test( + arguments: [ + (path: "/bar/../foo/..//", numParentDirectoryCalls: 2, expected: "/"), + (path: "/bar/../foo/..//yabba/a/b", numParentDirectoryCalls: 2, expected: "/yabba") + ] + ) + func absolutePathParentDirectoryAttributeReturnsAsExpectedFailsOnWindows(path: String, numParentDirectoryCalls: Int, expected: String) throws { + try withKnownIssue("https://github.com/swiftlang/swift-package-manager/issues/8511: Path \(path) is not handled properly") { + try testImplementation(path: path, numParentDirectoryCalls: numParentDirectoryCalls, expected: expected) + } when: { + ProcessInfo.hostOperatingSystem == .windows + } + } - func testCombinationsAndEdgeCases() throws { - try XCTSkipOnWindows() - - XCTAssertEqual(AbsolutePath("///").pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/./").pathString, windows ? #"\"# : "/") - XCTAssertEqual(RelativePath("").pathString, ".") - XCTAssertEqual(RelativePath(".").pathString, ".") - XCTAssertEqual(RelativePath("./abc").pathString, "abc") - XCTAssertEqual(RelativePath("./abc/").pathString, "abc") - XCTAssertEqual(RelativePath("./abc/../bar").pathString, "bar") - XCTAssertEqual(RelativePath("foo/../bar").pathString, "bar") - XCTAssertEqual(RelativePath("foo///..///bar///baz").pathString, "bar/baz") - XCTAssertEqual(RelativePath("foo/../bar/./").pathString, "bar") - XCTAssertEqual(RelativePath("../abc/def/").pathString, "../abc/def") - XCTAssertEqual(RelativePath("././././.").pathString, ".") - XCTAssertEqual(RelativePath("./././../.").pathString, "..") - XCTAssertEqual(RelativePath("./").pathString, ".") - XCTAssertEqual(RelativePath(".//").pathString, ".") - XCTAssertEqual(RelativePath("./.").pathString, ".") - XCTAssertEqual(RelativePath("././").pathString, ".") - XCTAssertEqual(RelativePath("../").pathString, "..") - XCTAssertEqual(RelativePath("../.").pathString, "..") - XCTAssertEqual(RelativePath("./..").pathString, "..") - XCTAssertEqual(RelativePath("./../.").pathString, "..") - XCTAssertEqual(RelativePath("./////../////./////").pathString, "..") - XCTAssertEqual(RelativePath("../a").pathString, windows ? #"..\a"# : "../a") - XCTAssertEqual(RelativePath("../a/..").pathString, "..") - XCTAssertEqual(RelativePath("a/..").pathString, ".") - XCTAssertEqual(RelativePath("a/../////../////./////").pathString, "..") - } + } - func testDirectoryNameExtraction() throws { - try XCTSkipOnWindows() - - XCTAssertEqual(AbsolutePath("/").dirname, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/a").dirname, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/./a").dirname, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/../..").dirname, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/ab/c//d/").dirname, windows ? #"\ab\c"# : "/ab/c") - XCTAssertEqual(RelativePath("ab/c//d/").dirname, windows ? #"ab\c"# : "ab/c") - XCTAssertEqual(RelativePath("../a").dirname, "..") - XCTAssertEqual(RelativePath("../a/..").dirname, ".") - XCTAssertEqual(RelativePath("a/..").dirname, ".") - XCTAssertEqual(RelativePath("./..").dirname, ".") - XCTAssertEqual(RelativePath("a/../////../////./////").dirname, ".") - XCTAssertEqual(RelativePath("abc").dirname, ".") - XCTAssertEqual(RelativePath("").dirname, ".") - XCTAssertEqual(RelativePath(".").dirname, ".") - } + @Test( + arguments: [ + (path: "/", expected: ["/"]), + (path: "/.", expected: ["/"]), + (path: "/..", expected: ["/"]), + (path: "/bar", expected: ["/", "bar"]), + (path: "/foo/bar/..", expected: ["/", "foo"]), + (path: "/bar/../foo", expected: ["/", "foo"]), + (path: "/bar/../foo/..//", expected: ["/"]), + (path: "/bar/../foo/..//yabba/a/b/", expected: ["/", "yabba", "a", "b"]), + ] + ) + func componentsAttributeReturnsExpectedValue(path: String, expected: [String]) throws { + withKnownIssue("https://github.com/swiftlang/swift-package-manager/issues/8511: Path \(path) is not handled properly") { + let actual = AbsolutePath(path).components + + #expect(actual == expected, "Actual is not as expected. Path is: \(path)") + } when: { + ProcessInfo.hostOperatingSystem == .windows + } + } - func testBaseNameExtraction() throws { - try XCTSkipOnWindows() - - XCTAssertEqual(AbsolutePath("/").basename, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/a").basename, "a") - XCTAssertEqual(AbsolutePath("/./a").basename, "a") - XCTAssertEqual(AbsolutePath("/../..").basename, "/") - XCTAssertEqual(RelativePath("../..").basename, "..") - XCTAssertEqual(RelativePath("../a").basename, "a") - XCTAssertEqual(RelativePath("../a/..").basename, "..") - XCTAssertEqual(RelativePath("a/..").basename, ".") - XCTAssertEqual(RelativePath("./..").basename, "..") - XCTAssertEqual(RelativePath("a/../////../////./////").basename, "..") - XCTAssertEqual(RelativePath("abc").basename, "abc") - XCTAssertEqual(RelativePath("").basename, ".") - XCTAssertEqual(RelativePath(".").basename, ".") - } + struct AncestryTest { + @Test( + arguments: [ + (path: "/a/b/c/d/e/f", descendentOfOrEqualTo: "/a/b/c/d", expected: true), + (path: "/a/b/c/d/e/f.swift", descendentOfOrEqualTo: "/a/b/c", expected: true), + (path: "/", descendentOfOrEqualTo: "/", expected: true), + (path: "/foo/bar", descendentOfOrEqualTo: "/", expected: true), + (path: "/foo/bar", descendentOfOrEqualTo: "/foo/bar/baz", expected: false), + (path: "/foo/bar", descendentOfOrEqualTo: "/bar", expected: false) + ] + ) + func isDescendantOfOrEqual(path: String, descendentOfOrEqualTo: String, expected: Bool) { + let actual = AbsolutePath(path).isDescendantOfOrEqual(to: AbsolutePath(descendentOfOrEqualTo)) + + #expect(actual == expected, "Actual is not as expected. Path is: \(path)") + } + + @Test( + arguments: [ + (path: "/foo/bar", descendentOf: "/foo/bar", expected: false), + (path: "/foo/bar", descendentOf: "/foo", expected: true) + ] + ) + func isDescendant(path: String, ancesterOf: String, expected: Bool) { + let actual = AbsolutePath(path).isDescendant(of: AbsolutePath(ancesterOf)) + + #expect(actual == expected, "Actual is not as expected. Path is: \(path)") + } + + @Test( + arguments: [ + (path: "/a/b/c/d", ancestorOfOrEqualTo: "/a/b/c/d/e/f", expected: true), + (path: "/a/b/c", ancestorOfOrEqualTo: "/a/b/c/d/e/f.swift", expected: true), + (path: "/", ancestorOfOrEqualTo: "/", expected: true), + (path: "/", ancestorOfOrEqualTo: "/foo/bar", expected: true), + (path: "/foo/bar/baz", ancestorOfOrEqualTo: "/foo/bar", expected: false), + (path: "/bar", ancestorOfOrEqualTo: "/foo/bar", expected: false), + ] + ) + func isAncestorOfOrEqual(path: String, ancestorOfOrEqualTo: String, expected: Bool) { + let actual = AbsolutePath(path).isAncestorOfOrEqual(to: AbsolutePath(ancestorOfOrEqualTo)) + + #expect(actual == expected, "Actual is not as expected. Path is: \(path)") + } + + @Test( + arguments: [ + (path: "/foo/bar", ancesterOf: "/foo/bar", expected: false), + (path: "/foo", ancesterOf: "/foo/bar", expected: true), + ] + ) + func isAncestor(path: String, ancesterOf: String, expected: Bool) { + let actual = AbsolutePath(path).isAncestor(of: AbsolutePath(ancesterOf)) + + #expect(actual == expected, "Actual is not as expected. Path is: \(path)") + } + } - func testBaseNameWithoutExt() throws{ - try XCTSkipOnWindows() - - XCTAssertEqual(AbsolutePath("/").basenameWithoutExt, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/a").basenameWithoutExt, "a") - XCTAssertEqual(AbsolutePath("/./a").basenameWithoutExt, "a") - XCTAssertEqual(AbsolutePath("/../..").basenameWithoutExt, "/") - XCTAssertEqual(RelativePath("../..").basenameWithoutExt, "..") - XCTAssertEqual(RelativePath("../a").basenameWithoutExt, "a") - XCTAssertEqual(RelativePath("../a/..").basenameWithoutExt, "..") - XCTAssertEqual(RelativePath("a/..").basenameWithoutExt, ".") - XCTAssertEqual(RelativePath("./..").basenameWithoutExt, "..") - XCTAssertEqual(RelativePath("a/../////../////./////").basenameWithoutExt, "..") - XCTAssertEqual(RelativePath("abc").basenameWithoutExt, "abc") - XCTAssertEqual(RelativePath("").basenameWithoutExt, ".") - XCTAssertEqual(RelativePath(".").basenameWithoutExt, ".") - - XCTAssertEqual(AbsolutePath("/a.txt").basenameWithoutExt, "a") - XCTAssertEqual(AbsolutePath("/./a.txt").basenameWithoutExt, "a") - XCTAssertEqual(RelativePath("../a.bc").basenameWithoutExt, "a") - XCTAssertEqual(RelativePath("abc.swift").basenameWithoutExt, "abc") - XCTAssertEqual(RelativePath("../a.b.c").basenameWithoutExt, "a.b") - XCTAssertEqual(RelativePath("abc.xyz.123").basenameWithoutExt, "abc.xyz") - } + @Test + func absolutePathValidation() throws { + #expect(throws: Never.self) { + try AbsolutePath(validating: "/a/b/c/d") + } + + withKnownIssue { + #expect {try AbsolutePath(validating: "~/a/b/d")} throws: { error in + ("\(error)" == "invalid absolute path '~/a/b/d'; absolute path must begin with '/'") + } + } when: { + ProcessInfo.hostOperatingSystem == .windows + } + + #expect {try AbsolutePath(validating: "a/b/d") } throws: { error in + ("\(error)" == "invalid absolute path 'a/b/d'") + } + } + + @Test + func comparison() { + #expect(AbsolutePath("/") <= AbsolutePath("/")); + #expect(AbsolutePath("/abc") < AbsolutePath("/def")); + #expect(AbsolutePath("/2") <= AbsolutePath("/2.1")); + #expect(AbsolutePath("/3.1") > AbsolutePath("/2")); + #expect(AbsolutePath("/2") >= AbsolutePath("/2")); + #expect(AbsolutePath("/2.1") >= AbsolutePath("/2")); + } - func testSuffixExtraction() throws { - try XCTSkipOnWindows(because: "expected nil is not the actual") - - XCTAssertEqual(RelativePath("a").suffix, nil) - XCTAssertEqual(RelativePath("a").extension, nil) - XCTAssertEqual(RelativePath("a.").suffix, nil) - XCTAssertEqual(RelativePath("a.").extension, nil) - XCTAssertEqual(RelativePath(".a").suffix, nil) - XCTAssertEqual(RelativePath(".a").extension, nil) - XCTAssertEqual(RelativePath("").suffix, nil) - XCTAssertEqual(RelativePath("").extension, nil) - XCTAssertEqual(RelativePath(".").suffix, nil) - XCTAssertEqual(RelativePath(".").extension, nil) - XCTAssertEqual(RelativePath("..").suffix, nil) - XCTAssertEqual(RelativePath("..").extension, nil) - XCTAssertEqual(RelativePath("a.foo").suffix, ".foo") - XCTAssertEqual(RelativePath("a.foo").extension, "foo") - XCTAssertEqual(RelativePath(".a.foo").suffix, ".foo") - XCTAssertEqual(RelativePath(".a.foo").extension, "foo") - XCTAssertEqual(RelativePath(".a.foo.bar").suffix, ".bar") - XCTAssertEqual(RelativePath(".a.foo.bar").extension, "bar") - XCTAssertEqual(RelativePath("a.foo.bar").suffix, ".bar") - XCTAssertEqual(RelativePath("a.foo.bar").extension, "bar") - XCTAssertEqual(RelativePath(".a.foo.bar.baz").suffix, ".baz") - XCTAssertEqual(RelativePath(".a.foo.bar.baz").extension, "baz") } + struct RelativePathTests { + private func pathStringIsSetCorrectlyTestImplementation(path: String, expected: String, label: String) { + let actual = RelativePath(path).pathString - func testParentDirectory() throws { - try XCTSkipOnWindows() + #expect(actual == expected, "\(label): Actual is not as expected. Path is: \(path)") + } - XCTAssertEqual(AbsolutePath("/").parentDirectory, AbsolutePath("/")) - XCTAssertEqual(AbsolutePath("/").parentDirectory.parentDirectory, AbsolutePath("/")) - XCTAssertEqual(AbsolutePath("/bar").parentDirectory, AbsolutePath("/")) - XCTAssertEqual(AbsolutePath("/bar/../foo/..//").parentDirectory.parentDirectory, AbsolutePath("/")) - XCTAssertEqual(AbsolutePath("/bar/../foo/..//yabba/a/b").parentDirectory.parentDirectory, AbsolutePath("/yabba")) - } + @Test( + arguments: [ + (path: ".", expected: ".", label: "Basics"), + (path: "a", expected: "a", label: "Basics"), + (path: "a/b/c", expected: (windows ? #"a\b\c"# : "a/b/c"), label: "Basics"), + (path: "~", expected: "~", label: "Basics"), + (path: "..", expected: "..", label: "dot dot path component"), + (path: "", expected: ".", label: "combinations and edge cases"), + (path: ".", expected: ".", label: "combinations and edge cases"), + (path: "../a", expected: (windows ? #"..\a"# : "../a"), label: "combinations and edge cases"), + ] + ) + func pathStringIsSetCorrectly(path: String, expected: String, label: String) { + pathStringIsSetCorrectlyTestImplementation( + path: path, + expected: expected, + label: label + ) + } - @available(*, deprecated) - func testConcatenation() throws { - try XCTSkipOnWindows() - - XCTAssertEqual(AbsolutePath(AbsolutePath("/"), RelativePath("")).pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath(AbsolutePath("/"), RelativePath(".")).pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath(AbsolutePath("/"), RelativePath("..")).pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath(AbsolutePath("/"), RelativePath("bar")).pathString, windows ? #"\bar"# : "/bar") - XCTAssertEqual(AbsolutePath(AbsolutePath("/foo/bar"), RelativePath("..")).pathString, windows ? #"\foo"# : "/foo") - XCTAssertEqual(AbsolutePath(AbsolutePath("/bar"), RelativePath("../foo")).pathString, windows ? #"\foo"# : "/foo") - XCTAssertEqual(AbsolutePath(AbsolutePath("/bar"), RelativePath("../foo/..//")).pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath(AbsolutePath("/bar/../foo/..//yabba/"), RelativePath("a/b")).pathString, windows ? #"\yabba\a\b"# : "/yabba/a/b") - - XCTAssertEqual(AbsolutePath("/").appending(RelativePath("")).pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/").appending(RelativePath(".")).pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/").appending(RelativePath("..")).pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/").appending(RelativePath("bar")).pathString, windows ? #"\bar"# : "/bar") - XCTAssertEqual(AbsolutePath("/foo/bar").appending(RelativePath("..")).pathString, windows ? #"\foo"# : "/foo") - XCTAssertEqual(AbsolutePath("/bar").appending(RelativePath("../foo")).pathString, windows ? #"\foo"# : "/foo") - XCTAssertEqual(AbsolutePath("/bar").appending(RelativePath("../foo/..//")).pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/bar/../foo/..//yabba/").appending(RelativePath("a/b")).pathString, windows ? #"\yabba\a\b"# : "/yabba/a/b") - - XCTAssertEqual(AbsolutePath("/").appending(component: "a").pathString, windows ? #"\a"# : "/a") - XCTAssertEqual(AbsolutePath("/a").appending(component: "b").pathString, windows ? #"\a\b"# : "/a/b") - XCTAssertEqual(AbsolutePath("/").appending(components: "a", "b").pathString, windows ? #"\a\b"# : "/a/b") - XCTAssertEqual(AbsolutePath("/a").appending(components: "b", "c").pathString, windows ? #"\a\b\c"# : "/a/b/c") - - XCTAssertEqual(AbsolutePath("/a/b/c").appending(components: "", "c").pathString, windows ? #"\a\b\c\c"# : "/a/b/c/c") - XCTAssertEqual(AbsolutePath("/a/b/c").appending(components: "").pathString, windows ? #"\a\b\c"# : "/a/b/c") - XCTAssertEqual(AbsolutePath("/a/b/c").appending(components: ".").pathString, windows ? #"\a\b\c"# : "/a/b/c") - XCTAssertEqual(AbsolutePath("/a/b/c").appending(components: "..").pathString, windows ? #"\a\b"# : "/a/b") - XCTAssertEqual(AbsolutePath("/a/b/c").appending(components: "..", "d").pathString, windows ? #"\a\b\d"# : "/a/b/d") - XCTAssertEqual(AbsolutePath("/").appending(components: "..").pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/").appending(components: ".").pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/").appending(components: "..", "a").pathString, windows ? #"\a"# : "/a") - - XCTAssertEqual(RelativePath("hello").appending(components: "a", "b", "c", "..").pathString, windows ? #"hello\a\b"# : "hello/a/b") - XCTAssertEqual(RelativePath("hello").appending(RelativePath("a/b/../c/d")).pathString, windows ? #"hello\a\c\d"# : "hello/a/c/d") - } + @Test( + arguments: [ + (path: "ab//cd//ef", expected: (windows ? #"ab\cd\ef"# : "ab/cd/ef"), label: "repeated path seperators"), + (path: "ab//cd///ef", expected: (windows ? #"ab\cd\ef"# : "ab/cd/ef"), label: "repeated path seperators"), + + (path: "ab/cd/ef/", expected: (windows ? #"ab\cd\ef"# : "ab/cd/ef"), label: "Trailing path seperator"), + (path: "ab/cd/ef//", expected: (windows ? #"ab\cd\ef"# : "ab/cd/ef"), label: "Trailing path seperator"), + (path: "ab/cd/ef///", expected: (windows ? #"ab\cd\ef"# : "ab/cd/ef"), label: "Trailing path seperator"), + + (path: "ab/./cd/././ef", expected: "ab/cd/ef", label: "dot path component"), + (path: "ab/./cd/ef/.", expected: "ab/cd/ef", label: "dot path component"), + + (path: "../..", expected: "../..", label: "dot dot path component"), + (path: ".././..", expected: "../..", label: "dot dot path component"), + (path: "../abc/..", expected: "..", label: "dot dot path component"), + (path: "../abc/.././", expected: "..", label: "dot dot path component"), + (path: "abc/..", expected: ".", label: "dot dot path component"), + + (path: "../", expected: "..", label: "combinations and edge cases"), + (path: "./abc", expected: "abc", label: "combinations and edge cases"), + (path: "./abc/", expected: "abc", label: "combinations and edge cases"), + (path: "./abc/../bar", expected: "bar", label: "combinations and edge cases"), + (path: "foo/../bar", expected: "bar", label: "combinations and edge cases"), + (path: "foo///..///bar///baz", expected: "bar/baz", label: "combinations and edge cases"), + (path: "foo/../bar/./", expected: "bar", label: "combinations and edge cases"), + (path: "../abc/def/", expected: "../abc/def", label: "combinations and edge cases"), + (path: "././././.", expected: ".", label: "combinations and edge cases"), + (path: "./././../.", expected: "..", label: "combinations and edge cases"), + (path: "./", expected: ".", label: "combinations and edge cases"), + (path: ".//", expected: ".", label: "combinations and edge cases"), + (path: "./.", expected: ".", label: "combinations and edge cases"), + (path: "././", expected: ".", label: "combinations and edge cases"), + (path: "../.", expected: "..", label: "combinations and edge cases"), + (path: "./..", expected: "..", label: "combinations and edge cases"), + (path: "./../.", expected: "..", label: "combinations and edge cases"), + (path: "./////../////./////", expected: "..", label: "combinations and edge cases"), + (path: "../a/..", expected: "..", label: "combinations and edge cases"), + (path: "a/..", expected: ".", label: "combinations and edge cases"), + (path: "a/../////../////./////", expected: "..", label: "combinations and edge cases"), + + ] + ) + func pathStringIsSetCorrectlyFailsOnWindows(path: String, expected: String, label: String) { + withKnownIssue("https://github.com/swiftlang/swift-package-manager/issues/8511: Path \(path) does not resolve properly") { + pathStringIsSetCorrectlyTestImplementation( + path: path, + expected: expected, + label: label + ) + } when: { + ProcessInfo.hostOperatingSystem == .windows + } + } - func testPathComponents() throws { - try XCTSkipOnWindows() - - XCTAssertEqual(AbsolutePath("/").components, ["/"]) - XCTAssertEqual(AbsolutePath("/.").components, ["/"]) - XCTAssertEqual(AbsolutePath("/..").components, ["/"]) - XCTAssertEqual(AbsolutePath("/bar").components, ["/", "bar"]) - XCTAssertEqual(AbsolutePath("/foo/bar/..").components, ["/", "foo"]) - XCTAssertEqual(AbsolutePath("/bar/../foo").components, ["/", "foo"]) - XCTAssertEqual(AbsolutePath("/bar/../foo/..//").components, ["/"]) - XCTAssertEqual(AbsolutePath("/bar/../foo/..//yabba/a/b/").components, ["/", "yabba", "a", "b"]) - - XCTAssertEqual(RelativePath("").components, ["."]) - XCTAssertEqual(RelativePath(".").components, ["."]) - XCTAssertEqual(RelativePath("..").components, [".."]) - XCTAssertEqual(RelativePath("bar").components, ["bar"]) - XCTAssertEqual(RelativePath("foo/bar/..").components, ["foo"]) - XCTAssertEqual(RelativePath("bar/../foo").components, ["foo"]) - XCTAssertEqual(RelativePath("bar/../foo/..//").components, ["."]) - XCTAssertEqual(RelativePath("bar/../foo/..//yabba/a/b/").components, ["yabba", "a", "b"]) - XCTAssertEqual(RelativePath("../..").components, ["..", ".."]) - XCTAssertEqual(RelativePath(".././/..").components, ["..", ".."]) - XCTAssertEqual(RelativePath("../a").components, ["..", "a"]) - XCTAssertEqual(RelativePath("../a/..").components, [".."]) - XCTAssertEqual(RelativePath("a/..").components, ["."]) - XCTAssertEqual(RelativePath("./..").components, [".."]) - XCTAssertEqual(RelativePath("a/../////../////./////").components, [".."]) - XCTAssertEqual(RelativePath("abc").components, ["abc"]) - } + struct relateivePathDirectoryNameAttributeReturnsExpectedValue { + private func testImplementation(path: String, expected: String) throws { + let actual = RelativePath(path).dirname + + #expect(actual == expected, "Actual is not as expected. Path is: \(path)") + } + + @Test( + arguments: [ + (path: "ab/c//d/", expected: (windows ? #"ab\c"# : "ab/c")), + (path: "../a", expected: ".."), + (path: "./..", expected: "."), + ] + ) + func relativePathDirectoryNameExtraction(path: String, expected: String) throws { + try testImplementation(path: path, expected: expected) + } + + @Test( + arguments: [ + (path: "../a/..", expected: "."), + (path: "a/..", expected: "."), + (path: "a/../////../////./////", expected: "."), + (path: "abc", expected: "."), + (path: "", expected: "."), + (path: ".", expected: "."), + ] + ) + func relativePathDirectoryNameExtractionFailsOnWindows(path: String, expected: String) throws { + try withKnownIssue("https://github.com/swiftlang/swift-package-manager/issues/8511: Path \(path) is not handled properly") { + try testImplementation(path: path, expected: expected) + } when: { + ProcessInfo.hostOperatingSystem == .windows + } + } - func testRelativePathFromAbsolutePaths() throws { - try XCTSkipOnWindows() + } - XCTAssertEqual(AbsolutePath("/").relative(to: AbsolutePath("/")), RelativePath(".")); - XCTAssertEqual(AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/")), RelativePath("a/b/c/d")); - XCTAssertEqual(AbsolutePath("/").relative(to: AbsolutePath("/a/b/c")), RelativePath("../../..")); - XCTAssertEqual(AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/a/b")), RelativePath("c/d")); - XCTAssertEqual(AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/a/b/c")), RelativePath("d")); - XCTAssertEqual(AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/a/c/d")), RelativePath("../../b/c/d")); - XCTAssertEqual(AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/b/c/d")), RelativePath("../../../a/b/c/d")); - } + struct relativePathBaseNameAttributeReturnsExpectedValue { + private func testImplementation(path: String, expected: String) throws { + let actual = RelativePath(path).basename + + #expect(actual == expected, "Actual is not as expected. Path is: \(path)") + } + @Test( + arguments: [ + (path: "../..", expected: ".."), + (path: "../a", expected: "a"), + (path: "../a/..", expected: ".."), + (path: "./..", expected: ".."), + (path: "abc", expected: "abc"), + (path: "", expected: "."), + (path: ".", expected: "."), + ] + ) + func relativePathBaseNameExtraction(path: String, expected: String) throws { + try testImplementation(path: path, expected: expected) + } + + @Test( + arguments: [ + (path: "a/..", expected: "."), + (path: "a/../////../////./////", expected: ".."), + ] + ) + func relativePathBaseNameExtractionFailsOnWindows(path: String, expected: String) throws { + try withKnownIssue("https://github.com/swiftlang/swift-package-manager/issues/8511: Path \(path) is not handled properly") { + try testImplementation(path: path, expected: expected) + } when: { + ProcessInfo.hostOperatingSystem == .windows + } + } - func testComparison() { - XCTAssertTrue(AbsolutePath("/") <= AbsolutePath("/")); - XCTAssertTrue(AbsolutePath("/abc") < AbsolutePath("/def")); - XCTAssertTrue(AbsolutePath("/2") <= AbsolutePath("/2.1")); - XCTAssertTrue(AbsolutePath("/3.1") > AbsolutePath("/2")); - XCTAssertTrue(AbsolutePath("/2") >= AbsolutePath("/2")); - XCTAssertTrue(AbsolutePath("/2.1") >= AbsolutePath("/2")); - } + } - func testAncestry() { - XCTAssertTrue(AbsolutePath("/a/b/c/d/e/f").isDescendantOfOrEqual(to: AbsolutePath("/a/b/c/d"))) - XCTAssertTrue(AbsolutePath("/a/b/c/d/e/f.swift").isDescendantOfOrEqual(to: AbsolutePath("/a/b/c"))) - XCTAssertTrue(AbsolutePath("/").isDescendantOfOrEqual(to: AbsolutePath("/"))) - XCTAssertTrue(AbsolutePath("/foo/bar").isDescendantOfOrEqual(to: AbsolutePath("/"))) - XCTAssertFalse(AbsolutePath("/foo/bar").isDescendantOfOrEqual(to: AbsolutePath("/foo/bar/baz"))) - XCTAssertFalse(AbsolutePath("/foo/bar").isDescendantOfOrEqual(to: AbsolutePath("/bar"))) - - XCTAssertFalse(AbsolutePath("/foo/bar").isDescendant(of: AbsolutePath("/foo/bar"))) - XCTAssertTrue(AbsolutePath("/foo/bar").isDescendant(of: AbsolutePath("/foo"))) - - XCTAssertTrue(AbsolutePath("/a/b/c/d").isAncestorOfOrEqual(to: AbsolutePath("/a/b/c/d/e/f"))) - XCTAssertTrue(AbsolutePath("/a/b/c").isAncestorOfOrEqual(to: AbsolutePath("/a/b/c/d/e/f.swift"))) - XCTAssertTrue(AbsolutePath("/").isAncestorOfOrEqual(to: AbsolutePath("/"))) - XCTAssertTrue(AbsolutePath("/").isAncestorOfOrEqual(to: AbsolutePath("/foo/bar"))) - XCTAssertFalse(AbsolutePath("/foo/bar/baz").isAncestorOfOrEqual(to: AbsolutePath("/foo/bar"))) - XCTAssertFalse(AbsolutePath("/bar").isAncestorOfOrEqual(to: AbsolutePath("/foo/bar"))) - - XCTAssertFalse(AbsolutePath("/foo/bar").isAncestor(of: AbsolutePath("/foo/bar"))) - XCTAssertTrue(AbsolutePath("/foo").isAncestor(of: AbsolutePath("/foo/bar"))) - } + struct RelativePathBasenameWithoutExtAttributeReturnsExpectedValue { + private func testImplementation(path: String, expected: String) throws { + let actual: String = RelativePath(path).basenameWithoutExt + + #expect(actual == expected, "Actual is not as expected. Path is: \(path)") + } + + @Test( + arguments: [ + (path: "../a", expected: "a"), + (path: "a/..", expected: "."), + (path: "abc", expected: "abc"), + (path: "../a.bc", expected: "a"), + (path: "abc.swift", expected: "abc"), + (path: "../a.b.c", expected: "a.b"), + (path: "abc.xyz.123", expected: "abc.xyz"), + ] + ) + func relativePathBaseNameWithoutExt(path: String, expected: String) throws { + try testImplementation(path: path, expected: expected) + } + + @Test( + arguments: [ + (path: "../..", expected: ".."), + (path: "../a/..", expected: ".."), + (path: "./..", expected: ".."), + (path: "a/../////../////./////", expected: ".."), + (path: "", expected: "."), + (path: ".", expected: "."), + ] + ) + func relativePathBaseNameWithoutExtFailsOnWindows(path: String, expected: String) throws { + try withKnownIssue("https://github.com/swiftlang/swift-package-manager/issues/8511: Path \(path) is not properly") { + try testImplementation(path: path, expected: expected) + } when: { + ProcessInfo.hostOperatingSystem == .windows + } + } - func testAbsolutePathValidation() throws { - try XCTSkipOnWindows() + } - XCTAssertNoThrow(try AbsolutePath(validating: "/a/b/c/d")) + struct relativePathSuffixAndExtensionAttributeReturnsExpectedValue { + private func testImplementation(path: String, expectedSuffix: String?, expectedExtension: String?) throws { + let pathUnderTest = RelativePath(path) + + #expect(pathUnderTest.suffix == expectedSuffix, "Actual suffix is not as expected. Path is: \(path)") + #expect(pathUnderTest.extension == expectedExtension, "Actual extension is not as expected. Path is: \(path)") + } + @Test( + arguments: [ + (path: "a", expectedSuffix: nil, expectedExtension: nil), + (path: "a.foo", expectedSuffix: ".foo", expectedExtension: "foo"), + (path: ".a.foo", expectedSuffix: ".foo", expectedExtension: "foo"), + (path: "a.foo.bar", expectedSuffix: ".bar", expectedExtension: "bar"), + (path: ".a.foo.bar", expectedSuffix: ".bar", expectedExtension: "bar"), + (path: ".a.foo.bar.baz", expectedSuffix: ".baz", expectedExtension: "baz"), + ] + ) + func suffixExtraction(path: String, expectedSuffix: String?, expectedExtension: String?) throws { + try testImplementation(path: path, expectedSuffix: expectedSuffix, expectedExtension: expectedExtension) + } + + @Test( + arguments:[ + "a.", + ".a", + "", + ".", + "..", + ] + ) + func suffixExtractionFailsOnWindows(path: String) throws { + try withKnownIssue("https://github.com/swiftlang/swift-package-manager/issues/8511: Path \(path) is not handled properly") { + try testImplementation(path: path, expectedSuffix: nil, expectedExtension: nil) + } when: { + ProcessInfo.hostOperatingSystem == .windows + } + } - XCTAssertThrowsError(try AbsolutePath(validating: "~/a/b/d")) { error in - XCTAssertEqual("\(error)", "invalid absolute path '~/a/b/d'; absolute path must begin with '/'") } - XCTAssertThrowsError(try AbsolutePath(validating: "a/b/d")) { error in - XCTAssertEqual("\(error)", "invalid absolute path 'a/b/d'") + struct componentsAttributeReturnsExpectedValue { + private func testImplementation(path: String, expected: [String]) throws { + let actual = RelativePath(path).components + + #expect(actual == expected, "Actual is not as expected: \(path)") + } + + @Test( + arguments: [ + (path: "", expected: ["."]), + (path: ".", expected: ["."]), + (path: "..", expected: [".."]), + (path: "bar", expected: ["bar"]), + (path: "../..", expected: ["..", ".."]), + (path: "../a", expected: ["..", "a"]), + (path: "abc", expected: ["abc"]), + ] as [(String, [String])] + ) + func relativePathComponentsAttributeAllPlatform(path: String, expected: [String]) throws { + try testImplementation(path: path, expected: expected) + } + + @Test( + arguments: [ + (path: "foo/bar/..", expected: ["foo"]), + (path: "bar/../foo", expected: ["foo"]), + (path: "bar/../foo/..//", expected: ["."]), + (path: "bar/../foo/..//yabba/a/b/", expected: ["yabba", "a", "b"]), + (path: ".././/..", expected: ["..", ".."]), + (path: "../a/..", expected: [".."]), + (path: "a/..", expected: ["."]), + (path: "./..", expected: [".."]), + (path: "a/../////../////./////", expected: [".."]), + ] as [(String, [String])] + ) + func relativePathComponentsAttributeFailsOnWindows(path: String, expected: [String]) throws { + try withKnownIssue("https://github.com/swiftlang/swift-package-manager/issues/8511: Path \(path) is not properly") { + try testImplementation(path: path, expected: expected) + } when: { + ProcessInfo.hostOperatingSystem == .windows + } + } + } + + @Test + func relativePathValidation() throws { + #expect(throws: Never.self) { + try RelativePath(validating: "a/b/c/d") + } + + withKnownIssue { + #expect {try RelativePath(validating: "/a/b/d")} throws: { error in + ("\(error)" == "invalid relative path '/a/b/d'; relative path should not begin with '/'") + } + } when: { + ProcessInfo.hostOperatingSystem == .windows + } } - } - func testRelativePathValidation() throws { - try XCTSkipOnWindows() + } - XCTAssertNoThrow(try RelativePath(validating: "a/b/c/d")) + @Test + @available(*, deprecated) + func concatenation() throws { + #expect(AbsolutePath(AbsolutePath("/"), RelativePath("")).pathString == (windows ? #"\"# : "/")) + #expect(AbsolutePath(AbsolutePath("/"), RelativePath(".")).pathString == (windows ? #"\"# : "/")) + #expect(AbsolutePath(AbsolutePath("/"), RelativePath("..")).pathString == (windows ? #"\"# : "/")) + #expect(AbsolutePath(AbsolutePath("/"), RelativePath("bar")).pathString == (windows ? #"\bar"# : "/bar")) + #expect(AbsolutePath(AbsolutePath("/foo/bar"), RelativePath("..")).pathString == (windows ? #"\foo"# : "/foo")) + #expect(AbsolutePath(AbsolutePath("/bar"), RelativePath("../foo")).pathString == (windows ? #"\foo"# : "/foo")) + withKnownIssue { + #expect(AbsolutePath(AbsolutePath("/bar"), RelativePath("../foo/..//")).pathString == (windows ? #"\"# : "/")) + #expect(AbsolutePath(AbsolutePath("/bar/../foo/..//yabba/"), RelativePath("a/b")).pathString == (windows ? #"\yabba\a\b"# : "/yabba/a/b")) + } when: { + ProcessInfo.hostOperatingSystem == .windows + } - XCTAssertThrowsError(try RelativePath(validating: "/a/b/d")) { error in - XCTAssertEqual("\(error)", "invalid relative path '/a/b/d'; relative path should not begin with '/'") - //XCTAssertEqual("\(error)", "invalid relative path '/a/b/d'; relative path should not begin with '/' or '~'") + #expect(AbsolutePath("/").appending(RelativePath("")).pathString == (windows ? #"\"# : "/")) + #expect(AbsolutePath("/").appending(RelativePath(".")).pathString == (windows ? #"\"# : "/")) + #expect(AbsolutePath("/").appending(RelativePath("..")).pathString == (windows ? #"\"# : "/")) + #expect(AbsolutePath("/").appending(RelativePath("bar")).pathString == (windows ? #"\bar"# : "/bar")) + #expect(AbsolutePath("/foo/bar").appending(RelativePath("..")).pathString == (windows ? #"\foo"# : "/foo")) + #expect(AbsolutePath("/bar").appending(RelativePath("../foo")).pathString == (windows ? #"\foo"# : "/foo")) + withKnownIssue { + #expect(AbsolutePath("/bar").appending(RelativePath("../foo/..//")).pathString == (windows ? #"\"# : "/")) + #expect(AbsolutePath("/bar/../foo/..//yabba/").appending(RelativePath("a/b")).pathString == (windows ? #"\yabba\a\b"# : "/yabba/a/b")) + } when: { + ProcessInfo.hostOperatingSystem == .windows } - /*XCTAssertThrowsError(try RelativePath(validating: "~/a/b/d")) { error in - XCTAssertEqual("\(error)", "invalid relative path '~/a/b/d'; relative path should not begin with '/' or '~'") - }*/ + #expect(AbsolutePath("/").appending(component: "a").pathString == (windows ? #"\a"# : "/a")) + #expect(AbsolutePath("/a").appending(component: "b").pathString == (windows ? #"\a\b"# : "/a/b")) + #expect(AbsolutePath("/").appending(components: "a", "b").pathString == (windows ? #"\a\b"# : "/a/b")) + #expect(AbsolutePath("/a").appending(components: "b", "c").pathString == (windows ? #"\a\b\c"# : "/a/b/c")) + + #expect(AbsolutePath("/a/b/c").appending(components: "", "c").pathString == (windows ? #"\a\b\c\c"# : "/a/b/c/c")) + #expect(AbsolutePath("/a/b/c").appending(components: "").pathString == (windows ? #"\a\b\c"# : "/a/b/c")) + #expect(AbsolutePath("/a/b/c").appending(components: ".").pathString == (windows ? #"\a\b\c"# : "/a/b/c")) + #expect(AbsolutePath("/a/b/c").appending(components: "..").pathString == (windows ? #"\a\b"# : "/a/b")) + #expect(AbsolutePath("/a/b/c").appending(components: "..", "d").pathString == (windows ? #"\a\b\d"# : "/a/b/d")) + #expect(AbsolutePath("/").appending(components: "..").pathString == (windows ? #"\"# : "/")) + #expect(AbsolutePath("/").appending(components: ".").pathString == (windows ? #"\"# : "/")) + #expect(AbsolutePath("/").appending(components: "..", "a").pathString == (windows ? #"\a"# : "/a")) + + #expect(RelativePath("hello").appending(components: "a", "b", "c", "..").pathString == (windows ? #"hello\a\b"# : "hello/a/b")) + #expect(RelativePath("hello").appending(RelativePath("a/b/../c/d")).pathString == (windows ? #"hello\a\c\d"# : "hello/a/c/d")) } - func testCodable() throws { - try XCTSkipOnWindows() + @Test + func relativePathFromAbsolutePaths() throws { + #expect(AbsolutePath("/").relative(to: AbsolutePath("/")) == RelativePath(".")); + #expect(AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/")) == RelativePath("a/b/c/d")); + #expect(AbsolutePath("/").relative(to: AbsolutePath("/a/b/c")) == RelativePath("../../..")); + #expect(AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/a/b")) == RelativePath("c/d")); + #expect(AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/a/b/c")) == RelativePath("d")); + #expect(AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/a/c/d")) == RelativePath("../../b/c/d")); + #expect(AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/b/c/d")) == RelativePath("../../../a/b/c/d")); + } + @Test + func codable() throws { struct Foo: Codable, Equatable { var path: AbsolutePath } @@ -392,48 +715,64 @@ class PathTests: XCTestCase { let foo = Foo(path: "/path/to/foo") let data = try JSONEncoder().encode(foo) let decodedFoo = try JSONDecoder().decode(Foo.self, from: data) - XCTAssertEqual(foo, decodedFoo) + #expect(foo == decodedFoo) } do { let foo = Foo(path: "/path/to/../to/foo") let data = try JSONEncoder().encode(foo) let decodedFoo = try JSONDecoder().decode(Foo.self, from: data) - XCTAssertEqual(foo, decodedFoo) - XCTAssertEqual(foo.path.pathString, windows ? #"\path\to\foo"# : "/path/to/foo") - XCTAssertEqual(decodedFoo.path.pathString, windows ? #"\path\to\foo"# : "/path/to/foo") + #expect(foo == decodedFoo) + withKnownIssue { + #expect(foo.path.pathString == (windows ? #"\path\to\foo"# : "/path/to/foo")) + #expect(decodedFoo.path.pathString == (windows ? #"\path\to\foo"# : "/path/to/foo")) + } when: { + ProcessInfo.hostOperatingSystem == .windows + } } do { let bar = Bar(path: "path/to/bar") let data = try JSONEncoder().encode(bar) let decodedBar = try JSONDecoder().decode(Bar.self, from: data) - XCTAssertEqual(bar, decodedBar) + #expect(bar == decodedBar) } do { let bar = Bar(path: "path/to/../to/bar") let data = try JSONEncoder().encode(bar) let decodedBar = try JSONDecoder().decode(Bar.self, from: data) - XCTAssertEqual(bar, decodedBar) - XCTAssertEqual(bar.path.pathString, "path/to/bar") - XCTAssertEqual(decodedBar.path.pathString, "path/to/bar") + #expect(bar == decodedBar) + withKnownIssue { + #expect(bar.path.pathString == "path/to/bar") + #expect(decodedBar.path.pathString == "path/to/bar") + } when: { + ProcessInfo.hostOperatingSystem == .windows + } } do { let data = try JSONEncoder().encode(Baz(path: "")) - XCTAssertThrowsError(try JSONDecoder().decode(Foo.self, from: data)) - XCTAssertNoThrow(try JSONDecoder().decode(Bar.self, from: data)) // empty string is a valid relative path + #expect(throws: (any Error).self) { + try JSONDecoder().decode(Foo.self, from: data) + } + #expect(throws: Never.self) { + try JSONDecoder().decode(Bar.self, from: data) + } // empty string is a valid relative path } do { let data = try JSONEncoder().encode(Baz(path: "foo")) - XCTAssertThrowsError(try JSONDecoder().decode(Foo.self, from: data)) + #expect(throws: (any Error).self) { + try JSONDecoder().decode(Foo.self, from: data) + } } do { let data = try JSONEncoder().encode(Baz(path: "/foo")) - XCTAssertThrowsError(try JSONDecoder().decode(Bar.self, from: data)) + #expect(throws: (any Error).self) { + try JSONDecoder().decode(Bar.self, from: data) + } } } diff --git a/Tests/BasicsTests/FileSystem/TemporaryFileTests.swift b/Tests/BasicsTests/FileSystem/TemporaryFileTests.swift index 5460faf901d..cb5b80206f8 100644 --- a/Tests/BasicsTests/FileSystem/TemporaryFileTests.swift +++ b/Tests/BasicsTests/FileSystem/TemporaryFileTests.swift @@ -9,83 +9,84 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import Foundation -import XCTest +import Testing import Basics -class TemporaryAsyncFileTests: XCTestCase { - func testBasicTemporaryDirectory() async throws { - // Test can create and remove temp directory. +struct TemporaryAsyncFileTests { + @Test + func basicTemporaryDirectory() async throws { let path1: AbsolutePath = try await withTemporaryDirectory(removeTreeOnDeinit: true) { tempDirPath in // Do some async task try await Task.sleep(nanoseconds: 1_000) - - XCTAssertTrue(localFileSystem.isDirectory(tempDirPath)) + + #expect(localFileSystem.isDirectory(tempDirPath)) return tempDirPath }.value - XCTAssertFalse(localFileSystem.isDirectory(path1)) - + #expect(!localFileSystem.isDirectory(path1)) + // Test temp directory is not removed when its not empty. let path2: AbsolutePath = try await withTemporaryDirectory { tempDirPath in - XCTAssertTrue(localFileSystem.isDirectory(tempDirPath)) + #expect(localFileSystem.isDirectory(tempDirPath)) // Create a file inside the temp directory. let filePath = tempDirPath.appending("somefile") // Do some async task try await Task.sleep(nanoseconds: 1_000) - + try localFileSystem.writeFileContents(filePath, bytes: []) return tempDirPath }.value - XCTAssertTrue(localFileSystem.isDirectory(path2)) + #expect(localFileSystem.isDirectory(path2)) // Cleanup. try localFileSystem.removeFileTree(path2) - XCTAssertFalse(localFileSystem.isDirectory(path2)) - + #expect(!localFileSystem.isDirectory(path2)) + // Test temp directory is removed when its not empty and removeTreeOnDeinit is enabled. let path3: AbsolutePath = try await withTemporaryDirectory(removeTreeOnDeinit: true) { tempDirPath in - XCTAssertTrue(localFileSystem.isDirectory(tempDirPath)) + #expect(localFileSystem.isDirectory(tempDirPath)) let filePath = tempDirPath.appending("somefile") // Do some async task try await Task.sleep(nanoseconds: 1_000) - + try localFileSystem.writeFileContents(filePath, bytes: []) return tempDirPath }.value - XCTAssertFalse(localFileSystem.isDirectory(path3)) + #expect(!localFileSystem.isDirectory(path3)) } - - func testCanCreateUniqueTempDirectories() async throws { + + @Test + func canCreateUniqueTempDirectories() async throws { let (pathOne, pathTwo): (AbsolutePath, AbsolutePath) = try await withTemporaryDirectory(removeTreeOnDeinit: true) { pathOne in let pathTwo: AbsolutePath = try await withTemporaryDirectory(removeTreeOnDeinit: true) { pathTwo in // Do some async task try await Task.sleep(nanoseconds: 1_000) - - XCTAssertTrue(localFileSystem.isDirectory(pathOne)) - XCTAssertTrue(localFileSystem.isDirectory(pathTwo)) + + #expect(localFileSystem.isDirectory(pathOne)) + #expect(localFileSystem.isDirectory(pathTwo)) // Their paths should be different. - XCTAssertTrue(pathOne != pathTwo) + #expect(pathOne != pathTwo) return pathTwo }.value return (pathOne, pathTwo) }.value - XCTAssertFalse(localFileSystem.isDirectory(pathOne)) - XCTAssertFalse(localFileSystem.isDirectory(pathTwo)) + #expect(!localFileSystem.isDirectory(pathOne)) + #expect(!localFileSystem.isDirectory(pathTwo)) } - - func testCancelOfTask() async throws { + + @Test + func cancelOfTask() async throws { let task: Task = try withTemporaryDirectory { path in - + try await Task.sleep(nanoseconds: 1_000_000_000) - XCTAssertTrue(Task.isCancelled) - XCTAssertFalse(localFileSystem.isDirectory(path)) + #expect(Task.isCancelled) + #expect(!localFileSystem.isDirectory(path)) return path } task.cancel() - do { - // The correct path is to throw an error here - let _ = try await task.value - XCTFail("The correct path here is to throw an error") - } catch {} + await #expect(throws: (any Error).self, "Error did not error when accessing `task.value`") { + try await task.value + } } } diff --git a/Tests/BasicsTests/FileSystem/VFSTests.swift b/Tests/BasicsTests/FileSystem/VFSTests.swift index 867c7078ae2..fc251b3b650 100644 --- a/Tests/BasicsTests/FileSystem/VFSTests.swift +++ b/Tests/BasicsTests/FileSystem/VFSTests.swift @@ -9,10 +9,11 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import Foundation import Basics import func TSCBasic.withTemporaryFile -import XCTest +import Testing import struct TSCBasic.ByteString @@ -36,107 +37,112 @@ func testWithTemporaryDirectory( }.value } -class VFSTests: XCTestCase { - func testLocalBasics() throws { - try XCTSkipOnWindows() - - // tiny PE binary from: https://archive.is/w01DO - let contents: [UInt8] = [ - 0x4d, 0x5a, 0x00, 0x00, 0x50, 0x45, 0x00, 0x00, 0x4c, 0x01, 0x01, 0x00, - 0x6a, 0x2a, 0x58, 0xc3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x03, 0x01, 0x0b, 0x01, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x68, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02 - ] - - let fs = localFileSystem - try withTemporaryFile { [contents] vfsPath in - try withTemporaryDirectory(removeTreeOnDeinit: true) { [contents] tempDirPath in - let file = tempDirPath.appending("best") - try fs.writeFileContents(file, string: "best") - - let sym = tempDirPath.appending("hello") - try fs.createSymbolicLink(sym, pointingAt: file, relative: false) - - let executable = tempDirPath.appending("exec-foo") - try fs.writeFileContents(executable, bytes: ByteString(contents)) -#if !os(Windows) - try fs.chmod(.executable, path: executable, options: []) -#endif - - let executableSym = tempDirPath.appending("exec-sym") - try fs.createSymbolicLink(executableSym, pointingAt: executable, relative: false) - - try fs.createDirectory(tempDirPath.appending("dir")) - try fs.writeFileContents(tempDirPath.appending(components: ["dir", "file"]), bytes: []) - - try VirtualFileSystem.serializeDirectoryTree(tempDirPath, into: AbsolutePath(vfsPath.path), fs: fs, includeContents: [executable]) - } - let vfs = try VirtualFileSystem(path: vfsPath.path, fs: fs) - - // exists() - XCTAssertTrue(vfs.exists(AbsolutePath("/"))) - XCTAssertFalse(vfs.exists(AbsolutePath("/does-not-exist"))) - - // isFile() - let filePath = AbsolutePath("/best") - XCTAssertTrue(vfs.exists(filePath)) - XCTAssertTrue(vfs.isFile(filePath)) - XCTAssertEqual(try vfs.getFileInfo(filePath).fileType, .typeRegular) - XCTAssertFalse(vfs.isDirectory(filePath)) - XCTAssertFalse(vfs.isFile(AbsolutePath("/does-not-exist"))) - XCTAssertFalse(vfs.isSymlink(AbsolutePath("/does-not-exist"))) - XCTAssertThrowsError(try vfs.getFileInfo(AbsolutePath("/does-not-exist"))) - - // isSymlink() - let symPath = AbsolutePath("/hello") - XCTAssertTrue(vfs.isSymlink(symPath)) - XCTAssertTrue(vfs.isFile(symPath)) - XCTAssertEqual(try vfs.getFileInfo(symPath).fileType, .typeSymbolicLink) - XCTAssertFalse(vfs.isDirectory(symPath)) - - // isExecutableFile - let executablePath = AbsolutePath("/exec-foo") - let executableSymPath = AbsolutePath("/exec-sym") - XCTAssertTrue(vfs.isExecutableFile(executablePath)) - XCTAssertTrue(vfs.isExecutableFile(executableSymPath)) - XCTAssertTrue(vfs.isSymlink(executableSymPath)) - XCTAssertFalse(vfs.isExecutableFile(symPath)) - XCTAssertFalse(vfs.isExecutableFile(filePath)) - XCTAssertFalse(vfs.isExecutableFile(AbsolutePath("/does-not-exist"))) - XCTAssertFalse(vfs.isExecutableFile(AbsolutePath("/"))) - - // readFileContents - let execFileContents = try vfs.readFileContents(executablePath) - XCTAssertEqual(execFileContents, ByteString(contents)) - - // isDirectory() - XCTAssertTrue(vfs.isDirectory(AbsolutePath("/"))) - XCTAssertFalse(vfs.isDirectory(AbsolutePath("/does-not-exist"))) - - // getDirectoryContents() - do { - _ = try vfs.getDirectoryContents(AbsolutePath("/does-not-exist")) - XCTFail("Unexpected success") - } catch { - XCTAssertEqual(error.localizedDescription, "no such file or directory: \(AbsolutePath("/does-not-exist"))") +struct VFSTests { + @Test + func localBasics() throws { + try withKnownIssue("Permission issues on Windows") { + // tiny PE binary from: https://archive.is/w01DO + let contents: [UInt8] = [ + 0x4d, 0x5a, 0x00, 0x00, 0x50, 0x45, 0x00, 0x00, 0x4c, 0x01, 0x01, 0x00, + 0x6a, 0x2a, 0x58, 0xc3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x03, 0x01, 0x0b, 0x01, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x68, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02 + ] + + let fs = localFileSystem + try withTemporaryFile { [contents] vfsPath in + try withTemporaryDirectory(removeTreeOnDeinit: true) { [contents] tempDirPath in + let file = tempDirPath.appending("best") + try fs.writeFileContents(file, string: "best") + + let sym = tempDirPath.appending("hello") + try fs.createSymbolicLink(sym, pointingAt: file, relative: false) + + let executable = tempDirPath.appending("exec-foo") + try fs.writeFileContents(executable, bytes: ByteString(contents)) + #if !os(Windows) + try fs.chmod(.executable, path: executable, options: []) + #endif + + let executableSym = tempDirPath.appending("exec-sym") + try fs.createSymbolicLink(executableSym, pointingAt: executable, relative: false) + + try fs.createDirectory(tempDirPath.appending("dir")) + try fs.writeFileContents(tempDirPath.appending(components: ["dir", "file"]), bytes: []) + + try VirtualFileSystem.serializeDirectoryTree(tempDirPath, into: AbsolutePath(vfsPath.path), fs: fs, includeContents: [executable]) + } + + let vfs = try VirtualFileSystem(path: vfsPath.path, fs: fs) + + // exists() + #expect(vfs.exists(AbsolutePath("/"))) + #expect(!vfs.exists(AbsolutePath("/does-not-exist"))) + + // isFile() + let filePath = AbsolutePath("/best") + #expect(vfs.exists(filePath)) + #expect(vfs.isFile(filePath)) + #expect(try vfs.getFileInfo(filePath).fileType == .typeRegular) + #expect(!vfs.isDirectory(filePath)) + #expect(!vfs.isFile(AbsolutePath("/does-not-exist"))) + #expect(!vfs.isSymlink(AbsolutePath("/does-not-exist"))) + #expect(throws: (any Error).self) { + try vfs.getFileInfo(AbsolutePath("/does-not-exist")) + } + + // isSymlink() + let symPath = AbsolutePath("/hello") + #expect(vfs.isSymlink(symPath)) + #expect(vfs.isFile(symPath)) + #expect(try vfs.getFileInfo(symPath).fileType == .typeSymbolicLink) + #expect(!vfs.isDirectory(symPath)) + + // isExecutableFile + let executablePath = AbsolutePath("/exec-foo") + let executableSymPath = AbsolutePath("/exec-sym") + #expect(vfs.isExecutableFile(executablePath)) + #expect(vfs.isExecutableFile(executableSymPath)) + #expect(vfs.isSymlink(executableSymPath)) + #expect(!vfs.isExecutableFile(symPath)) + #expect(!vfs.isExecutableFile(filePath)) + #expect(!vfs.isExecutableFile(AbsolutePath("/does-not-exist"))) + #expect(!vfs.isExecutableFile(AbsolutePath("/"))) + + // readFileContents + let execFileContents = try vfs.readFileContents(executablePath) + #expect(execFileContents == ByteString(contents)) + + // isDirectory() + #expect(vfs.isDirectory(AbsolutePath("/"))) + #expect(!vfs.isDirectory(AbsolutePath("/does-not-exist"))) + + // getDirectoryContents() + let dirContents = try vfs.getDirectoryContents(AbsolutePath("/")) + #expect(dirContents.sorted() == ["best", "dir", "exec-foo", "exec-sym", "hello"]) + #expect {try vfs.getDirectoryContents(AbsolutePath("/does-not-exist"))} throws: { error in + (error.localizedDescription == "no such file or directory: \(AbsolutePath("/does-not-exist"))") + } + + let thisDirectoryContents = try vfs.getDirectoryContents(AbsolutePath("/")) + #expect(!thisDirectoryContents.contains(where: { $0 == "." })) + #expect(!thisDirectoryContents.contains(where: { $0 == ".." })) + #expect(thisDirectoryContents.sorted() == ["best", "dir", "exec-foo", "exec-sym", "hello"]) + + let contents = try vfs.getDirectoryContents(AbsolutePath("/dir")) + #expect(contents == ["file"]) + + let fileContents = try vfs.readFileContents(AbsolutePath("/dir/file")) + #expect(fileContents == "") } - - let thisDirectoryContents = try vfs.getDirectoryContents(AbsolutePath("/")) - XCTAssertFalse(thisDirectoryContents.contains(where: { $0 == "." })) - XCTAssertFalse(thisDirectoryContents.contains(where: { $0 == ".." })) - XCTAssertEqual(thisDirectoryContents.sorted(), ["best", "dir", "exec-foo", "exec-sym", "hello"]) - - let contents = try vfs.getDirectoryContents(AbsolutePath("/dir")) - XCTAssertEqual(contents, ["file"]) - - let fileContents = try vfs.readFileContents(AbsolutePath("/dir/file")) - XCTAssertEqual(fileContents, "") + } when: { + ProcessInfo.hostOperatingSystem == .windows } } }