diff --git a/Sources/Testing/ABI/EntryPoints/EntryPoint.swift b/Sources/Testing/ABI/EntryPoints/EntryPoint.swift index 102776238..d7e505cd5 100644 --- a/Sources/Testing/ABI/EntryPoints/EntryPoint.swift +++ b/Sources/Testing/ABI/EntryPoints/EntryPoint.swift @@ -8,6 +8,7 @@ // See https://swift.org/CONTRIBUTORS.txt for Swift project authors // +private import Foundation private import _TestingInternals #if canImport(Synchronization) @@ -622,9 +623,7 @@ public func configurationForEntryPoint(from args: __CommandLineArguments_v0) thr // Attachment output. if let attachmentsPath = args.attachmentsPath { - guard fileExists(atPath: attachmentsPath) else { - throw _EntryPointError.invalidArgument("---attachments-path", value: attachmentsPath) - } + try FileManager().createDirectory(atPath: attachmentsPath, withIntermediateDirectories: true) configuration.attachmentsPath = attachmentsPath } diff --git a/Sources/Testing/Testing.docc/Attachments.md b/Sources/Testing/Testing.docc/Attachments.md index 018576d95..9e28633e4 100644 --- a/Sources/Testing/Testing.docc/Attachments.md +++ b/Sources/Testing/Testing.docc/Attachments.md @@ -244,8 +244,8 @@ after your tests finish running: `.build/attachments` by default. Visual Studio Code reports the paths to individual attachments in its Tests Results panel. - When using Swift Package Manager's `swift test` command, you can pass the - `--attachments-path` option. The testing library saves attachments to the - specified directory. + `--attachments-path` option. The testing library creates a directory at the + specified path and saves attachments to it. If you do not pass the `--attachments-path` option, the testing library does not save any attachments you record. diff --git a/Tests/TestingTests/SwiftPMTests.swift b/Tests/TestingTests/SwiftPMTests.swift index 6c7e27ab3..9e4b63bbf 100644 --- a/Tests/TestingTests/SwiftPMTests.swift +++ b/Tests/TestingTests/SwiftPMTests.swift @@ -250,6 +250,78 @@ struct SwiftPMTests { #expect(fileContents.contains(UInt8(ascii: ">"))) } + @Test( + "--attachments-path argument (creates missing directory)", + arguments: ["--attachments-path", "--experimental-attachments-path"] + ) + func attachmentsPathCreatesMissingDirectory(argumentName: String) throws { + let tempDirPath = try temporaryDirectory() + let attachmentsPath = appendPathComponent("swt_attachments_\(UInt64.random(in: 0 ..< .max))", to: tempDirPath) + defer { + _ = remove(attachmentsPath) + } + #expect(!fileExists(atPath: attachmentsPath)) + let configuration = try configurationForEntryPoint(withArguments: ["PATH", argumentName, attachmentsPath]) + #expect(fileExists(atPath: attachmentsPath)) + let actualPath = try #require(configuration.attachmentsPath, "Attachments path is not expected to be nil") + #expect(canonicalizePath(actualPath) == canonicalizePath(attachmentsPath)) + } + + @Test("--attachments-path argument (creates missing intermediate directories)") + func attachmentsPathCreatesMissingIntermediateDirectories() throws { + let tempDirPath = try temporaryDirectory() + let root = appendPathComponent("swt_attachments_\(UInt64.random(in: 0 ..< .max))", to: tempDirPath) + let intermediate = appendPathComponent("foo", to: root) + let attachmentsPath = appendPathComponent("bar", to: intermediate) + defer { + _ = remove(attachmentsPath) + _ = remove(intermediate) + _ = remove(root) + } + #expect(!fileExists(atPath: root)) + let configuration = try configurationForEntryPoint(withArguments: ["PATH", "--attachments-path", attachmentsPath]) + #expect(fileExists(atPath: root)) + #expect(fileExists(atPath: intermediate)) + #expect(fileExists(atPath: attachmentsPath)) + let actualPath = try #require(configuration.attachmentsPath, "Attachments path is not expected to be nil") + #expect(canonicalizePath(actualPath) == canonicalizePath(attachmentsPath)) + } + + @Test("--attachments-path argument (accepts existing directory)") + func attachmentsPathAcceptsExistingDirectory() throws { + let tempDirPath = try temporaryDirectory() + #expect(fileExists(atPath: tempDirPath)) + let configuration = try configurationForEntryPoint(withArguments: ["PATH", "--attachments-path", tempDirPath]) + let actualPath = try #require(configuration.attachmentsPath, "Attachments path is not expected to be nil") + #expect( + canonicalizePath(actualPath) == canonicalizePath(tempDirPath), + "Canonicalized actual path (\(actualPath)) is not equal to canonicalized expected path (\(tempDirPath))", + ) + } + + @Test("--attachments-path argument (bad path)") + func attachmentsPathWithBadPath() throws { + let attachmentPathParent: String + let attachmentPath: String + #if !os(Windows) + attachmentPathParent = "/usr/bin" + attachmentPath = "\(attachmentPathParent)/cannot-save-attachments-here" + #else + attachmentPathParent = #"C:\Windows\System32"# + attachmentPath = "\(attachmentPathParent)\\cannot-save-attachments-here" + #endif + + #expect(throws: (any Error).self) { + _ = try configurationForEntryPoint(withArguments: ["PATH", "--attachments-path", attachmentPath]) + } + } + + @Test("--attachments-path argument (missing path)") + func attachmentsPathWithMissingPath() throws { + let args = try parseCommandLineArguments(from: ["PATH", "--attachments-path"]) + #expect(args.attachmentsPath == nil) + } + @Test("--configuration-path argument", arguments: [ "--configuration-path", "--experimental-configuration-path", ])