Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Documentation/ABI/JSON.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,19 +188,24 @@ sufficient information to display the event in a human-readable format.
"kind": <event-kind>,
"instant": <instant>, ; when the event occurred
["issue": <issue>,] ; the recorded issue (if "kind" is "issueRecorded")
["attachment": <attachment>,] ; the attachment (if kind is "valueAttached")
"messages": <array:message>,
["testID": <test-id>,]
}

<event-kind> ::= "runStarted" | "testStarted" | "testCaseStarted" |
"issueRecorded" | "testCaseEnded" | "testEnded" | "testSkipped" |
"runEnded" ; additional event kinds may be added in the future
"runEnded" | "valueAttached"; additional event kinds may be added in the future

<issue> ::= {
"isKnown": <bool>, ; is this a known issue or not?
["sourceLocation": <source-location>,] ; where the issue occurred, if known
}

<attachment> ::= {
"path": <string>, ; the absolute path to the attachment on disk
}

<message> ::= {
"symbol": <message-symbol>,
"text": <string>, ; the human-readable text of this message
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ extension Attachment {
contentType: (any Sendable)?,
encodingQuality: Float,
sourceLocation: SourceLocation
) where AttachableValue == _AttachableImageContainer<T> {
let imageContainer = _AttachableImageContainer(image: attachableValue, encodingQuality: encodingQuality, contentType: contentType)
self.init(imageContainer, named: preferredName, sourceLocation: sourceLocation)
) where AttachableValue == _AttachableImageWrapper<T> {
let imageWrapper = _AttachableImageWrapper(image: attachableValue, encodingQuality: encodingQuality, contentType: contentType)
self.init(imageWrapper, named: preferredName, sourceLocation: sourceLocation)
}

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

Expand Down Expand Up @@ -109,7 +109,7 @@ extension Attachment {
named preferredName: String? = nil,
encodingQuality: Float = 1.0,
sourceLocation: SourceLocation = #_sourceLocation
) where AttachableValue == _AttachableImageContainer<T> {
) where AttachableValue == _AttachableImageWrapper<T> {
self.init(attachableValue: attachableValue, named: preferredName, contentType: nil, encodingQuality: encodingQuality, sourceLocation: sourceLocation)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//

#if SWT_TARGET_OS_APPLE && canImport(CoreGraphics)
@_spi(Experimental) public import Testing
public import Testing
private import CoreGraphics

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

// MARK: -

extension _AttachableImageContainer: AttachableContainer {
public var attachableValue: Image {
extension _AttachableImageWrapper: AttachableWrapper {
public var wrappedValue: Image {
image
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//

#if canImport(Foundation)
@_spi(Experimental) public import Testing
public import Testing
public import Foundation

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

@_spi(Experimental)
/// @Metadata {
/// @Available(Swift, introduced: 6.2)
/// }
extension Attachable where Self: Encodable & NSSecureCoding {
@_documentation(visibility: private)
public func withUnsafeBytes<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//

#if canImport(Foundation)
@_spi(Experimental) public import Testing
public import Testing
private import Foundation

/// A common implementation of ``withUnsafeBytes(for:_:)`` that is used when a
Expand Down Expand Up @@ -53,7 +53,10 @@ func withUnsafeBytes<E, R>(encoding attachableValue: borrowing E, for attachment
// Implement the protocol requirements generically for any encodable value by
// encoding to JSON. This lets developers provide trivial conformance to the
// protocol for types that already support Codable.
@_spi(Experimental)

/// @Metadata {
/// @Available(Swift, introduced: 6.2)
/// }
extension Attachable where Self: Encodable {
/// Encode this value into a buffer using either [`PropertyListEncoder`](https://developer.apple.com/documentation/foundation/propertylistencoder)
/// or [`JSONEncoder`](https://developer.apple.com/documentation/foundation/jsonencoder),
Expand Down Expand Up @@ -86,6 +89,10 @@ extension Attachable where Self: Encodable {
/// _and_ [`NSSecureCoding`](https://developer.apple.com/documentation/foundation/nssecurecoding),
/// the default implementation of this function uses the value's conformance
/// to `Encodable`.
///
/// @Metadata {
/// @Available(Swift, introduced: 6.2)
/// }
public func withUnsafeBytes<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
try _Testing_Foundation.withUnsafeBytes(encoding: self, for: attachment, body)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@
//

#if canImport(Foundation)
@_spi(Experimental) public import Testing
public import Testing
public import Foundation

// As with Encodable, implement the protocol requirements for
// NSSecureCoding-conformant classes by default. The implementation uses
// NSKeyedArchiver for encoding.
@_spi(Experimental)

/// @Metadata {
/// @Available(Swift, introduced: 6.2)
/// }
extension Attachable where Self: NSSecureCoding {
/// Encode this object using [`NSKeyedArchiver`](https://developer.apple.com/documentation/foundation/nskeyedarchiver)
/// into a buffer, then call a function and pass that buffer to it.
Expand Down Expand Up @@ -46,6 +49,10 @@ extension Attachable where Self: NSSecureCoding {
/// _and_ [`NSSecureCoding`](https://developer.apple.com/documentation/foundation/nssecurecoding),
/// the default implementation of this function uses the value's conformance
/// to `Encodable`.
///
/// @Metadata {
/// @Available(Swift, introduced: 6.2)
/// }
public func withUnsafeBytes<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
let format = try EncodingFormat(for: attachment)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//

#if canImport(Foundation)
@_spi(Experimental) public import Testing
public import Testing
public import Foundation

#if !SWT_NO_PROCESS_SPAWNING && os(Windows)
Expand All @@ -32,8 +32,7 @@ extension URL {
}
}

@_spi(Experimental)
extension Attachment where AttachableValue == _AttachableURLContainer {
extension Attachment where AttachableValue == _AttachableURLWrapper {
#if SWT_TARGET_OS_APPLE
/// An operation queue to use for asynchronously reading data from disk.
private static let _operationQueue = OperationQueue()
Expand All @@ -51,6 +50,10 @@ extension Attachment where AttachableValue == _AttachableURLContainer {
/// attachment.
///
/// - Throws: Any error that occurs attempting to read from `url`.
///
/// @Metadata {
/// @Available(Swift, introduced: 6.2)
/// }
public init(
contentsOf url: URL,
named preferredName: String? = nil,
Expand Down Expand Up @@ -91,8 +94,8 @@ extension Attachment where AttachableValue == _AttachableURLContainer {
}
#endif

let urlContainer = _AttachableURLContainer(url: url, data: data, isCompressedDirectory: isDirectory)
self.init(urlContainer, named: preferredName, sourceLocation: sourceLocation)
let urlWrapper = _AttachableURLWrapper(url: url, data: data, isCompressedDirectory: isDirectory)
self.init(urlWrapper, named: preferredName, sourceLocation: sourceLocation)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@
//

#if canImport(Foundation)
@_spi(Experimental) public import Testing
public import Testing
public import Foundation

@_spi(Experimental)
/// @Metadata {
/// @Available(Swift, introduced: 6.2)
/// }
extension Data: Attachable {
/// @Metadata {
/// @Available(Swift, introduced: 6.2)
/// }
public func withUnsafeBytes<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
try withUnsafeBytes(body)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//

#if canImport(Foundation)
@_spi(Experimental) import Testing
import Testing
import Foundation

/// An enumeration describing the encoding formats we support for `Encodable`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,15 @@
//

#if canImport(Foundation)
@_spi(Experimental) public import Testing
public import Testing
public import Foundation

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

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

// MARK: -

extension _AttachableURLContainer: AttachableContainer {
public var attachableValue: URL {
extension _AttachableURLWrapper: AttachableWrapper {
public var wrappedValue: URL {
url
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/Overlays/_Testing_Foundation/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors

add_library(_Testing_Foundation
Attachments/_AttachableURLContainer.swift
Attachments/_AttachableURLWrapper.swift
Attachments/EncodingFormat.swift
Attachments/Attachment+URL.swift
Attachments/Attachable+NSSecureCoding.swift
Expand Down
8 changes: 3 additions & 5 deletions Sources/Testing/ABI/Encoded/ABI.EncodedEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ extension ABI {
case testStarted
case testCaseStarted
case issueRecorded
case valueAttached = "_valueAttached"
case valueAttached
case testCaseEnded
case testEnded
case testSkipped
Expand All @@ -50,9 +50,7 @@ extension ABI {
///
/// The value of this property is `nil` unless the value of the
/// ``kind-swift.property`` property is ``Kind-swift.enum/valueAttached``.
///
/// - Warning: Attachments are not yet part of the JSON schema.
var _attachment: EncodedAttachment<V>?
var attachment: EncodedAttachment<V>?

/// Human-readable messages associated with this event that can be presented
/// to the user.
Expand Down Expand Up @@ -82,7 +80,7 @@ extension ABI {
issue = EncodedIssue(encoding: recordedIssue, in: eventContext)
case let .valueAttached(attachment):
kind = .valueAttached
_attachment = EncodedAttachment(encoding: attachment, in: eventContext)
self.attachment = EncodedAttachment(encoding: attachment, in: eventContext)
case .testCaseEnded:
if eventContext.test?.isParameterized == false {
return nil
Expand Down
26 changes: 18 additions & 8 deletions Sources/Testing/Attachments/Attachable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@
/// A type should conform to this protocol if it can be represented as a
/// sequence of bytes that would be diagnostically useful if a test fails. If a
/// type cannot conform directly to this protocol (such as a non-final class or
/// a type declared in a third-party module), you can create a container type
/// that conforms to ``AttachableContainer`` to act as a proxy.
@_spi(Experimental)
/// a type declared in a third-party module), you can create a wrapper type that
/// conforms to ``AttachableWrapper`` to act as a proxy.
///
/// @Metadata {
/// @Available(Swift, introduced: 6.2)
/// }
public protocol Attachable: ~Copyable {
/// An estimate of the number of bytes of memory needed to store this value as
/// an attachment.
Expand All @@ -42,6 +45,10 @@ public protocol Attachable: ~Copyable {
///
/// - Complexity: O(1) unless `Self` conforms to `Collection`, in which case
/// up to O(_n_) where _n_ is the length of the collection.
///
/// @Metadata {
/// @Available(Swift, introduced: 6.2)
/// }
var estimatedAttachmentByteCount: Int? { get }

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

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

Expand Down Expand Up @@ -119,28 +134,24 @@ extension Attachable where Self: StringProtocol {

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

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

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

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

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