Skip to content

Commit c165dd9

Browse files
committed
[SWT-NNNN] Attachments
Test authors frequently need to include out-of-band data with tests that can be used to diagnose issues when a test fails. This proposal introduces a new API called "attachments" (analogous to the same-named feature in XCTest) as well as the infrastructure necessary to create new attachments and handle them in tools like VS Code. Read the full proposal [here]().
1 parent 588807a commit c165dd9

17 files changed

+654
-34
lines changed

Documentation/ABI/JSON.md

Lines changed: 6 additions & 1 deletion
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

Documentation/Proposals/NNNN-attachments.md

Lines changed: 436 additions & 0 deletions
Large diffs are not rendered by default.

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

Lines changed: 4 additions & 2 deletions
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

Lines changed: 9 additions & 2 deletions
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

Lines changed: 9 additions & 2 deletions
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

Lines changed: 5 additions & 2 deletions
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,7 +32,6 @@ extension URL {
3232
}
3333
}
3434

35-
@_spi(Experimental)
3635
extension Attachment where AttachableValue == _AttachableURLContainer {
3736
#if SWT_TARGET_OS_APPLE
3837
/// An operation queue to use for asynchronously reading data from disk.
@@ -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,

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

Lines changed: 7 additions & 2 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,14 @@
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)
2120
public struct _AttachableURLContainer: Sendable {
2221
/// The underlying URL.
2322
var url: URL

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

Lines changed: 3 additions & 5 deletions
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

0 commit comments

Comments
 (0)