Skip to content

Commit 564e4f2

Browse files
authored
When saving a String as an attachment, assume ".txt" as a path extension. (#1404)
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?) ### Checklist: - [x] Code and documentation should follow the style of the [Style Guide](https://github.com/apple/swift-testing/blob/main/Documentation/StyleGuide.md). - [x] If public symbols are renamed or modified, DocC references should be updated.
1 parent b86ca03 commit 564e4f2

File tree

3 files changed

+92
-5
lines changed

3 files changed

+92
-5
lines changed

Sources/Testing/Attachments/Attachable.swift

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,17 +103,37 @@ public protocol Attachable: ~Copyable {
103103

104104
// MARK: - Default implementations
105105

106+
/// @Metadata {
107+
/// @Available(Swift, introduced: 6.2)
108+
/// @Available(Xcode, introduced: 26.0)
109+
/// }
106110
extension Attachable where Self: ~Copyable {
111+
/// @Metadata {
112+
/// @Available(Swift, introduced: 6.2)
113+
/// @Available(Xcode, introduced: 26.0)
114+
/// }
107115
public var estimatedAttachmentByteCount: Int? {
108116
nil
109117
}
110118

119+
/// @Metadata {
120+
/// @Available(Swift, introduced: 6.2)
121+
/// @Available(Xcode, introduced: 26.0)
122+
/// }
111123
public borrowing func preferredName(for attachment: borrowing Attachment<Self>, basedOn suggestedName: String) -> String {
112124
suggestedName
113125
}
114126
}
115127

128+
/// @Metadata {
129+
/// @Available(Swift, introduced: 6.2)
130+
/// @Available(Xcode, introduced: 26.0)
131+
/// }
116132
extension Attachable where Self: Collection, Element == UInt8 {
133+
/// @Metadata {
134+
/// @Available(Swift, introduced: 6.2)
135+
/// @Available(Xcode, introduced: 26.0)
136+
/// }
117137
public var estimatedAttachmentByteCount: Int? {
118138
count
119139
}
@@ -125,37 +145,88 @@ extension Attachable where Self: Collection, Element == UInt8 {
125145
// (potentially expensive!) copy of the collection.
126146
}
127147

148+
/// @Metadata {
149+
/// @Available(Swift, introduced: 6.2)
150+
/// @Available(Xcode, introduced: 26.0)
151+
/// }
128152
extension Attachable where Self: StringProtocol {
153+
/// @Metadata {
154+
/// @Available(Swift, introduced: 6.2)
155+
/// @Available(Xcode, introduced: 26.0)
156+
/// }
129157
public var estimatedAttachmentByteCount: Int? {
130158
// NOTE: utf8.count may be O(n) for foreign strings.
131159
// SEE: https://github.com/swiftlang/swift/blob/main/stdlib/public/core/StringUTF8View.swift
132160
utf8.count
133161
}
162+
163+
/// @Metadata {
164+
/// @Available(Swift, introduced: 6.2)
165+
/// @Available(Xcode, introduced: 26.0)
166+
/// }
167+
public borrowing func preferredName(for attachment: borrowing Attachment<Self>, basedOn suggestedName: String) -> String {
168+
if suggestedName.contains(".") {
169+
return suggestedName
170+
}
171+
return "\(suggestedName).txt"
172+
}
134173
}
135174

136175
// MARK: - Default conformances
137176

138177
// Implement the protocol requirements for byte arrays and buffers so that
139178
// developers can attach raw data when needed.
179+
/// @Metadata {
180+
/// @Available(Swift, introduced: 6.2)
181+
/// @Available(Xcode, introduced: 26.0)
182+
/// }
140183
extension Array<UInt8>: Attachable {
184+
/// @Metadata {
185+
/// @Available(Swift, introduced: 6.2)
186+
/// @Available(Xcode, introduced: 26.0)
187+
/// }
141188
public func withUnsafeBytes<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
142189
try withUnsafeBytes(body)
143190
}
144191
}
145192

193+
/// @Metadata {
194+
/// @Available(Swift, introduced: 6.2)
195+
/// @Available(Xcode, introduced: 26.0)
196+
/// }
146197
extension ContiguousArray<UInt8>: Attachable {
198+
/// @Metadata {
199+
/// @Available(Swift, introduced: 6.2)
200+
/// @Available(Xcode, introduced: 26.0)
201+
/// }
147202
public func withUnsafeBytes<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
148203
try withUnsafeBytes(body)
149204
}
150205
}
151206

207+
/// @Metadata {
208+
/// @Available(Swift, introduced: 6.2)
209+
/// @Available(Xcode, introduced: 26.0)
210+
/// }
152211
extension ArraySlice<UInt8>: Attachable {
212+
/// @Metadata {
213+
/// @Available(Swift, introduced: 6.2)
214+
/// @Available(Xcode, introduced: 26.0)
215+
/// }
153216
public func withUnsafeBytes<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
154217
try withUnsafeBytes(body)
155218
}
156219
}
157220

221+
/// @Metadata {
222+
/// @Available(Swift, introduced: 6.2)
223+
/// @Available(Xcode, introduced: 26.0)
224+
/// }
158225
extension String: Attachable {
226+
/// @Metadata {
227+
/// @Available(Swift, introduced: 6.2)
228+
/// @Available(Xcode, introduced: 26.0)
229+
/// }
159230
public func withUnsafeBytes<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
160231
var selfCopy = self
161232
return try selfCopy.withUTF8 { utf8 in
@@ -164,7 +235,15 @@ extension String: Attachable {
164235
}
165236
}
166237

238+
/// @Metadata {
239+
/// @Available(Swift, introduced: 6.2)
240+
/// @Available(Xcode, introduced: 26.0)
241+
/// }
167242
extension Substring: Attachable {
243+
/// @Metadata {
244+
/// @Available(Swift, introduced: 6.2)
245+
/// @Available(Xcode, introduced: 26.0)
246+
/// }
168247
public func withUnsafeBytes<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
169248
var selfCopy = self
170249
return try selfCopy.withUTF8 { utf8 in

Tests/TestingTests/AttachmentTests.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ struct AttachmentTests {
6262
}
6363
#endif
6464

65+
@Test func preferredNameOfStringAttachment() {
66+
let attachment1 = Attachment("", named: "abc123")
67+
#expect(attachment1.preferredName == "abc123.txt")
68+
69+
let attachment2 = Attachment("", named: "abc123.html")
70+
#expect(attachment2.preferredName == "abc123.html")
71+
}
72+
6573
#if !SWT_NO_FILE_IO
6674
func compare(_ attachableValue: borrowing MySendableAttachable, toContentsOfFileAtPath filePath: String) throws {
6775
let file = try FileHandle(forReadingAtPath: filePath)

Tests/TestingTests/Traits/AttachmentSavingTraitTests.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -130,27 +130,27 @@ extension `AttachmentSavingTrait tests` {
130130
@Suite(.hidden, currentAttachmentSavingTrait)
131131
struct FixtureSuite {
132132
@Test(.hidden) func `Records an attachment (passing)`() {
133-
Attachment.record("", named: "PASSING TEST")
133+
Attachment.record([], named: "PASSING TEST")
134134
}
135135

136136
@Test(.hidden) func `Records an attachment (warning)`() {
137-
Attachment.record("", named: "PASSING TEST")
137+
Attachment.record([], named: "PASSING TEST")
138138
Issue.record("", severity: .warning)
139139
}
140140

141141
@Test(.hidden) func `Records an attachment (failing)`() {
142-
Attachment.record("", named: "FAILING TEST")
142+
Attachment.record([], named: "FAILING TEST")
143143
Issue.record("")
144144
}
145145

146146
@Test(.hidden, arguments: 0 ..< 5)
147147
func `Records an attachment (passing, parameterized)`(i: Int) async {
148-
Attachment.record("\(i)", named: "PASSING TEST")
148+
Attachment.record([UInt8(i)], named: "PASSING TEST")
149149
}
150150

151151
@Test(.hidden, arguments: 0 ..< 7) // intentionally different count
152152
func `Records an attachment (failing, parameterized)`(i: Int) async {
153-
Attachment.record("\(i)", named: "FAILING TEST")
153+
Attachment.record([UInt8(i)], named: "FAILING TEST")
154154
Issue.record("\(i)")
155155
}
156156
}

0 commit comments

Comments
 (0)