From 4c5e45edc56d26ec42b9d984a58eed38f727939f Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Fri, 7 Nov 2025 14:16:38 -0500 Subject: [PATCH 1/2] When saving a `String` as an attachment, assume `".txt"` as a path extension. When a string is attached to a test, we encode it as UTF-8 (as is tradition). This PR adjusts `String`'s conformance to `Attachable` so that it uses the `".txt"` extension (unless the user specifies something else). This improves usability in Xcode test reports by making these attachments double-clickable and QuickLookable (is that a word?) --- Sources/Testing/Attachments/Attachable.swift | 7 +++++++ Tests/TestingTests/AttachmentTests.swift | 8 ++++++++ .../Traits/AttachmentSavingTraitTests.swift | 10 +++++----- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/Sources/Testing/Attachments/Attachable.swift b/Sources/Testing/Attachments/Attachable.swift index 8e2c06420..03ce10efd 100644 --- a/Sources/Testing/Attachments/Attachable.swift +++ b/Sources/Testing/Attachments/Attachable.swift @@ -131,6 +131,13 @@ extension Attachable where Self: StringProtocol { // SEE: https://github.com/swiftlang/swift/blob/main/stdlib/public/core/StringUTF8View.swift utf8.count } + + public borrowing func preferredName(for attachment: borrowing Attachment, basedOn suggestedName: String) -> String { + if suggestedName.contains(".") { + return suggestedName + } + return "\(suggestedName).txt" + } } // MARK: - Default conformances diff --git a/Tests/TestingTests/AttachmentTests.swift b/Tests/TestingTests/AttachmentTests.swift index 623ead084..854ecd133 100644 --- a/Tests/TestingTests/AttachmentTests.swift +++ b/Tests/TestingTests/AttachmentTests.swift @@ -62,6 +62,14 @@ struct AttachmentTests { } #endif + @Test func preferredNameOfStringAttachment() { + let attachment1 = Attachment("", named: "abc123") + #expect(attachment1.preferredName == "abc123.txt") + + let attachment2 = Attachment("", named: "abc123.html") + #expect(attachment2.preferredName == "abc123.html") + } + #if !SWT_NO_FILE_IO func compare(_ attachableValue: borrowing MySendableAttachable, toContentsOfFileAtPath filePath: String) throws { let file = try FileHandle(forReadingAtPath: filePath) diff --git a/Tests/TestingTests/Traits/AttachmentSavingTraitTests.swift b/Tests/TestingTests/Traits/AttachmentSavingTraitTests.swift index 1ea63eeda..47edd8b25 100644 --- a/Tests/TestingTests/Traits/AttachmentSavingTraitTests.swift +++ b/Tests/TestingTests/Traits/AttachmentSavingTraitTests.swift @@ -130,27 +130,27 @@ extension `AttachmentSavingTrait tests` { @Suite(.hidden, currentAttachmentSavingTrait) struct FixtureSuite { @Test(.hidden) func `Records an attachment (passing)`() { - Attachment.record("", named: "PASSING TEST") + Attachment.record([], named: "PASSING TEST") } @Test(.hidden) func `Records an attachment (warning)`() { - Attachment.record("", named: "PASSING TEST") + Attachment.record([], named: "PASSING TEST") Issue.record("", severity: .warning) } @Test(.hidden) func `Records an attachment (failing)`() { - Attachment.record("", named: "FAILING TEST") + Attachment.record([], named: "FAILING TEST") Issue.record("") } @Test(.hidden, arguments: 0 ..< 5) func `Records an attachment (passing, parameterized)`(i: Int) async { - Attachment.record("\(i)", named: "PASSING TEST") + Attachment.record([UInt8(i)], named: "PASSING TEST") } @Test(.hidden, arguments: 0 ..< 7) // intentionally different count func `Records an attachment (failing, parameterized)`(i: Int) async { - Attachment.record("\(i)", named: "FAILING TEST") + Attachment.record([UInt8(i)], named: "FAILING TEST") Issue.record("\(i)") } } From dc2c40ecfd6228d75a44a0a4bb0daa616591002a Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Fri, 7 Nov 2025 14:49:41 -0500 Subject: [PATCH 2/2] Add missing availabilities in that file while I'm in there --- Sources/Testing/Attachments/Attachable.swift | 72 ++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/Sources/Testing/Attachments/Attachable.swift b/Sources/Testing/Attachments/Attachable.swift index 03ce10efd..8c3476657 100644 --- a/Sources/Testing/Attachments/Attachable.swift +++ b/Sources/Testing/Attachments/Attachable.swift @@ -103,17 +103,37 @@ public protocol Attachable: ~Copyable { // MARK: - Default implementations +/// @Metadata { +/// @Available(Swift, introduced: 6.2) +/// @Available(Xcode, introduced: 26.0) +/// } extension Attachable where Self: ~Copyable { + /// @Metadata { + /// @Available(Swift, introduced: 6.2) + /// @Available(Xcode, introduced: 26.0) + /// } public var estimatedAttachmentByteCount: Int? { nil } + /// @Metadata { + /// @Available(Swift, introduced: 6.2) + /// @Available(Xcode, introduced: 26.0) + /// } public borrowing func preferredName(for attachment: borrowing Attachment, basedOn suggestedName: String) -> String { suggestedName } } +/// @Metadata { +/// @Available(Swift, introduced: 6.2) +/// @Available(Xcode, introduced: 26.0) +/// } extension Attachable where Self: Collection, Element == UInt8 { + /// @Metadata { + /// @Available(Swift, introduced: 6.2) + /// @Available(Xcode, introduced: 26.0) + /// } public var estimatedAttachmentByteCount: Int? { count } @@ -125,13 +145,25 @@ extension Attachable where Self: Collection, Element == UInt8 { // (potentially expensive!) copy of the collection. } +/// @Metadata { +/// @Available(Swift, introduced: 6.2) +/// @Available(Xcode, introduced: 26.0) +/// } extension Attachable where Self: StringProtocol { + /// @Metadata { + /// @Available(Swift, introduced: 6.2) + /// @Available(Xcode, introduced: 26.0) + /// } public var estimatedAttachmentByteCount: Int? { // NOTE: utf8.count may be O(n) for foreign strings. // SEE: https://github.com/swiftlang/swift/blob/main/stdlib/public/core/StringUTF8View.swift utf8.count } + /// @Metadata { + /// @Available(Swift, introduced: 6.2) + /// @Available(Xcode, introduced: 26.0) + /// } public borrowing func preferredName(for attachment: borrowing Attachment, basedOn suggestedName: String) -> String { if suggestedName.contains(".") { return suggestedName @@ -144,25 +176,57 @@ extension Attachable where Self: StringProtocol { // Implement the protocol requirements for byte arrays and buffers so that // developers can attach raw data when needed. +/// @Metadata { +/// @Available(Swift, introduced: 6.2) +/// @Available(Xcode, introduced: 26.0) +/// } extension Array: Attachable { + /// @Metadata { + /// @Available(Swift, introduced: 6.2) + /// @Available(Xcode, introduced: 26.0) + /// } public func withUnsafeBytes(for attachment: borrowing Attachment, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { try withUnsafeBytes(body) } } +/// @Metadata { +/// @Available(Swift, introduced: 6.2) +/// @Available(Xcode, introduced: 26.0) +/// } extension ContiguousArray: Attachable { + /// @Metadata { + /// @Available(Swift, introduced: 6.2) + /// @Available(Xcode, introduced: 26.0) + /// } public func withUnsafeBytes(for attachment: borrowing Attachment, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { try withUnsafeBytes(body) } } +/// @Metadata { +/// @Available(Swift, introduced: 6.2) +/// @Available(Xcode, introduced: 26.0) +/// } extension ArraySlice: Attachable { + /// @Metadata { + /// @Available(Swift, introduced: 6.2) + /// @Available(Xcode, introduced: 26.0) + /// } public func withUnsafeBytes(for attachment: borrowing Attachment, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { try withUnsafeBytes(body) } } +/// @Metadata { +/// @Available(Swift, introduced: 6.2) +/// @Available(Xcode, introduced: 26.0) +/// } extension String: Attachable { + /// @Metadata { + /// @Available(Swift, introduced: 6.2) + /// @Available(Xcode, introduced: 26.0) + /// } public func withUnsafeBytes(for attachment: borrowing Attachment, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { var selfCopy = self return try selfCopy.withUTF8 { utf8 in @@ -171,7 +235,15 @@ extension String: Attachable { } } +/// @Metadata { +/// @Available(Swift, introduced: 6.2) +/// @Available(Xcode, introduced: 26.0) +/// } extension Substring: Attachable { + /// @Metadata { + /// @Available(Swift, introduced: 6.2) + /// @Available(Xcode, introduced: 26.0) + /// } public func withUnsafeBytes(for attachment: borrowing Attachment, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { var selfCopy = self return try selfCopy.withUTF8 { utf8 in