Skip to content

Commit 81028d7

Browse files
committed
Allow non-optional metadata, adopt in CGImage overlay
1 parent d04dfc6 commit 81028d7

File tree

11 files changed

+305
-202
lines changed

11 files changed

+305
-202
lines changed

Sources/Overlays/_Testing_CoreGraphics/Attachments/Attachment+AttachableAsCGImage.swift

Lines changed: 26 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -13,40 +13,11 @@
1313

1414
public import UniformTypeIdentifiers
1515

16-
extension Attachment {
17-
/// Initialize an instance of this type that encloses the given image.
18-
///
19-
/// - Parameters:
20-
/// - attachableValue: The value that will be attached to the output of
21-
/// the test run.
22-
/// - preferredName: The preferred name of the attachment when writing it
23-
/// to a test report or to disk. If `nil`, the testing library attempts
24-
/// to derive a reasonable filename for the attached value.
25-
/// - contentType: The image format with which to encode `attachableValue`.
26-
/// If this type does not conform to [`UTType.image`](https://developer.apple.com/documentation/uniformtypeidentifiers/uttype-swift.struct/image),
27-
/// the result is undefined. Pass `nil` to let the testing library decide
28-
/// which image format to use.
29-
/// - encodingQuality: The encoding quality to use when encoding the image.
30-
/// If the image format used for encoding (specified by the `contentType`
31-
/// argument) does not support variable-quality encoding, the value of
32-
/// this argument is ignored.
33-
/// - sourceLocation: The source location of the call to this initializer.
34-
/// This value is used when recording issues associated with the
35-
/// attachment.
36-
///
37-
/// This is the designated initializer for this type when attaching an image
38-
/// that conforms to ``AttachableAsCGImage``.
39-
fileprivate init<T>(
40-
attachableValue: T,
41-
named preferredName: String?,
42-
contentType: (any Sendable)?,
43-
encodingQuality: Float,
44-
sourceLocation: SourceLocation
45-
) where AttachableValue == _AttachableImageWrapper<T> {
46-
let imageWrapper = _AttachableImageWrapper(image: attachableValue, encodingQuality: encodingQuality, contentType: contentType)
47-
self.init(imageWrapper, named: preferredName, sourceLocation: sourceLocation)
48-
}
16+
#if canImport(CoreServices_Private)
17+
private import CoreServices_Private
18+
#endif
4919

20+
extension Attachment {
5021
/// Initialize an instance of this type that encloses the given image.
5122
///
5223
/// - Parameters:
@@ -55,14 +26,9 @@ extension Attachment {
5526
/// - preferredName: The preferred name of the attachment when writing it
5627
/// to a test report or to disk. If `nil`, the testing library attempts
5728
/// to derive a reasonable filename for the attached value.
58-
/// - contentType: The image format with which to encode `attachableValue`.
59-
/// If this type does not conform to [`UTType.image`](https://developer.apple.com/documentation/uniformtypeidentifiers/uttype-swift.struct/image),
60-
/// the result is undefined. Pass `nil` to let the testing library decide
61-
/// which image format to use.
62-
/// - encodingQuality: The encoding quality to use when encoding the image.
63-
/// If the image format used for encoding (specified by the `contentType`
64-
/// argument) does not support variable-quality encoding, the value of
65-
/// this argument is ignored.
29+
/// - metadata: Optional metadata such as the image format to use when
30+
/// encoding `image`. If `nil`, the testing library will infer the format
31+
/// and other metadata.
6632
/// - sourceLocation: The source location of the call to this initializer.
6733
/// This value is used when recording issues associated with the
6834
/// attachment.
@@ -72,45 +38,30 @@ extension Attachment {
7238
///
7339
/// - [`CGImage`](https://developer.apple.com/documentation/coregraphics/cgimage)
7440
@_spi(Experimental)
75-
@available(_uttypesAPI, *)
7641
public init<T>(
7742
_ attachableValue: T,
78-
named preferredName: String? = nil,
79-
as contentType: UTType?,
80-
encodingQuality: Float = 1.0,
43+
named preferredName: String?,
44+
metadata: ImageAttachmentMetadata? = nil,
8145
sourceLocation: SourceLocation = #_sourceLocation
8246
) where AttachableValue == _AttachableImageWrapper<T> {
83-
self.init(attachableValue: attachableValue, named: preferredName, contentType: contentType, encodingQuality: encodingQuality, sourceLocation: sourceLocation)
84-
}
47+
var preferredName = preferredName ?? Self.defaultPreferredName
48+
var metadata = metadata ?? ImageAttachmentMetadata()
8549

86-
/// Initialize an instance of this type that encloses the given image.
87-
///
88-
/// - Parameters:
89-
/// - attachableValue: The value that will be attached to the output of
90-
/// the test run.
91-
/// - preferredName: The preferred name of the attachment when writing it
92-
/// to a test report or to disk. If `nil`, the testing library attempts
93-
/// to derive a reasonable filename for the attached value.
94-
/// - encodingQuality: The encoding quality to use when encoding the image.
95-
/// If the image format used for encoding (specified by the `contentType`
96-
/// argument) does not support variable-quality encoding, the value of
97-
/// this argument is ignored.
98-
/// - sourceLocation: The source location of the call to this initializer.
99-
/// This value is used when recording issues associated with the
100-
/// attachment.
101-
///
102-
/// The following system-provided image types conform to the
103-
/// ``AttachableAsCGImage`` protocol and can be attached to a test:
104-
///
105-
/// - [`CGImage`](https://developer.apple.com/documentation/coregraphics/cgimage)
106-
@_spi(Experimental)
107-
public init<T>(
108-
_ attachableValue: T,
109-
named preferredName: String? = nil,
110-
encodingQuality: Float = 1.0,
111-
sourceLocation: SourceLocation = #_sourceLocation
112-
) where AttachableValue == _AttachableImageWrapper<T> {
113-
self.init(attachableValue: attachableValue, named: preferredName, contentType: nil, encodingQuality: encodingQuality, sourceLocation: sourceLocation)
50+
// Update the preferred name to include an extension appropriate for the
51+
// given content type. (Note the `else` branch duplicates the logic in
52+
// `preferredContentType(forEncodingQuality:)` but will go away once our
53+
// minimum deployment targets include the UniformTypeIdentifiers framework.)
54+
if #available(_uttypesAPI, *) {
55+
preferredName = (preferredName as NSString).appendingPathExtension(for: metadata.contentType)
56+
} else {
57+
#if canImport(CoreServices_Private)
58+
// The caller can't provide a content type, so we'll pick one for them.
59+
preferredName = _UTTypeCreateSuggestedFilename(preferredName as CFString, metadata.typeIdentifier)?.takeRetainedValue() ?? preferredName
60+
#endif
61+
}
62+
63+
let imageContainer = _AttachableImageWrapper(attachableValue)
64+
self.init(imageContainer, named: preferredName, metadata: metadata, sourceLocation: sourceLocation)
11465
}
11566
}
11667
#endif
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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+
#if SWT_TARGET_OS_APPLE && canImport(CoreGraphics)
12+
@_spi(Experimental) public import Testing
13+
private import CoreGraphics
14+
15+
public import UniformTypeIdentifiers
16+
17+
/// A type defining metadata used when attaching an image to a test.
18+
///
19+
/// The following system-provided image types can be attached to a test:
20+
///
21+
/// - [`CGImage`](https://developer.apple.com/documentation/coregraphics/cgimage)
22+
@_spi(Experimental)
23+
public struct ImageAttachmentMetadata: Sendable {
24+
/// The encoding quality to use when encoding the represented image.
25+
///
26+
/// If the image format used for encoding (specified by the ``contentType``
27+
/// property) does not support variable-quality encoding, the value of this
28+
/// property is ignored.
29+
public var encodingQuality: Float
30+
31+
/// Storage for ``contentType``.
32+
private var _contentType: (any Sendable)?
33+
34+
/// The content type to use when encoding the image.
35+
///
36+
/// The testing library uses this property to determine which image format to
37+
/// encode the associated image as when it is attached to a test.
38+
///
39+
/// If the value of this property does not conform to [`UTType.image`](https://developer.apple.com/documentation/uniformtypeidentifiers/uttype-swift.struct/image),
40+
/// the result is undefined.
41+
@available(_uttypesAPI, *)
42+
var contentType: UTType {
43+
get {
44+
if let contentType = _contentType as? UTType {
45+
return contentType
46+
} else {
47+
return encodingQuality < 1.0 ? .jpeg : .png
48+
}
49+
}
50+
set {
51+
precondition(
52+
newValue.conforms(to: .image),
53+
"An image cannot be attached as an instance of type '\(newValue.identifier)'. Use a type that conforms to 'public.image' instead."
54+
)
55+
_contentType = newValue
56+
}
57+
}
58+
59+
/// The content type to use when encoding the image, substituting a concrete
60+
/// type for `UTType.image`.
61+
///
62+
/// This property is not part of the public interface of the testing library.
63+
@available(_uttypesAPI, *)
64+
var computedContentType: UTType {
65+
if let contentType = _contentType as? UTType, contentType != .image {
66+
contentType
67+
} else {
68+
encodingQuality < 1.0 ? .jpeg : .png
69+
}
70+
}
71+
72+
/// The type identifier (as a `CFString`) corresponding to this instance's
73+
/// ``computedContentType`` property.
74+
///
75+
/// The value of this property is used by ImageIO when serializing an image.
76+
///
77+
/// This property is not part of the public interface of the testing library.
78+
/// It is used by ImageIO below.
79+
var typeIdentifier: CFString {
80+
if #available(_uttypesAPI, *) {
81+
computedContentType.identifier as CFString
82+
} else {
83+
encodingQuality < 1.0 ? kUTTypeJPEG : kUTTypePNG
84+
}
85+
}
86+
87+
public init(encodingQuality: Float = 1.0) {
88+
self.encodingQuality = encodingQuality
89+
}
90+
91+
@available(_uttypesAPI, *)
92+
public init(encodingQuality: Float = 1.0, contentType: UTType) {
93+
self.encodingQuality = encodingQuality
94+
self.contentType = contentType
95+
}
96+
}
97+
#endif

Sources/Overlays/_Testing_CoreGraphics/Attachments/_AttachableImageWrapper.swift

Lines changed: 7 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,7 @@ import UniformTypeIdentifiers
2424
/// event handler (primarily because `Event` is `Sendable`.) So we would have
2525
/// to eagerly serialize them, which is unnecessarily expensive if we know
2626
/// they're actually concurrency-safe.
27-
/// 2. We would have no place to store metadata such as the encoding quality
28-
/// (although in the future we may introduce a "metadata" associated type to
29-
/// `Attachable` that could store that info.)
30-
/// 3. `Attachable` has a requirement with `Self` in non-parameter, non-return
27+
/// 2. `Attachable` has a requirement with `Self` in non-parameter, non-return
3128
/// position. As far as Swift is concerned, a non-final class cannot satisfy
3229
/// such a requirement, and all image types we care about are non-final
3330
/// classes. Thus, the compiler will steadfastly refuse to allow non-final
@@ -57,71 +54,8 @@ public struct _AttachableImageWrapper<Image>: Sendable where Image: AttachableAs
5754
/// instances of this type it creates hold "safe" `NSImage` instances.
5855
nonisolated(unsafe) var image: Image
5956

60-
/// The encoding quality to use when encoding the represented image.
61-
var encodingQuality: Float
62-
63-
/// Storage for ``contentType``.
64-
private var _contentType: (any Sendable)?
65-
66-
/// The content type to use when encoding the image.
67-
///
68-
/// The testing library uses this property to determine which image format to
69-
/// encode the associated image as when it is attached to a test.
70-
///
71-
/// If the value of this property does not conform to [`UTType.image`](https://developer.apple.com/documentation/uniformtypeidentifiers/uttype-swift.struct/image),
72-
/// the result is undefined.
73-
@available(_uttypesAPI, *)
74-
var contentType: UTType {
75-
get {
76-
if let contentType = _contentType as? UTType {
77-
return contentType
78-
} else {
79-
return encodingQuality < 1.0 ? .jpeg : .png
80-
}
81-
}
82-
set {
83-
precondition(
84-
newValue.conforms(to: .image),
85-
"An image cannot be attached as an instance of type '\(newValue.identifier)'. Use a type that conforms to 'public.image' instead."
86-
)
87-
_contentType = newValue
88-
}
89-
}
90-
91-
/// The content type to use when encoding the image, substituting a concrete
92-
/// type for `UTType.image`.
93-
///
94-
/// This property is not part of the public interface of the testing library.
95-
@available(_uttypesAPI, *)
96-
var computedContentType: UTType {
97-
if let contentType = _contentType as? UTType, contentType != .image {
98-
contentType
99-
} else {
100-
encodingQuality < 1.0 ? .jpeg : .png
101-
}
102-
}
103-
104-
/// The type identifier (as a `CFString`) corresponding to this instance's
105-
/// ``computedContentType`` property.
106-
///
107-
/// The value of this property is used by ImageIO when serializing an image.
108-
///
109-
/// This property is not part of the public interface of the testing library.
110-
/// It is used by ImageIO below.
111-
var typeIdentifier: CFString {
112-
if #available(_uttypesAPI, *) {
113-
computedContentType.identifier as CFString
114-
} else {
115-
encodingQuality < 1.0 ? kUTTypeJPEG : kUTTypePNG
116-
}
117-
}
118-
119-
init(image: Image, encodingQuality: Float, contentType: (any Sendable)?) {
57+
init(_ image: borrowing Image) {
12058
self.image = image._makeCopyForAttachment()
121-
self.encodingQuality = encodingQuality
122-
if #available(_uttypesAPI, *), let contentType = contentType as? UTType {
123-
self.contentType = contentType
124-
}
12559
}
12660
}
12761

@@ -132,22 +66,24 @@ extension _AttachableImageWrapper: AttachableWrapper {
13266
image
13367
}
13468

69+
public typealias AttachmentMetadata = ImageAttachmentMetadata
70+
13571
public func withUnsafeBytes<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
13672
let data = NSMutableData()
13773

13874
// Convert the image to a CGImage.
13975
let attachableCGImage = try image.attachableCGImage
14076

14177
// Create the image destination.
142-
guard let dest = CGImageDestinationCreateWithData(data as CFMutableData, typeIdentifier, 1, nil) else {
78+
guard let dest = CGImageDestinationCreateWithData(data as CFMutableData, attachment.metadata.typeIdentifier, 1, nil) else {
14379
throw ImageAttachmentError.couldNotCreateImageDestination
14480
}
14581

14682
// Configure the properties of the image conversion operation.
14783
let orientation = image._attachmentOrientation
14884
let scaleFactor = image._attachmentScaleFactor
14985
let properties: [CFString: Any] = [
150-
kCGImageDestinationLossyCompressionQuality: CGFloat(encodingQuality),
86+
kCGImageDestinationLossyCompressionQuality: CGFloat(attachment.metadata.encodingQuality),
15187
kCGImagePropertyOrientation: orientation,
15288
kCGImagePropertyDPIWidth: 72.0 * scaleFactor,
15389
kCGImagePropertyDPIHeight: 72.0 * scaleFactor,
@@ -169,7 +105,7 @@ extension _AttachableImageWrapper: AttachableWrapper {
169105

170106
public borrowing func preferredName(for attachment: borrowing Attachment<Self>, basedOn suggestedName: String) -> String {
171107
if #available(_uttypesAPI, *) {
172-
return (suggestedName as NSString).appendingPathExtension(for: computedContentType)
108+
return (suggestedName as NSString).appendingPathExtension(for: attachment.metadata.computedContentType)
173109
}
174110

175111
return suggestedName

Sources/Overlays/_Testing_Foundation/Attachments/Attachable+Encodable+NSSecureCoding.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@ public import Foundation
2121
/// @Metadata {
2222
/// @Available(Swift, introduced: 6.2)
2323
/// }
24-
extension Attachable where Self: Encodable & NSSecureCoding {
25-
public typealias AttachmentMetadata = EncodableAttachmentMetadata
26-
24+
extension Attachable where Self: Encodable & NSSecureCoding, AttachmentMetadata == EncodableAttachmentMetadata? {
2725
@_documentation(visibility: private)
2826
public func withUnsafeBytes<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
2927
try _Testing_Foundation.withUnsafeBytes(encoding: self, for: attachment, body)

0 commit comments

Comments
 (0)