Skip to content

Commit 45384e5

Browse files
authored
Promote attachments to API (#973)
This PR promotes attachments to API and makes the appropriate changes to match what was approved in the review of [ST-0009](https://github.com/swiftlang/swift-evolution/blob/main/proposals/testing/0009-attachments.md). ### 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 756316c commit 45384e5

24 files changed

+265
-85
lines changed

Documentation/ABI/JSON.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -188,19 +188,24 @@ sufficient information to display the event in a human-readable format.
188188
"kind": <event-kind>,
189189
"instant": <instant>, ; when the event occurred
190190
["issue": <issue>,] ; the recorded issue (if "kind" is "issueRecorded")
191+
["attachment": <attachment>,] ; the attachment (if kind is "valueAttached")
191192
"messages": <array:message>,
192193
["testID": <test-id>,]
193194
}
194195
195196
<event-kind> ::= "runStarted" | "testStarted" | "testCaseStarted" |
196197
"issueRecorded" | "testCaseEnded" | "testEnded" | "testSkipped" |
197-
"runEnded" ; additional event kinds may be added in the future
198+
"runEnded" | "valueAttached"; additional event kinds may be added in the future
198199
199200
<issue> ::= {
200201
"isKnown": <bool>, ; is this a known issue or not?
201202
["sourceLocation": <source-location>,] ; where the issue occurred, if known
202203
}
203204
205+
<attachment> ::= {
206+
"path": <string>, ; the absolute path to the attachment on disk
207+
}
208+
204209
<message> ::= {
205210
"symbol": <message-symbol>,
206211
"text": <string>, ; the human-readable text of this message

Sources/Overlays/_Testing_CoreGraphics/Attachments/Attachment+AttachableAsCGImage.swift

+5-5
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ extension Attachment {
4242
contentType: (any Sendable)?,
4343
encodingQuality: Float,
4444
sourceLocation: SourceLocation
45-
) where AttachableValue == _AttachableImageContainer<T> {
46-
let imageContainer = _AttachableImageContainer(image: attachableValue, encodingQuality: encodingQuality, contentType: contentType)
47-
self.init(imageContainer, named: preferredName, sourceLocation: sourceLocation)
45+
) where AttachableValue == _AttachableImageWrapper<T> {
46+
let imageWrapper = _AttachableImageWrapper(image: attachableValue, encodingQuality: encodingQuality, contentType: contentType)
47+
self.init(imageWrapper, named: preferredName, sourceLocation: sourceLocation)
4848
}
4949

5050
/// Initialize an instance of this type that encloses the given image.
@@ -79,7 +79,7 @@ extension Attachment {
7979
as contentType: UTType?,
8080
encodingQuality: Float = 1.0,
8181
sourceLocation: SourceLocation = #_sourceLocation
82-
) where AttachableValue == _AttachableImageContainer<T> {
82+
) where AttachableValue == _AttachableImageWrapper<T> {
8383
self.init(attachableValue: attachableValue, named: preferredName, contentType: contentType, encodingQuality: encodingQuality, sourceLocation: sourceLocation)
8484
}
8585

@@ -109,7 +109,7 @@ extension Attachment {
109109
named preferredName: String? = nil,
110110
encodingQuality: Float = 1.0,
111111
sourceLocation: SourceLocation = #_sourceLocation
112-
) where AttachableValue == _AttachableImageContainer<T> {
112+
) where AttachableValue == _AttachableImageWrapper<T> {
113113
self.init(attachableValue: attachableValue, named: preferredName, contentType: nil, encodingQuality: encodingQuality, sourceLocation: sourceLocation)
114114
}
115115
}

Sources/Overlays/_Testing_CoreGraphics/Attachments/_AttachableImageContainer.swift Sources/Overlays/_Testing_CoreGraphics/Attachments/_AttachableImageWrapper.swift

+4-4
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
//
1010

1111
#if SWT_TARGET_OS_APPLE && canImport(CoreGraphics)
12-
@_spi(Experimental) public import Testing
12+
public import Testing
1313
private import CoreGraphics
1414

1515
private import ImageIO
@@ -48,7 +48,7 @@ import UniformTypeIdentifiers
4848
///
4949
/// - [`CGImage`](https://developer.apple.com/documentation/coregraphics/cgimage)
5050
@_spi(Experimental)
51-
public struct _AttachableImageContainer<Image>: Sendable where Image: AttachableAsCGImage {
51+
public struct _AttachableImageWrapper<Image>: Sendable where Image: AttachableAsCGImage {
5252
/// The underlying image.
5353
///
5454
/// `CGImage` and `UIImage` are sendable, but `NSImage` is not. `NSImage`
@@ -127,8 +127,8 @@ public struct _AttachableImageContainer<Image>: Sendable where Image: Attachable
127127

128128
// MARK: -
129129

130-
extension _AttachableImageContainer: AttachableContainer {
131-
public var attachableValue: Image {
130+
extension _AttachableImageWrapper: AttachableWrapper {
131+
public var wrappedValue: Image {
132132
image
133133
}
134134

Sources/Overlays/_Testing_Foundation/Attachments/Attachable+Encodable+NSSecureCoding.swift

+4-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
//
1010

1111
#if canImport(Foundation)
12-
@_spi(Experimental) public import Testing
12+
public import Testing
1313
public import Foundation
1414

1515
// This implementation is necessary to let the compiler disambiguate when a type
@@ -18,7 +18,9 @@ public import Foundation
1818
// (which explicitly document what happens when a type conforms to both
1919
// protocols.)
2020

21-
@_spi(Experimental)
21+
/// @Metadata {
22+
/// @Available(Swift, introduced: 6.2)
23+
/// }
2224
extension Attachable where Self: Encodable & NSSecureCoding {
2325
@_documentation(visibility: private)
2426
public func withUnsafeBytes<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {

Sources/Overlays/_Testing_Foundation/Attachments/Attachable+Encodable.swift

+9-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
//
1010

1111
#if canImport(Foundation)
12-
@_spi(Experimental) public import Testing
12+
public import Testing
1313
private import Foundation
1414

1515
/// A common implementation of ``withUnsafeBytes(for:_:)`` that is used when a
@@ -53,7 +53,10 @@ func withUnsafeBytes<E, R>(encoding attachableValue: borrowing E, for attachment
5353
// Implement the protocol requirements generically for any encodable value by
5454
// encoding to JSON. This lets developers provide trivial conformance to the
5555
// protocol for types that already support Codable.
56-
@_spi(Experimental)
56+
57+
/// @Metadata {
58+
/// @Available(Swift, introduced: 6.2)
59+
/// }
5760
extension Attachable where Self: Encodable {
5861
/// Encode this value into a buffer using either [`PropertyListEncoder`](https://developer.apple.com/documentation/foundation/propertylistencoder)
5962
/// or [`JSONEncoder`](https://developer.apple.com/documentation/foundation/jsonencoder),
@@ -86,6 +89,10 @@ extension Attachable where Self: Encodable {
8689
/// _and_ [`NSSecureCoding`](https://developer.apple.com/documentation/foundation/nssecurecoding),
8790
/// the default implementation of this function uses the value's conformance
8891
/// to `Encodable`.
92+
///
93+
/// @Metadata {
94+
/// @Available(Swift, introduced: 6.2)
95+
/// }
8996
public func withUnsafeBytes<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
9097
try _Testing_Foundation.withUnsafeBytes(encoding: self, for: attachment, body)
9198
}

Sources/Overlays/_Testing_Foundation/Attachments/Attachable+NSSecureCoding.swift

+9-2
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@
99
//
1010

1111
#if canImport(Foundation)
12-
@_spi(Experimental) public import Testing
12+
public import Testing
1313
public import Foundation
1414

1515
// As with Encodable, implement the protocol requirements for
1616
// NSSecureCoding-conformant classes by default. The implementation uses
1717
// NSKeyedArchiver for encoding.
18-
@_spi(Experimental)
18+
19+
/// @Metadata {
20+
/// @Available(Swift, introduced: 6.2)
21+
/// }
1922
extension Attachable where Self: NSSecureCoding {
2023
/// Encode this object using [`NSKeyedArchiver`](https://developer.apple.com/documentation/foundation/nskeyedarchiver)
2124
/// into a buffer, then call a function and pass that buffer to it.
@@ -46,6 +49,10 @@ extension Attachable where Self: NSSecureCoding {
4649
/// _and_ [`NSSecureCoding`](https://developer.apple.com/documentation/foundation/nssecurecoding),
4750
/// the default implementation of this function uses the value's conformance
4851
/// to `Encodable`.
52+
///
53+
/// @Metadata {
54+
/// @Available(Swift, introduced: 6.2)
55+
/// }
4956
public func withUnsafeBytes<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
5057
let format = try EncodingFormat(for: attachment)
5158

Sources/Overlays/_Testing_Foundation/Attachments/Attachment+URL.swift

+8-5
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
//
1010

1111
#if canImport(Foundation)
12-
@_spi(Experimental) public import Testing
12+
public import Testing
1313
public import Foundation
1414

1515
#if !SWT_NO_PROCESS_SPAWNING && os(Windows)
@@ -32,8 +32,7 @@ extension URL {
3232
}
3333
}
3434

35-
@_spi(Experimental)
36-
extension Attachment where AttachableValue == _AttachableURLContainer {
35+
extension Attachment where AttachableValue == _AttachableURLWrapper {
3736
#if SWT_TARGET_OS_APPLE
3837
/// An operation queue to use for asynchronously reading data from disk.
3938
private static let _operationQueue = OperationQueue()
@@ -51,6 +50,10 @@ extension Attachment where AttachableValue == _AttachableURLContainer {
5150
/// attachment.
5251
///
5352
/// - Throws: Any error that occurs attempting to read from `url`.
53+
///
54+
/// @Metadata {
55+
/// @Available(Swift, introduced: 6.2)
56+
/// }
5457
public init(
5558
contentsOf url: URL,
5659
named preferredName: String? = nil,
@@ -91,8 +94,8 @@ extension Attachment where AttachableValue == _AttachableURLContainer {
9194
}
9295
#endif
9396

94-
let urlContainer = _AttachableURLContainer(url: url, data: data, isCompressedDirectory: isDirectory)
95-
self.init(urlContainer, named: preferredName, sourceLocation: sourceLocation)
97+
let urlWrapper = _AttachableURLWrapper(url: url, data: data, isCompressedDirectory: isDirectory)
98+
self.init(urlWrapper, named: preferredName, sourceLocation: sourceLocation)
9699
}
97100
}
98101

Sources/Overlays/_Testing_Foundation/Attachments/Data+Attachable.swift

+7-2
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,16 @@
99
//
1010

1111
#if canImport(Foundation)
12-
@_spi(Experimental) public import Testing
12+
public import Testing
1313
public import Foundation
1414

15-
@_spi(Experimental)
15+
/// @Metadata {
16+
/// @Available(Swift, introduced: 6.2)
17+
/// }
1618
extension Data: Attachable {
19+
/// @Metadata {
20+
/// @Available(Swift, introduced: 6.2)
21+
/// }
1722
public func withUnsafeBytes<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
1823
try withUnsafeBytes(body)
1924
}

Sources/Overlays/_Testing_Foundation/Attachments/EncodingFormat.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
//
1010

1111
#if canImport(Foundation)
12-
@_spi(Experimental) import Testing
12+
import Testing
1313
import Foundation
1414

1515
/// An enumeration describing the encoding formats we support for `Encodable`

Sources/Overlays/_Testing_Foundation/Attachments/_AttachableURLContainer.swift Sources/Overlays/_Testing_Foundation/Attachments/_AttachableURLWrapper.swift

+4-5
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,15 @@
99
//
1010

1111
#if canImport(Foundation)
12-
@_spi(Experimental) public import Testing
12+
public import Testing
1313
public import Foundation
1414

1515
/// A wrapper type representing file system objects and URLs that can be
1616
/// attached indirectly.
1717
///
1818
/// You do not need to use this type directly. Instead, initialize an instance
1919
/// of ``Attachment`` using a file URL.
20-
@_spi(Experimental)
21-
public struct _AttachableURLContainer: Sendable {
20+
public struct _AttachableURLWrapper: Sendable {
2221
/// The underlying URL.
2322
var url: URL
2423

@@ -31,8 +30,8 @@ public struct _AttachableURLContainer: Sendable {
3130

3231
// MARK: -
3332

34-
extension _AttachableURLContainer: AttachableContainer {
35-
public var attachableValue: URL {
33+
extension _AttachableURLWrapper: AttachableWrapper {
34+
public var wrappedValue: URL {
3635
url
3736
}
3837

Sources/Overlays/_Testing_Foundation/CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors
88

99
add_library(_Testing_Foundation
10-
Attachments/_AttachableURLContainer.swift
10+
Attachments/_AttachableURLWrapper.swift
1111
Attachments/EncodingFormat.swift
1212
Attachments/Attachment+URL.swift
1313
Attachments/Attachable+NSSecureCoding.swift

Sources/Testing/ABI/Encoded/ABI.EncodedEvent.swift

+3-5
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ extension ABI {
2727
case testStarted
2828
case testCaseStarted
2929
case issueRecorded
30-
case valueAttached = "_valueAttached"
30+
case valueAttached
3131
case testCaseEnded
3232
case testEnded
3333
case testSkipped
@@ -50,9 +50,7 @@ extension ABI {
5050
///
5151
/// The value of this property is `nil` unless the value of the
5252
/// ``kind-swift.property`` property is ``Kind-swift.enum/valueAttached``.
53-
///
54-
/// - Warning: Attachments are not yet part of the JSON schema.
55-
var _attachment: EncodedAttachment<V>?
53+
var attachment: EncodedAttachment<V>?
5654

5755
/// Human-readable messages associated with this event that can be presented
5856
/// to the user.
@@ -82,7 +80,7 @@ extension ABI {
8280
issue = EncodedIssue(encoding: recordedIssue, in: eventContext)
8381
case let .valueAttached(attachment):
8482
kind = .valueAttached
85-
_attachment = EncodedAttachment(encoding: attachment, in: eventContext)
83+
self.attachment = EncodedAttachment(encoding: attachment, in: eventContext)
8684
case .testCaseEnded:
8785
if eventContext.test?.isParameterized == false {
8886
return nil

Sources/Testing/Attachments/Attachable.swift

+18-8
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@
2424
/// A type should conform to this protocol if it can be represented as a
2525
/// sequence of bytes that would be diagnostically useful if a test fails. If a
2626
/// type cannot conform directly to this protocol (such as a non-final class or
27-
/// a type declared in a third-party module), you can create a container type
28-
/// that conforms to ``AttachableContainer`` to act as a proxy.
29-
@_spi(Experimental)
27+
/// a type declared in a third-party module), you can create a wrapper type that
28+
/// conforms to ``AttachableWrapper`` to act as a proxy.
29+
///
30+
/// @Metadata {
31+
/// @Available(Swift, introduced: 6.2)
32+
/// }
3033
public protocol Attachable: ~Copyable {
3134
/// An estimate of the number of bytes of memory needed to store this value as
3235
/// an attachment.
@@ -42,6 +45,10 @@ public protocol Attachable: ~Copyable {
4245
///
4346
/// - Complexity: O(1) unless `Self` conforms to `Collection`, in which case
4447
/// up to O(_n_) where _n_ is the length of the collection.
48+
///
49+
/// @Metadata {
50+
/// @Available(Swift, introduced: 6.2)
51+
/// }
4552
var estimatedAttachmentByteCount: Int? { get }
4653

4754
/// Call a function and pass a buffer representing this instance to it.
@@ -64,6 +71,10 @@ public protocol Attachable: ~Copyable {
6471
/// the buffer to contain an image in PNG format, JPEG format, etc., but it
6572
/// would not be idiomatic for the buffer to contain a textual description of
6673
/// the image.
74+
///
75+
/// @Metadata {
76+
/// @Available(Swift, introduced: 6.2)
77+
/// }
6778
borrowing func withUnsafeBytes<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R
6879

6980
/// Generate a preferred name for the given attachment.
@@ -80,6 +91,10 @@ public protocol Attachable: ~Copyable {
8091
/// when adding `attachment` to a test report or persisting it to storage. The
8192
/// default implementation of this function returns `suggestedName` without
8293
/// any changes.
94+
///
95+
/// @Metadata {
96+
/// @Available(Swift, introduced: 6.2)
97+
/// }
8398
borrowing func preferredName(for attachment: borrowing Attachment<Self>, basedOn suggestedName: String) -> String
8499
}
85100

@@ -119,28 +134,24 @@ extension Attachable where Self: StringProtocol {
119134

120135
// Implement the protocol requirements for byte arrays and buffers so that
121136
// developers can attach raw data when needed.
122-
@_spi(Experimental)
123137
extension Array<UInt8>: Attachable {
124138
public func withUnsafeBytes<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
125139
try withUnsafeBytes(body)
126140
}
127141
}
128142

129-
@_spi(Experimental)
130143
extension ContiguousArray<UInt8>: Attachable {
131144
public func withUnsafeBytes<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
132145
try withUnsafeBytes(body)
133146
}
134147
}
135148

136-
@_spi(Experimental)
137149
extension ArraySlice<UInt8>: Attachable {
138150
public func withUnsafeBytes<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
139151
try withUnsafeBytes(body)
140152
}
141153
}
142154

143-
@_spi(Experimental)
144155
extension String: Attachable {
145156
public func withUnsafeBytes<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
146157
var selfCopy = self
@@ -150,7 +161,6 @@ extension String: Attachable {
150161
}
151162
}
152163

153-
@_spi(Experimental)
154164
extension Substring: Attachable {
155165
public func withUnsafeBytes<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
156166
var selfCopy = self

0 commit comments

Comments
 (0)