Skip to content
Open
Changes from all 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
166 changes: 166 additions & 0 deletions proposals/testing/NNNN-image-attachment-adjustments.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# Adjustments to image attachments in Swift Testing

* Proposal: [ST-NNNN](NNNN-image-attachment-adjustments.md)
* Authors: [Jonathan Grynspan](https://github.com/grynspan)
* Review Manager: TBD
* Status: **Awaiting review**
* Implementation: [swiftlang/swift-testing#1359](https://github.com/swiftlang/swift-testing/pull/1359)
* Review: ([pitch](https://forums.swift.org/...))

## Introduction

This proposal includes a small number of adjustments to the API surface of Swift
Testing's image attachments feature introduced in [ST-0014](0014-image-attachments-in-swift-testing-apple-platforms.md)
and [ST-0015](0015-image-attachments-in-swift-testing-windows.md).

## Motivation

These changes will help to align the platform-specific interfaces of the feature
more closely.

## Proposed solution

The `AttachableAsCGImage` and `AttachableAsIWICBitmapSource` protocols are
combined into a single protocol, `AttachableAsImage` with adjusted protocol
requirements; a change is made to `AttachableImageFormat` to more closely
align its interface between Darwin and Windows; `AttachableImageFormat` is made
to conform to `Equatable` and `Hashable`; and an additional property is added to
`Attachment` to query its image format.

## Detailed design

The following changes are proposed:

### Combining AttachableAsCGImage and AttachableAsIWICBitmapSource

The `AttachableAsCGImage` and `AttachableAsIWICBitmapSource` protocols are
combined into a single protocol, `AttachableAsImage`.

These platform-specific requirements are removed:

```diff
- var attachableCGImage: CGImage { get throws }
- func copyAttachableIWICBitmapSource() throws -> UnsafeMutablePointer<IWICBitmapSource>
```

They are replaced with a new requirement that encapsulates the image encoding
operation. This requirement is implemented by the CoreGraphics and WinSDK
overlays and is made publicly available for test authors who wish to declare
additional conformances to this protocol for types that are not based on
`CGImage` or `IWICBitmapSource`:

```swift
public protocol AttachableAsImage {
// ...

/// Encode a representation of this image in a given image format.
///
/// - Parameters:
/// - imageFormat: The image format to use when encoding this image.
/// - body: A function to call. A temporary buffer containing a data
/// representation of this instance is passed to it.
///
/// - Returns: Whatever is returned by `body`.
///
/// - Throws: Whatever is thrown by `body`, or any error that prevented the
/// creation of the buffer.
///
/// The testing library uses this function when saving an image as an
/// attachment. The implementation should use `imageFormat` to determine what
/// encoder to use.
borrowing func withUnsafeBytes<R>(as imageFormat: AttachableImageFormat, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R
}
```

If a developer has an image type that should conform to `AttachableAsImage` and
wraps an instance of `CGImage` or `IWICBitmapSource`, it is straightforward for
them to delegate to that object. For example:

```swift
import Testing
import CoreGraphics

struct MyImage {
var cgImage: CGImage
// ...
}

extension MyImage: AttachableAsImage {
func withUnsafeBytes<R>(as imageFormat: AttachableImageFormat, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
try cgImage.withUnsafeBytes(as: imageFormat, body)
}
}
```

### Adjusting AttachableImageFormat

The following Apple-specific `AttachableImageFormat` initializer is renamed so
that its first argument has an explicit label:

```diff
public struct AttachableImageFormat {
// ...
- public init(_ contentType: UTType, encodingQuality: Float = 1.0)
+ public init(contentType: UTType, encodingQuality: Float = 1.0)
}
```

This change makes the type's interface more consistent between Darwin and
Windows (where it has an `init(encoderCLSID:encodingQuality:)` initializer.)

As well, conformances to `Equatable`, `Hashable`, `CustomStringConvertible`, and
`CustomDebugStringConvertible` are added:

```swift
extension AttachableImageFormat: Equatable, Hashable {}
extension AttachableImageFormat: CustomStringConvertible, CustomDebugStringConvertible {}
```

Conformance to `Equatable` is necessary to correctly implement the
`withUnsafeBytes(as:_:)` protocol requirement mentioned above, and conformance
to `Hashable` is generally useful and straightforward to implement. Conformance
to `CustomStringConvertible` and `CustomDebugStringConvertible` allows for
better diagnostic output (especially if an encoding failure occurs.)

### Adding an imageFormat property to Attachment

The following property is added to `Attachment` when the attachable value is an
image:

```swift
extension Attachment where AttachableValue: AttachableWrapper,
AttachableValue.Wrapped: AttachableAsImage {
/// The image format to use when encoding the represented image.
public var imageFormat: AttachableImageFormat? { get }
}
```

## Source compatibility

These changes are breaking for anyone who has created a type that conforms to
either `AttachableAsCGImage` or `AttachableAsIWICBitmapSource`, or anyone who
has adopted `AttachableImageFormat.init(_:encodingQuality:)`.

This feature is new in Swift 6.3 and has not shipped to developers outside of
nightly toolchain builds. As such, we feel confident that any real-world impact
to developers will be both minimal and manageable.

## Integration with supporting tools

No changes.

## Future directions

N/A

## Alternatives considered

- Leaving the two protocols separate. Combining them allows us to lower more
code into the main Swift Testing library and improves our ability to generate
DocC documentation, while also simplifying the story for developers who want
to use this feature across platforms.

## Acknowledgments

Thanks to my colleagues for their feedback on the image attachments feature and
to the Swift community for putting up with the churn!