Skip to content

Commit 17209c0

Browse files
authored
Hoist Attachment and Attachable out of Test. (#821)
I initially placed these types inside `Test` out of concern that they could conflict with symbols in second- or third-party modules that are being tested. After discussing with my colleagues, we've decided to hoist the types up to the top level of the Testing module. There's no general solution for name conflicts in Swift other than providing the module name at point-of-use, and by and large the language, toolchain, and Apple's frameworks just sort of accept this possibility? So I was maybe overthinking it. If we run into trouble down the line before release, we can reevaluate this decision. ### 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 8b3632a commit 17209c0

File tree

9 files changed

+299
-308
lines changed

9 files changed

+299
-308
lines changed

Sources/Testing/ABI/v0/Encoded/ABIv0.EncodedAttachment.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ extension ABIv0 {
2121
/// The path where the attachment was written.
2222
var path: String?
2323

24-
init(encoding attachment: borrowing Test.Attachment<Test.AnyAttachable>, in eventContext: borrowing Event.Context) {
24+
init(encoding attachment: borrowing Attachment<AnyAttachable>, in eventContext: borrowing Event.Context) {
2525
path = attachment.fileSystemPath
2626
}
2727
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
//
2+
// This source file is part of the Swift.org open source project
3+
//
4+
// Copyright (c) 2024 Apple Inc. and the Swift project authors
5+
// Licensed under Apache License v2.0 with Runtime Library Exception
6+
//
7+
// See https://swift.org/LICENSE.txt for license information
8+
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
//
10+
11+
/// A protocol describing a type that can be attached to a test report or
12+
/// written to disk when a test is run.
13+
///
14+
/// To attach an attachable value to a test report or test run output, use it to
15+
/// initialize a new instance of ``Test/Attachment``, then call
16+
/// ``Test/Attachment/attach(sourceLocation:)``. An attachment can only be
17+
/// attached once.
18+
///
19+
/// The testing library provides default conformances to this protocol for a
20+
/// variety of standard library types. Most user-defined types do not need to
21+
/// conform to this protocol.
22+
///
23+
/// A type should conform to this protocol if it can be represented as a
24+
/// sequence of bytes that would be diagnostically useful if a test fails. If a
25+
/// type cannot conform directly to this protocol (such as a non-final class or
26+
/// a type declared in a third-party module), you can create a container type
27+
/// that conforms to ``Test/AttachableContainer`` to act as a proxy.
28+
@_spi(Experimental)
29+
public protocol Attachable: ~Copyable {
30+
/// An estimate of the number of bytes of memory needed to store this value as
31+
/// an attachment.
32+
///
33+
/// The testing library uses this property to determine if an attachment
34+
/// should be held in memory or should be immediately persisted to storage.
35+
/// Larger attachments are more likely to be persisted, but the algorithm the
36+
/// testing library uses is an implementation detail and is subject to change.
37+
///
38+
/// The value of this property is approximately equal to the number of bytes
39+
/// that will actually be needed, or `nil` if the value cannot be computed
40+
/// efficiently. The default implementation of this property returns `nil`.
41+
///
42+
/// - Complexity: O(1) unless `Self` conforms to `Collection`, in which case
43+
/// up to O(_n_) where _n_ is the length of the collection.
44+
var estimatedAttachmentByteCount: Int? { get }
45+
46+
/// Call a function and pass a buffer representing this instance to it.
47+
///
48+
/// - Parameters:
49+
/// - attachment: The attachment that is requesting a buffer (that is, the
50+
/// attachment containing this instance.)
51+
/// - body: A function to call. A temporary buffer containing a data
52+
/// representation of this instance is passed to it.
53+
///
54+
/// - Returns: Whatever is returned by `body`.
55+
///
56+
/// - Throws: Whatever is thrown by `body`, or any error that prevented the
57+
/// creation of the buffer.
58+
///
59+
/// The testing library uses this function when writing an attachment to a
60+
/// test report or to a file on disk. The format of the buffer is
61+
/// implementation-defined, but should be "idiomatic" for this type: for
62+
/// example, if this type represents an image, it would be appropriate for
63+
/// the buffer to contain an image in PNG format, JPEG format, etc., but it
64+
/// would not be idiomatic for the buffer to contain a textual description of
65+
/// the image.
66+
borrowing func withUnsafeBufferPointer<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R
67+
}
68+
69+
// MARK: - Default implementations
70+
71+
extension Attachable where Self: ~Copyable {
72+
public var estimatedAttachmentByteCount: Int? {
73+
nil
74+
}
75+
}
76+
77+
extension Attachable where Self: Collection, Element == UInt8 {
78+
public var estimatedAttachmentByteCount: Int? {
79+
count
80+
}
81+
82+
// We do not provide an implementation of withUnsafeBufferPointer(for:_:) here
83+
// because there is no way in the standard library to statically detect if a
84+
// collection can provide contiguous storage (_HasContiguousBytes is not API.)
85+
// If withContiguousBytesIfAvailable(_:) fails, we don't want to make a
86+
// (potentially expensive!) copy of the collection.
87+
//
88+
// The planned Foundation cross-import overlay can provide a default
89+
// implementation for collection types that conform to Foundation's
90+
// ContiguousBytes protocol.
91+
}
92+
93+
extension Attachable where Self: StringProtocol {
94+
public var estimatedAttachmentByteCount: Int? {
95+
// NOTE: utf8.count may be O(n) for foreign strings.
96+
// SEE: https://github.com/swiftlang/swift/blob/main/stdlib/public/core/StringUTF8View.swift
97+
utf8.count
98+
}
99+
}
100+
101+
// MARK: - Default conformances
102+
103+
// Implement the protocol requirements for byte arrays and buffers so that
104+
// developers can attach raw data when needed.
105+
@_spi(Experimental)
106+
extension Array<UInt8>: Attachable {
107+
public func withUnsafeBufferPointer<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
108+
try withUnsafeBytes(body)
109+
}
110+
}
111+
112+
@_spi(Experimental)
113+
extension ContiguousArray<UInt8>: Attachable {
114+
public func withUnsafeBufferPointer<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
115+
try withUnsafeBytes(body)
116+
}
117+
}
118+
119+
@_spi(Experimental)
120+
extension ArraySlice<UInt8>: Attachable {
121+
public func withUnsafeBufferPointer<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
122+
try withUnsafeBytes(body)
123+
}
124+
}
125+
126+
@_spi(Experimental)
127+
extension String: Attachable {
128+
public func withUnsafeBufferPointer<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
129+
var selfCopy = self
130+
return try selfCopy.withUTF8 { utf8 in
131+
try body(UnsafeRawBufferPointer(utf8))
132+
}
133+
}
134+
}
135+
136+
@_spi(Experimental)
137+
extension Substring: Attachable {
138+
public func withUnsafeBufferPointer<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
139+
var selfCopy = self
140+
return try selfCopy.withUTF8 { utf8 in
141+
try body(UnsafeRawBufferPointer(utf8))
142+
}
143+
}
144+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// This source file is part of the Swift.org open source project
3+
//
4+
// Copyright (c) 2024 Apple Inc. and the Swift project authors
5+
// Licensed under Apache License v2.0 with Runtime Library Exception
6+
//
7+
// See https://swift.org/LICENSE.txt for license information
8+
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
//
10+
11+
/// A protocol describing a type that can be attached to a test report or
12+
/// written to disk when a test is run and which contains another value that it
13+
/// stands in for.
14+
///
15+
/// To attach an attachable value to a test report or test run output, use it to
16+
/// initialize a new instance of ``Test/Attachment``, then call
17+
/// ``Test/Attachment/attach(sourceLocation:)``. An attachment can only be
18+
/// attached once.
19+
///
20+
/// A type can conform to this protocol if it represents another type that
21+
/// cannot directly conform to ``Test/Attachable``, such as a non-final class or
22+
/// a type declared in a third-party module.
23+
@_spi(Experimental)
24+
public protocol AttachableContainer<AttachableValue>: Attachable, ~Copyable {
25+
#if hasFeature(SuppressedAssociatedTypes)
26+
/// The type of the attachable value represented by this type.
27+
associatedtype AttachableValue: ~Copyable
28+
#else
29+
/// The type of the attachable value represented by this type.
30+
associatedtype AttachableValue
31+
#endif
32+
33+
/// The attachable value represented by this instance.
34+
var attachableValue: AttachableValue { get }
35+
}

0 commit comments

Comments
 (0)