Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

public import UniformTypeIdentifiers

@_spi(Experimental)
@available(_uttypesAPI, *)
extension Attachment {
/// Initialize an instance of this type that encloses the given image.
///
Expand All @@ -23,46 +25,9 @@ extension Attachment {
/// to a test report or to disk. If `nil`, the testing library attempts
/// to derive a reasonable filename for the attached value.
/// - contentType: The image format with which to encode `attachableValue`.
/// If this type does not conform to [`UTType.image`](https://developer.apple.com/documentation/uniformtypeidentifiers/uttype-swift.struct/image),
/// the result is undefined. Pass `nil` to let the testing library decide
/// which image format to use.
/// - encodingQuality: The encoding quality to use when encoding the image.
/// If the image format used for encoding (specified by the `contentType`
/// argument) does not support variable-quality encoding, the value of
/// this argument is ignored.
/// - sourceLocation: The source location of the call to this initializer.
/// This value is used when recording issues associated with the
/// attachment.
///
/// This is the designated initializer for this type when attaching an image
/// that conforms to ``AttachableAsCGImage``.
fileprivate init<T>(
attachableValue: T,
named preferredName: String?,
contentType: (any Sendable)?,
encodingQuality: Float,
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.
///
/// - Parameters:
/// - attachableValue: The value that will be attached to the output of
/// the test run.
/// - preferredName: The preferred name of the attachment when writing it
/// to a test report or to disk. If `nil`, the testing library attempts
/// to derive a reasonable filename for the attached value.
/// - contentType: The image format with which to encode `attachableValue`.
/// If this type does not conform to [`UTType.image`](https://developer.apple.com/documentation/uniformtypeidentifiers/uttype-swift.struct/image),
/// the result is undefined. Pass `nil` to let the testing library decide
/// which image format to use.
/// - encodingQuality: The encoding quality to use when encoding the image.
/// If the image format used for encoding (specified by the `contentType`
/// argument) does not support variable-quality encoding, the value of
/// this argument is ignored.
/// For the lowest supported quality, pass `0.0`. For the highest
/// supported quality, pass `1.0`.
/// - sourceLocation: The source location of the call to this initializer.
/// This value is used when recording issues associated with the
/// attachment.
Expand All @@ -71,46 +36,72 @@ extension Attachment {
/// ``AttachableAsCGImage`` protocol and can be attached to a test:
///
/// - [`CGImage`](https://developer.apple.com/documentation/coregraphics/cgimage)
@_spi(Experimental)
@available(_uttypesAPI, *)
///
/// The testing library uses the image format specified by `contentType`. 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.
///
/// If the target image format does not support variable-quality encoding,
/// the value of the `encodingQuality` argument is ignored. If `contentType`
/// is not `nil` and does not conform to [`UTType.image`](https://developer.apple.com/documentation/uniformtypeidentifiers/uttype-swift.struct/image),
/// the result is undefined.
public init<T>(
_ attachableValue: T,
named preferredName: String? = nil,
as contentType: UTType?,
as contentType: UTType? = nil,
encodingQuality: Float = 1.0,
sourceLocation: SourceLocation = #_sourceLocation
) where AttachableValue == _AttachableImageWrapper<T> {
self.init(attachableValue: attachableValue, named: preferredName, contentType: contentType, encodingQuality: encodingQuality, sourceLocation: sourceLocation)
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.
/// Attach an image to the current test.
///
/// - Parameters:
/// - attachableValue: The value that will be attached to the output of
/// the test run.
/// - preferredName: The preferred name of the attachment when writing it
/// to a test report or to disk. If `nil`, the testing library attempts
/// to derive a reasonable filename for the attached value.
/// - image: The value to attach.
/// - preferredName: The preferred name of the attachment when writing it to
/// a test report or to disk. If `nil`, the testing library attempts to
/// derive a reasonable filename for the attached value.
/// - contentType: The image format with which to encode `attachableValue`.
/// - encodingQuality: The encoding quality to use when encoding the image.
/// If the image format used for encoding (specified by the `contentType`
/// argument) does not support variable-quality encoding, the value of
/// this argument is ignored.
/// - sourceLocation: The source location of the call to this initializer.
/// This value is used when recording issues associated with the
/// attachment.
/// For the lowest supported quality, pass `0.0`. For the highest
/// supported quality, pass `1.0`.
/// - 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.
///
/// The following system-provided image types conform to the
/// ``AttachableAsCGImage`` protocol and can be attached to a test:
///
/// - [`CGImage`](https://developer.apple.com/documentation/coregraphics/cgimage)
@_spi(Experimental)
public init<T>(
_ attachableValue: T,
///
/// The testing library uses the image format specified by `contentType`. 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.
///
/// If the target image format does not support variable-quality encoding,
/// the value of the `encodingQuality` argument is ignored. If `contentType`
/// is not `nil` and does not conform to [`UTType.image`](https://developer.apple.com/documentation/uniformtypeidentifiers/uttype-swift.struct/image),
/// the result is undefined.
public static func record<T>(
_ image: consuming T,
named preferredName: String? = nil,
as contentType: UTType? = nil,
encodingQuality: Float = 1.0,
sourceLocation: SourceLocation = #_sourceLocation
) where AttachableValue == _AttachableImageWrapper<T> {
self.init(attachableValue: attachableValue, named: preferredName, contentType: nil, encodingQuality: encodingQuality, sourceLocation: sourceLocation)
let attachment = Self(image, named: preferredName, as: contentType, encodingQuality: encodingQuality, sourceLocation: sourceLocation)
Self.record(attachment, sourceLocation: sourceLocation)
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import UniformTypeIdentifiers
///
/// - [`CGImage`](https://developer.apple.com/documentation/coregraphics/cgimage)
@_spi(Experimental)
@available(_uttypesAPI, *)
public struct _AttachableImageWrapper<Image>: Sendable where Image: AttachableAsCGImage {
/// The underlying image.
///
Expand All @@ -61,7 +62,7 @@ public struct _AttachableImageWrapper<Image>: Sendable where Image: AttachableAs
var encodingQuality: Float

/// Storage for ``contentType``.
private var _contentType: (any Sendable)?
private var _contentType: UTType?

/// The content type to use when encoding the image.
///
Expand All @@ -70,14 +71,9 @@ public struct _AttachableImageWrapper<Image>: Sendable where Image: AttachableAs
///
/// If the value of this property does not conform to [`UTType.image`](https://developer.apple.com/documentation/uniformtypeidentifiers/uttype-swift.struct/image),
/// the result is undefined.
@available(_uttypesAPI, *)
var contentType: UTType {
get {
if let contentType = _contentType as? UTType {
return contentType
} else {
return encodingQuality < 1.0 ? .jpeg : .png
}
_contentType ?? (encodingQuality < 1.0 ? .jpeg : .png)
}
set {
precondition(
Expand All @@ -92,9 +88,8 @@ public struct _AttachableImageWrapper<Image>: Sendable where Image: AttachableAs
/// type for `UTType.image`.
///
/// This property is not part of the public interface of the testing library.
@available(_uttypesAPI, *)
var computedContentType: UTType {
if let contentType = _contentType as? UTType, contentType != .image {
if let contentType = _contentType, contentType != .image {
contentType
} else {
encodingQuality < 1.0 ? .jpeg : .png
Expand All @@ -109,24 +104,21 @@ public struct _AttachableImageWrapper<Image>: Sendable where Image: AttachableAs
/// This property is not part of the public interface of the testing library.
/// It is used by ImageIO below.
var typeIdentifier: CFString {
if #available(_uttypesAPI, *) {
computedContentType.identifier as CFString
} else {
encodingQuality < 1.0 ? kUTTypeJPEG : kUTTypePNG
}
computedContentType.identifier as CFString
}

init(image: Image, encodingQuality: Float, contentType: (any Sendable)?) {
init(image: Image, encodingQuality: Float, contentType: UTType?) {
self.image = image._makeCopyForAttachment()
self.encodingQuality = encodingQuality
if #available(_uttypesAPI, *), let contentType = contentType as? UTType {
if let contentType {
self.contentType = contentType
}
}
}

// MARK: -

@available(_uttypesAPI, *)
extension _AttachableImageWrapper: AttachableWrapper {
public var wrappedValue: Image {
image
Expand Down Expand Up @@ -168,11 +160,7 @@ extension _AttachableImageWrapper: AttachableWrapper {
}

public borrowing func preferredName(for attachment: borrowing Attachment<Self>, basedOn suggestedName: String) -> String {
if #available(_uttypesAPI, *) {
return (suggestedName as NSString).appendingPathExtension(for: computedContentType)
}

return suggestedName
(suggestedName as NSString).appendingPathExtension(for: computedContentType)
}
}
#endif
17 changes: 17 additions & 0 deletions Tests/TestingTests/AttachmentTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,23 @@ extension AttachmentTests {
Attachment.record(attachment)
}

@available(_uttypesAPI, *)
@Test func attachCGImageDirectly() async throws {
await confirmation("Attachment detected") { valueAttached in
var configuration = Configuration()
configuration.eventHandler = { event, _ in
if case .valueAttached = event.kind {
valueAttached()
}
}

await Test {
let image = try Self.cgImage.get()
Attachment.record(image, named: "diamond.jpg")
}.run(configuration: configuration)
}
}

@available(_uttypesAPI, *)
@Test(arguments: [Float(0.0).nextUp, 0.25, 0.5, 0.75, 1.0], [.png as UTType?, .jpeg, .gif, .image, nil])
func attachCGImage(quality: Float, type: UTType?) throws {
Expand Down