From 035bce02c4254dea4d327adb90730f5b0cb5feed Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Tue, 11 Nov 2025 09:44:34 -0500 Subject: [PATCH 1/2] Provide prose documentation for attachments. This PR adds prose documentation explaining how to create attachments and add them to tests. Resolves #1143. --- Sources/Testing/Attachments/Attachment.swift | 13 -- .../Images/AttachableAsImage.swift | 9 - .../Images/AttachableImageFormat.swift | 4 +- .../Images/Attachment+AttachableAsImage.swift | 33 +--- .../Images/_AttachableImageWrapper.swift | 9 - Sources/Testing/Testing.docc/Attachments.md | 168 ++++++++++++++++++ .../Traits/AttachmentSavingTrait.swift | 48 ----- 7 files changed, 178 insertions(+), 106 deletions(-) diff --git a/Sources/Testing/Attachments/Attachment.swift b/Sources/Testing/Attachments/Attachment.swift index 0cbd5d703..36b2399f9 100644 --- a/Sources/Testing/Attachments/Attachment.swift +++ b/Sources/Testing/Attachments/Attachment.swift @@ -19,19 +19,6 @@ private import _TestingInternals /// record the attachment, call ``Attachment/record(_:sourceLocation:)``. /// Alternatively, pass your attachable value directly to ``Attachment/record(_:named:sourceLocation:)``. /// -/// By default, the testing library saves your attachments as soon as you call -/// ``Attachment/record(_:sourceLocation:)`` or -/// ``Attachment/record(_:named:sourceLocation:)``. You can access saved -/// attachments after your tests finish running: -/// -/// - When using Xcode, you can access attachments from the test report. -/// - When using Visual Studio Code, the testing library saves attachments to -/// `.build/attachments` by default. Visual Studio Code reports the paths to -/// individual attachments in its Tests Results panel. -/// - When using Swift Package Manager's `swift test` command, you can pass the -/// `--attachments-path` option. The testing library saves attachments to the -/// specified directory. -/// /// @Metadata { /// @Available(Swift, introduced: 6.2) /// @Available(Xcode, introduced: 26.0) diff --git a/Sources/Testing/Attachments/Images/AttachableAsImage.swift b/Sources/Testing/Attachments/Images/AttachableAsImage.swift index a0683561c..0c3be1528 100644 --- a/Sources/Testing/Attachments/Images/AttachableAsImage.swift +++ b/Sources/Testing/Attachments/Images/AttachableAsImage.swift @@ -45,15 +45,6 @@ /// Instead, the testing library provides additional initializers on [`Attachment`](https://developer.apple.com/documentation/testing/attachment) /// that take instances of such types and handle converting them to image data when needed. /// -/// You can attach instances of the following system-provided image types to a -/// test: -/// -/// | Platform | Supported Types | -/// |-|-| -/// | macOS | [`CGImage`](https://developer.apple.com/documentation/coregraphics/cgimage), [`CIImage`](https://developer.apple.com/documentation/coreimage/ciimage), [`NSImage`](https://developer.apple.com/documentation/appkit/nsimage) | -/// | iOS, watchOS, tvOS, and visionOS | [`CGImage`](https://developer.apple.com/documentation/coregraphics/cgimage), [`CIImage`](https://developer.apple.com/documentation/coreimage/ciimage), [`UIImage`](https://developer.apple.com/documentation/uikit/uiimage) | -/// | Windows | [`HBITMAP`](https://learn.microsoft.com/en-us/windows/win32/gdi/bitmaps), [`HICON`](https://learn.microsoft.com/en-us/windows/win32/menurc/icons), [`IWICBitmapSource`](https://learn.microsoft.com/en-us/windows/win32/api/wincodec/nn-wincodec-iwicbitmapsource) (including its subclasses declared by Windows Imaging Component) | -/// /// You do not generally need to add your own conformances to this protocol. If /// you have an image in another format that needs to be attached to a test, /// first convert it to an instance of one of the types above. diff --git a/Sources/Testing/Attachments/Images/AttachableImageFormat.swift b/Sources/Testing/Attachments/Images/AttachableImageFormat.swift index 9bbc99c75..0bf3351fd 100644 --- a/Sources/Testing/Attachments/Images/AttachableImageFormat.swift +++ b/Sources/Testing/Attachments/Images/AttachableImageFormat.swift @@ -17,8 +17,8 @@ /// instance of this type, the testing library infers which format to use based /// on the attachment's preferred name. /// -/// The PNG and JPEG image formats are always supported. The set of additional -/// supported image formats is platform-specific: +/// The testing library always supports the PNG and JPEG image formats. The set +/// of additional supported image formats is platform-specific: /// /// - On Apple platforms, you can use [`CGImageDestinationCopyTypeIdentifiers()`](https://developer.apple.com/documentation/imageio/cgimagedestinationcopytypeidentifiers()) /// from the [Image I/O framework](https://developer.apple.com/documentation/imageio) diff --git a/Sources/Testing/Attachments/Images/Attachment+AttachableAsImage.swift b/Sources/Testing/Attachments/Images/Attachment+AttachableAsImage.swift index 3612596c5..0020c2fcd 100644 --- a/Sources/Testing/Attachments/Images/Attachment+AttachableAsImage.swift +++ b/Sources/Testing/Attachments/Images/Attachment+AttachableAsImage.swift @@ -29,15 +29,6 @@ extension Attachment { /// This value is used when recording issues associated with the /// attachment. /// - /// You can attach instances of the following system-provided image types to a - /// test: - /// - /// | Platform | Supported Types | - /// |-|-| - /// | macOS | [`CGImage`](https://developer.apple.com/documentation/coregraphics/cgimage), [`CIImage`](https://developer.apple.com/documentation/coreimage/ciimage), [`NSImage`](https://developer.apple.com/documentation/appkit/nsimage) | - /// | iOS, watchOS, tvOS, and visionOS | [`CGImage`](https://developer.apple.com/documentation/coregraphics/cgimage), [`CIImage`](https://developer.apple.com/documentation/coreimage/ciimage), [`UIImage`](https://developer.apple.com/documentation/uikit/uiimage) | - /// | Windows | [`HBITMAP`](https://learn.microsoft.com/en-us/windows/win32/gdi/bitmaps), [`HICON`](https://learn.microsoft.com/en-us/windows/win32/menurc/icons), [`IWICBitmapSource`](https://learn.microsoft.com/en-us/windows/win32/api/wincodec/nn-wincodec-iwicbitmapsource) (including its subclasses declared by Windows Imaging Component) | - /// /// The testing library uses the image format specified by `imageFormat`. Pass /// `nil` to let the testing library decide which image format to use. If you /// pass `nil`, then the image format that the testing library uses depends on @@ -70,22 +61,14 @@ extension Attachment { /// - sourceLocation: The source location of the call to this function. /// /// This function creates a new instance of ``Attachment`` wrapping `image` - /// and immediately attaches it to the current test. You can attach instances - /// of the following system-provided image types to a test: - /// - /// | Platform | Supported Types | - /// |-|-| - /// | macOS | [`CGImage`](https://developer.apple.com/documentation/coregraphics/cgimage), [`CIImage`](https://developer.apple.com/documentation/coreimage/ciimage), [`NSImage`](https://developer.apple.com/documentation/appkit/nsimage) | - /// | iOS, watchOS, tvOS, and visionOS | [`CGImage`](https://developer.apple.com/documentation/coregraphics/cgimage), [`CIImage`](https://developer.apple.com/documentation/coreimage/ciimage), [`UIImage`](https://developer.apple.com/documentation/uikit/uiimage) | - /// | Windows | [`HBITMAP`](https://learn.microsoft.com/en-us/windows/win32/gdi/bitmaps), [`HICON`](https://learn.microsoft.com/en-us/windows/win32/menurc/icons), [`IWICBitmapSource`](https://learn.microsoft.com/en-us/windows/win32/api/wincodec/nn-wincodec-iwicbitmapsource) (including its subclasses declared by Windows Imaging Component) | - /// - /// The testing library uses the image format specified by `imageFormat`. Pass - /// `nil` to let the testing library decide which image format to use. If you - /// pass `nil`, then the image format that the testing library uses depends on - /// the path extension you specify in `preferredName`, if any. If you do not - /// specify a path extension, or if the path extension you specify doesn't - /// correspond to an image format the operating system knows how to write, the - /// testing library selects an appropriate image format for you. + /// and immediately attaches it to the current test. The testing library uses + /// the image format specified by `imageFormat`. Pass `nil` to let the testing + /// library decide which image format to use. If you pass `nil`, then the + /// image format that the testing library uses depends on the path extension + /// you specify in `preferredName`, if any. If you do not specify a path + /// extension, or if the path extension you specify doesn't correspond to an + /// image format the operating system knows how to write, the testing library + /// selects an appropriate image format for you. /// /// @Metadata { /// @Available(Swift, introduced: 6.3) diff --git a/Sources/Testing/Attachments/Images/_AttachableImageWrapper.swift b/Sources/Testing/Attachments/Images/_AttachableImageWrapper.swift index 7065c03f7..3326fa833 100644 --- a/Sources/Testing/Attachments/Images/_AttachableImageWrapper.swift +++ b/Sources/Testing/Attachments/Images/_AttachableImageWrapper.swift @@ -9,15 +9,6 @@ // /// A wrapper type for images that can be indirectly attached to a test. -/// -/// You can attach instances of the following system-provided image types to a -/// test: -/// -/// | Platform | Supported Types | -/// |-|-| -/// | macOS | [`CGImage`](https://developer.apple.com/documentation/coregraphics/cgimage), [`CIImage`](https://developer.apple.com/documentation/coreimage/ciimage), [`NSImage`](https://developer.apple.com/documentation/appkit/nsimage) | -/// | iOS, watchOS, tvOS, and visionOS | [`CGImage`](https://developer.apple.com/documentation/coregraphics/cgimage), [`CIImage`](https://developer.apple.com/documentation/coreimage/ciimage), [`UIImage`](https://developer.apple.com/documentation/uikit/uiimage) | -/// | Windows | [`HBITMAP`](https://learn.microsoft.com/en-us/windows/win32/gdi/bitmaps), [`HICON`](https://learn.microsoft.com/en-us/windows/win32/menurc/icons), [`IWICBitmapSource`](https://learn.microsoft.com/en-us/windows/win32/api/wincodec/nn-wincodec-iwicbitmapsource) (including its subclasses declared by Windows Imaging Component) | #if SWT_NO_IMAGE_ATTACHMENTS @_unavailableInEmbedded @available(*, unavailable, message: "Image attachments are not available on this platform.") diff --git a/Sources/Testing/Testing.docc/Attachments.md b/Sources/Testing/Testing.docc/Attachments.md index b84a50e13..a2fe2f42b 100644 --- a/Sources/Testing/Testing.docc/Attachments.md +++ b/Sources/Testing/Testing.docc/Attachments.md @@ -17,6 +17,174 @@ Attach values to tests to help diagnose issues and gather feedback. Attach values such as strings and files to tests. Implement the ``Attachable`` protocol to create your own attachable types. +### Attach data or strings + +If your test produces encoded data that you want to save as an attachment, you +can call ``Attachment/record(_:named:sourceLocation:)``: + +```swift +struct SalesReport { ... } + +@Test func `sales report adds up`() async throws { + let salesReport = await generateSalesReport() + try salesReport.validate() + let bytes: [UInt8] = try salesReport.convertToCSV() + Attachment.record(bytes, named: "sales report.csv") +} +``` + +You can attach an instance of [`Array`](https://developer.apple.com/documentation/swift/array), +[`ContiguousArray`](https://developer.apple.com/documentation/swift/contiguousarray), +[`ArraySlice`](https://developer.apple.com/documentation/swift/arrayslice), +or [`Data`](https://developer.apple.com/documentation/foundation/data) as +these types automatically conform to ``Attachable``. + +You can also attach an instance of [`String`](https://developer.apple.com/documentation/swift/string) +or [`Substring`](https://developer.apple.com/documentation/swift/substring). The +testing library assumes that it should treat attached strings as UTF-8 text +files. If you want to save a string as an attachment using a different encoding, +convert it to [`Data`](https://developer.apple.com/documentation/foundation/data) +using [`data(using:allowLossyConversion:)`](https://developer.apple.com/documentation/swift/stringprotocol/data(using:allowlossyconversion:)) +and attach the resulting data instead of the original string. + +### Attach encodable values + +If you have a value you want to save as an attachment that conforms to either +[`Encodable`](https://developer.apple.com/documentation/swift/encodable) or +[`NSSecureCoding`](https://developer.apple.com/documentation/foundation/nssecurecoding), +you can extend it to add conformance to ``Attachable``. When you import the +[Foundation](https://developer.apple.com/documentation/foundation) module, the +testing library automatically provides a default implementation of +``Attachable`` to types that also conform to [`Encodable`](https://developer.apple.com/documentation/swift/encodable) +or [`NSSecureCoding`](https://developer.apple.com/documentation/foundation/nssecurecoding): + +```swift +import Testing +import Foundation + +struct SalesReport { ... } +extension SalesReport: Encodable, Attachable {} + +@Test func `sales report adds up`() async throws { + let salesReport = await generateSalesReport() + try salesReport.validate() + Attachment.record(salesReport, named: "sales report.json") +} +``` + +- Important: The testing library only provides these default implementations if + your test target imports the [Foundation](https://developer.apple.com/documentation/foundation) + module. + +### Attach images + +You can attach instances of the following system-provided image types to a test: + +| Platform | Supported Types | +|-|-| +| macOS | [`CGImage`](https://developer.apple.com/documentation/coregraphics/cgimage), [`CIImage`](https://developer.apple.com/documentation/coreimage/ciimage), [`NSImage`](https://developer.apple.com/documentation/appkit/nsimage) | +| iOS, watchOS, tvOS, and visionOS | [`CGImage`](https://developer.apple.com/documentation/coregraphics/cgimage), [`CIImage`](https://developer.apple.com/documentation/coreimage/ciimage), [`UIImage`](https://developer.apple.com/documentation/uikit/uiimage) | +| Windows | [`HBITMAP`](https://learn.microsoft.com/en-us/windows/win32/gdi/bitmaps), [`HICON`](https://learn.microsoft.com/en-us/windows/win32/menurc/icons), [`IWICBitmapSource`](https://learn.microsoft.com/en-us/windows/win32/api/wincodec/nn-wincodec-iwicbitmapsource) (including its subclasses declared by Windows Imaging Component) | + +When you attach an image to a test, you can specify the image format to use in +addition to a preferred name: + +```swift +struct SalesReport { ... } + +@Test func `sales report adds up`() async throws { + let salesReport = await generateSalesReport() + let image = try salesReport.renderTrendsGraph() + Attachment.record(image, named: "sales report", as: .png) +} +``` + +If you don't specify an image format when attaching an image to a test, the +testing library infers which format to use based on the preferred name you pass. + +### Attach other values + +If you have a value that needs a custom encoded representation when you save it +as an attachment, implement ``Attachable/withUnsafeBytes(for:_:)``. The +implementation of this function calls its `body` argument and passes the encoded +representation of `self` or, if a failure occurs, throws an error representing +that failure: + +```swift +struct SalesReport { ... } + +extension SalesReport: Attachable { + borrowing func withUnsafeBytes( + for attachment: borrowing Attachment, + _ body: (UnsafeRawBufferPointer) throws -> R + ) throws -> R { + let bytes = try salesReport.convertToCSV() // might fail to convert to CSV + try bytes.withUnsafeBytes { buffer in // rethrows any error from `body` + try body(buffer) + } + } +} +``` + +If your type conforms to [`Sendable`](https://developer.apple.com/documentation/swift/sendable), +the testing library avoids calling this function until it needs to save the +attachment. If your type does _not_ conform to [`Sendable`](https://developer.apple.com/documentation/swift/sendable), +the testing library calls this function as soon as you record the attachment. + +#### Customize attachment behavior + +If you can reliably estimate in advance how large the encoded representation +will be, implement ``Attachable/estimatedAttachmentByteCount``. The testing +library uses the value of this property as a hint to optimize memory and disk +usage: + +```swift +extension SalesReport: Attachable { + ... + + var estimatedAttachmentByteCount: Int? { + return self.entries.count * 123 + } +} +``` + +You can also implement ``Attachable/preferredName(for:basedOn:)`` if you wish to +customize the name of the attachment when it is saved: + +```swift +extension SalesReport: Attachable { + ... + + borrowing func preferredName( + for attachment: borrowing Attachment, + basedOn suggestedName: String + ) -> String { + if suggestedName.lastIndex(of: ".") != nil { + // The name already contains a path extension, so do not append another. + return suggestedName + } + + // Append ".csv" to the name so the resulting file opens as a spreadsheet. + return "\(suggestedName).csv" + } +} +``` + +### Inspect attachments after a test run ends + +By default, the testing library saves your attachments as soon as you call +``Attachment/record(_:sourceLocation:)`` or +``Attachment/record(_:named:sourceLocation:)``. You can access saved attachments +after your tests finish running: + +- When using Xcode, you can access attachments from the test report. +- When using Visual Studio Code, the testing library saves attachments to + `.build/attachments` by default. Visual Studio Code reports the paths to + individual attachments in its Tests Results panel. +- When using Swift Package Manager's `swift test` command, you can pass the + `--attachments-path` option. The testing library saves attachments to the + specified directory. + ## Topics ### Attaching values to tests diff --git a/Sources/Testing/Traits/AttachmentSavingTrait.swift b/Sources/Testing/Traits/AttachmentSavingTrait.swift index eed8085d5..b53e1ef2e 100644 --- a/Sources/Testing/Traits/AttachmentSavingTrait.swift +++ b/Sources/Testing/Traits/AttachmentSavingTrait.swift @@ -15,18 +15,6 @@ /// /// - ``Trait/savingAttachments(if:)`` /// -/// By default, the testing library saves your attachments as soon as you call -/// ``Attachment/record(_:named:sourceLocation:)``. You can access saved -/// attachments after your tests finish running: -/// -/// - When using Xcode, you can access attachments from the test report. -/// - When using Visual Studio Code, the testing library saves attachments to -/// `.build/attachments` by default. Visual Studio Code reports the paths to -/// individual attachments in its Tests Results panel. -/// - When using Swift Package Manager's `swift test` command, you can pass the -/// `--attachments-path` option. The testing library saves attachments to the -/// specified directory. -/// /// If you add an instance of this trait type to a test, any attachments that /// test records are stored in memory until the test finishes running. The /// testing library then evaluates the instance's condition and, if the @@ -228,18 +216,6 @@ extension Trait where Self == AttachmentSavingTrait { /// - Returns: An instance of ``AttachmentSavingTrait`` that evaluates the /// closure you provide. /// - /// By default, the testing library saves your attachments as soon as you call - /// ``Attachment/record(_:named:sourceLocation:)``. You can access saved - /// attachments after your tests finish running: - /// - /// - When using Xcode, you can access attachments from the test report. - /// - When using Visual Studio Code, the testing library saves attachments to - /// `.build/attachments` by default. Visual Studio Code reports the paths to - /// individual attachments in its Tests Results panel. - /// - When using Swift Package Manager's `swift test` command, you can pass - /// the `--attachments-path` option. The testing library saves attachments - /// to the specified directory. - /// /// If you add this trait to a test, any attachments that test records are /// stored in memory until the test finishes running. The testing library then /// evaluates `condition` and, if the condition is met, saves the attachments. @@ -266,18 +242,6 @@ extension Trait where Self == AttachmentSavingTrait { /// - Returns: An instance of ``AttachmentSavingTrait`` that evaluates the /// closure you provide. /// - /// By default, the testing library saves your attachments as soon as you call - /// ``Attachment/record(_:named:sourceLocation:)``. You can access saved - /// attachments after your tests finish running: - /// - /// - When using Xcode, you can access attachments from the test report. - /// - When using Visual Studio Code, the testing library saves attachments - ///  to `.build/attachments` by default. Visual Studio Code reports the paths - ///  to individual attachments in its Tests Results panel. - /// - When using Swift Package Manager's `swift test` command, you can pass - /// the `--attachments-path` option. The testing library saves attachments - /// to the specified directory. - /// /// If you add this trait to a test, any attachments that test records are /// stored in memory until the test finishes running. The testing library then /// evaluates `condition` and, if the condition is met, saves the attachments. @@ -305,18 +269,6 @@ extension Trait where Self == AttachmentSavingTrait { /// - Returns: An instance of ``AttachmentSavingTrait`` that evaluates the /// closure you provide. /// - /// By default, the testing library saves your attachments as soon as you call - /// ``Attachment/record(_:named:sourceLocation:)``. You can access saved - /// attachments after your tests finish running: - /// - /// - When using Xcode, you can access attachments from the test report. - /// - When using Visual Studio Code, the testing library saves attachments - ///  to `.build/attachments` by default. Visual Studio Code reports the paths - ///  to individual attachments in its Tests Results panel. - /// - When using Swift Package Manager's `swift test` command, you can pass - /// the `--attachments-path` option. The testing library saves attachments - /// to the specified directory. - /// /// If you add this trait to a test, any attachments that test records are /// stored in memory until the test finishes running. The testing library then /// evaluates `condition` and, if the condition is met, saves the attachments. From 993612a04745be84a2c18d92522c79da969ecd82 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Tue, 11 Nov 2025 14:14:47 -0500 Subject: [PATCH 2/2] Split out watchOS --- Sources/Testing/Testing.docc/Attachments.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/Testing/Testing.docc/Attachments.md b/Sources/Testing/Testing.docc/Attachments.md index a2fe2f42b..8aca99767 100644 --- a/Sources/Testing/Testing.docc/Attachments.md +++ b/Sources/Testing/Testing.docc/Attachments.md @@ -83,7 +83,8 @@ You can attach instances of the following system-provided image types to a test: | Platform | Supported Types | |-|-| | macOS | [`CGImage`](https://developer.apple.com/documentation/coregraphics/cgimage), [`CIImage`](https://developer.apple.com/documentation/coreimage/ciimage), [`NSImage`](https://developer.apple.com/documentation/appkit/nsimage) | -| iOS, watchOS, tvOS, and visionOS | [`CGImage`](https://developer.apple.com/documentation/coregraphics/cgimage), [`CIImage`](https://developer.apple.com/documentation/coreimage/ciimage), [`UIImage`](https://developer.apple.com/documentation/uikit/uiimage) | +| iOS, tvOS, and visionOS | [`CGImage`](https://developer.apple.com/documentation/coregraphics/cgimage), [`CIImage`](https://developer.apple.com/documentation/coreimage/ciimage), [`UIImage`](https://developer.apple.com/documentation/uikit/uiimage) | +| watchOS | [`CGImage`](https://developer.apple.com/documentation/coregraphics/cgimage), [`UIImage`](https://developer.apple.com/documentation/uikit/uiimage) | | Windows | [`HBITMAP`](https://learn.microsoft.com/en-us/windows/win32/gdi/bitmaps), [`HICON`](https://learn.microsoft.com/en-us/windows/win32/menurc/icons), [`IWICBitmapSource`](https://learn.microsoft.com/en-us/windows/win32/api/wincodec/nn-wincodec-iwicbitmapsource) (including its subclasses declared by Windows Imaging Component) | When you attach an image to a test, you can specify the image format to use in