-
Notifications
You must be signed in to change notification settings - Fork 120
Add support for attachments to the Foundation cross-import overlay. #799
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
e533273
Make `Test.Attachment` generic.
grynspan b9c95ef
Add accessors through withUnsafeBufferPointer since the type-erased a…
grynspan 764cc6a
Work around rdar://137614425 (again)
grynspan 0f5252e
Work around a compiler crash (again, yes)
grynspan 78770a2
Work around the compiler crash a second time
grynspan 06ab04f
Use a dedicated type to represent any attachable value rather than an…
grynspan a31910c
Nest AnyAttachable
grynspan 6862d64
Add AttachableContainer protocol for abstracting away box/adapter typ…
grynspan 9aec954
Update comments, make AttachableContainer always Copyable & Sendable
grynspan 173f077
Actually no, make Test.AttachableContainer optionally copyable/sendab…
grynspan b867258
Add support for attachments to the Foundation cross-import overlay.
grynspan 32c487a
Minor fixes
grynspan a810dbf
Fix rebase glitch
grynspan b334529
Remove nonfunctional reserved-file check in a test
grynspan afdf317
Consistently apply .tgz to the preferred name of a compressed directory
grynspan 18d52d5
Customize documentation for protocol conformances of Encodable and NS…
grynspan c52b02d
Make MyContiguousCollectionAttachable sendable
grynspan 3b68c32
Add default Attachable implementation for types that conform to BOTH …
grynspan 4443939
Remove ContiguousBytes implementation for limited utility
grynspan 3feb342
Fix a typo
grynspan 311c1c9
Remove ContiguousBytes test
grynspan 9ffc49a
Minor cleanup
grynspan bb22280
Work around compiler crash
grynspan 184774b
Work around another way?
grynspan 950fa6e
Update to use generic Test.Attachment
grynspan d039ea0
Don't introduce a new public type for file attachments
grynspan df272e7
Restore tests
grynspan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
21 changes: 21 additions & 0 deletions
21
Sources/Overlays/_Testing_Foundation/Attachments/Data+Test.Attachable.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// | ||
// This source file is part of the Swift.org open source project | ||
// | ||
// Copyright (c) 2024 Apple Inc. and the Swift project authors | ||
// Licensed under Apache License v2.0 with Runtime Library Exception | ||
// | ||
// See https://swift.org/LICENSE.txt for license information | ||
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors | ||
// | ||
|
||
#if canImport(Foundation) | ||
@_spi(Experimental) public import Testing | ||
public import Foundation | ||
|
||
@_spi(Experimental) | ||
extension Data: Test.Attachable { | ||
public func withUnsafeBufferPointer<R>(for attachment: borrowing Test.Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { | ||
try withUnsafeBytes(body) | ||
} | ||
} | ||
#endif |
84 changes: 84 additions & 0 deletions
84
Sources/Overlays/_Testing_Foundation/Attachments/EncodingFormat.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
// | ||
// This source file is part of the Swift.org open source project | ||
// | ||
// Copyright (c) 2024 Apple Inc. and the Swift project authors | ||
// Licensed under Apache License v2.0 with Runtime Library Exception | ||
// | ||
// See https://swift.org/LICENSE.txt for license information | ||
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors | ||
// | ||
|
||
#if canImport(Foundation) | ||
@_spi(Experimental) import Testing | ||
import Foundation | ||
|
||
#if SWT_TARGET_OS_APPLE && canImport(UniformTypeIdentifiers) | ||
private import UniformTypeIdentifiers | ||
#endif | ||
|
||
/// An enumeration describing the encoding formats we support for `Encodable` | ||
/// and `NSSecureCoding` types that conform to `Test.Attachable`. | ||
enum EncodingFormat { | ||
/// A property list format. | ||
/// | ||
/// - Parameters: | ||
/// - format: The corresponding property list format. | ||
case propertyListFormat(_ format: PropertyListSerialization.PropertyListFormat) | ||
|
||
/// The JSON format. | ||
case json | ||
|
||
/// The encoding format to use by default. | ||
/// | ||
/// The specific format this case corresponds to depends on if we are encoding | ||
/// an `Encodable` value or an `NSSecureCoding` value. | ||
case `default` | ||
|
||
/// Initialize an instance of this type representing the content type or media | ||
/// type of the specified attachment. | ||
/// | ||
/// - Parameters: | ||
/// - attachment: The attachment that will be encoded. | ||
/// | ||
/// - Throws: If the attachment's content type or media type is unsupported. | ||
init(for attachment: borrowing Test.Attachment<some Test.Attachable>) throws { | ||
let ext = (attachment.preferredName as NSString).pathExtension | ||
|
||
#if SWT_TARGET_OS_APPLE && canImport(UniformTypeIdentifiers) | ||
// If the caller explicitly wants to encode their data as either XML or as a | ||
// property list, use PropertyListEncoder. Otherwise, we'll fall back to | ||
// JSONEncoder below. | ||
if #available(_uttypesAPI, *), let contentType = UTType(filenameExtension: ext) { | ||
if contentType == .data { | ||
self = .default | ||
} else if contentType.conforms(to: .json) { | ||
self = .json | ||
} else if contentType.conforms(to: .xml) { | ||
self = .propertyListFormat(.xml) | ||
} else if contentType.conforms(to: .binaryPropertyList) || contentType == .propertyList { | ||
self = .propertyListFormat(.binary) | ||
} else if contentType.conforms(to: .propertyList) { | ||
self = .propertyListFormat(.openStep) | ||
} else { | ||
let contentTypeDescription = contentType.localizedDescription ?? contentType.identifier | ||
throw CocoaError(.propertyListWriteInvalid, userInfo: [NSLocalizedDescriptionKey: "The content type '\(contentTypeDescription)' cannot be used to attach an instance of \(type(of: self)) to a test."]) | ||
} | ||
return | ||
} | ||
#endif | ||
|
||
if ext.isEmpty { | ||
// No path extension? No problem! Default data. | ||
self = .default | ||
} else if ext.caseInsensitiveCompare("plist") == .orderedSame { | ||
self = .propertyListFormat(.binary) | ||
} else if ext.caseInsensitiveCompare("xml") == .orderedSame { | ||
self = .propertyListFormat(.xml) | ||
} else if ext.caseInsensitiveCompare("json") == .orderedSame { | ||
self = .json | ||
} else { | ||
throw CocoaError(.propertyListWriteInvalid, userInfo: [NSLocalizedDescriptionKey: "The path extension '.\(ext)' cannot be used to attach an instance of \(type(of: self)) to a test."]) | ||
} | ||
} | ||
} | ||
#endif |
22 changes: 22 additions & 0 deletions
22
...s/Overlays/_Testing_Foundation/Attachments/Test.Attachable+Encodable&NSSecureCoding.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// | ||
// This source file is part of the Swift.org open source project | ||
// | ||
// Copyright (c) 2024 Apple Inc. and the Swift project authors | ||
// Licensed under Apache License v2.0 with Runtime Library Exception | ||
// | ||
// See https://swift.org/LICENSE.txt for license information | ||
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors | ||
// | ||
|
||
#if canImport(Foundation) | ||
@_spi(Experimental) public import Testing | ||
public import Foundation | ||
|
||
@_spi(Experimental) | ||
extension Test.Attachable where Self: Encodable & NSSecureCoding { | ||
@_documentation(visibility: private) | ||
public func withUnsafeBufferPointer<R>(for attachment: borrowing Test.Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { | ||
try _Testing_Foundation.withUnsafeBufferPointer(encoding: self, for: attachment, body) | ||
} | ||
} | ||
#endif |
97 changes: 97 additions & 0 deletions
97
Sources/Overlays/_Testing_Foundation/Attachments/Test.Attachable+Encodable.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
// | ||
// This source file is part of the Swift.org open source project | ||
// | ||
// Copyright (c) 2024 Apple Inc. and the Swift project authors | ||
// Licensed under Apache License v2.0 with Runtime Library Exception | ||
// | ||
// See https://swift.org/LICENSE.txt for license information | ||
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors | ||
// | ||
|
||
#if canImport(Foundation) | ||
@_spi(Experimental) public import Testing | ||
private import Foundation | ||
|
||
/// A common implementation of ``withUnsafeBufferPointer(for:_:)`` that is | ||
/// used when a type conforms to `Encodable`, whether or not it also conforms | ||
/// to `NSSecureCoding`. | ||
/// | ||
/// - Parameters: | ||
/// - attachment: The attachment that is requesting a buffer (that is, the | ||
/// attachment containing this instance.) | ||
/// - 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. | ||
func withUnsafeBufferPointer<E, R>(encoding attachableValue: borrowing E, for attachment: borrowing Test.Attachment<E>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R where E: Test.Attachable & Encodable { | ||
let format = try EncodingFormat(for: attachment) | ||
|
||
let data: Data | ||
switch format { | ||
case let .propertyListFormat(propertyListFormat): | ||
let plistEncoder = PropertyListEncoder() | ||
plistEncoder.outputFormat = propertyListFormat | ||
data = try plistEncoder.encode(attachableValue) | ||
case .default: | ||
// The default format is JSON. | ||
fallthrough | ||
case .json: | ||
// We cannot use our own JSON encoding wrapper here because that would | ||
// require it be exported with (at least) package visibility which would | ||
// create a visible external dependency on Foundation in the main testing | ||
// library target. | ||
data = try JSONEncoder().encode(attachableValue) | ||
} | ||
|
||
return try data.withUnsafeBytes(body) | ||
} | ||
|
||
// 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) | ||
extension Test.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), | ||
/// then call a function and pass that buffer to it. | ||
/// | ||
/// - Parameters: | ||
/// - attachment: The attachment that is requesting a buffer (that is, the | ||
/// attachment containing this instance.) | ||
/// - 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 writing an attachment to a | ||
/// test report or to a file on disk. The encoding used depends on the path | ||
/// extension specified by the value of `attachment`'s ``Testing/Test/Attachment/preferredName`` | ||
/// property: | ||
/// | ||
/// | Extension | Encoding Used | Encoder Used | | ||
/// |-|-|-| | ||
/// | `".xml"` | XML property list | [`PropertyListEncoder`](https://developer.apple.com/documentation/foundation/propertylistencoder) | | ||
/// | `".plist"` | Binary property list | [`PropertyListEncoder`](https://developer.apple.com/documentation/foundation/propertylistencoder) | | ||
/// | None, `".json"` | JSON | [`JSONEncoder`](https://developer.apple.com/documentation/foundation/jsonencoder) | | ||
/// | ||
/// OpenStep-style property lists are not supported. If a value conforms to | ||
/// _both_ [`Encodable`](https://developer.apple.com/documentation/swift/encodable) | ||
/// _and_ [`NSSecureCoding`](https://developer.apple.com/documentation/foundation/nssecurecoding), | ||
/// the default implementation of this function uses the value's conformance | ||
/// to `Encodable`. | ||
/// | ||
/// - Note: On Apple platforms, if the attachment's preferred name includes | ||
/// some other path extension, that path extension must represent a type | ||
/// that conforms to [`UTType.propertyList`](https://developer.apple.com/documentation/uniformtypeidentifiers/uttype-swift.struct/propertylist) | ||
/// or to [`UTType.json`](https://developer.apple.com/documentation/uniformtypeidentifiers/uttype-swift.struct/json). | ||
public func withUnsafeBufferPointer<R>(for attachment: borrowing Test.Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { | ||
try _Testing_Foundation.withUnsafeBufferPointer(encoding: self, for: attachment, body) | ||
} | ||
} | ||
#endif |
78 changes: 78 additions & 0 deletions
78
Sources/Overlays/_Testing_Foundation/Attachments/Test.Attachable+NSSecureCoding.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
// | ||
// This source file is part of the Swift.org open source project | ||
// | ||
// Copyright (c) 2024 Apple Inc. and the Swift project authors | ||
// Licensed under Apache License v2.0 with Runtime Library Exception | ||
// | ||
// See https://swift.org/LICENSE.txt for license information | ||
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors | ||
// | ||
|
||
#if canImport(Foundation) | ||
@_spi(Experimental) 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) | ||
extension Test.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. | ||
/// | ||
/// - Parameters: | ||
/// - attachment: The attachment that is requesting a buffer (that is, the | ||
/// attachment containing this instance.) | ||
/// - 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 writing an attachment to a | ||
/// test report or to a file on disk. The encoding used depends on the path | ||
/// extension specified by the value of `attachment`'s ``Testing/Test/Attachment/preferredName`` | ||
/// property: | ||
/// | ||
/// | Extension | Encoding Used | Encoder Used | | ||
/// |-|-|-| | ||
/// | `".xml"` | XML property list | [`NSKeyedArchiver`](https://developer.apple.com/documentation/foundation/nskeyedarchiver) | | ||
/// | None, `".plist"` | Binary property list | [`NSKeyedArchiver`](https://developer.apple.com/documentation/foundation/nskeyedarchiver) | | ||
/// | ||
/// OpenStep-style property lists are not supported. If a value conforms to | ||
/// _both_ [`Encodable`](https://developer.apple.com/documentation/swift/encodable) | ||
/// _and_ [`NSSecureCoding`](https://developer.apple.com/documentation/foundation/nssecurecoding), | ||
/// the default implementation of this function uses the value's conformance | ||
/// to `Encodable`. | ||
/// | ||
/// - Note: On Apple platforms, if the attachment's preferred name includes | ||
/// some other path extension, that path extension must represent a type | ||
/// that conforms to [`UTType.propertyList`](https://developer.apple.com/documentation/uniformtypeidentifiers/uttype-swift.struct/propertylist). | ||
public func withUnsafeBufferPointer<R>(for attachment: borrowing Test.Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { | ||
let format = try EncodingFormat(for: attachment) | ||
|
||
var data = try NSKeyedArchiver.archivedData(withRootObject: self, requiringSecureCoding: true) | ||
switch format { | ||
case .default: | ||
// The default format is just what NSKeyedArchiver produces. | ||
break | ||
case let .propertyListFormat(propertyListFormat): | ||
// BUG: Foundation does not offer a variant of | ||
// NSKeyedArchiver.archivedData(withRootObject:requiringSecureCoding:) | ||
// that is Swift-safe (throws errors instead of exceptions) and lets the | ||
// caller specify the output format. Work around this issue by decoding | ||
// the archive re-encoding it manually. | ||
if propertyListFormat != .binary { | ||
let plist = try PropertyListSerialization.propertyList(from: data, format: nil) | ||
data = try PropertyListSerialization.data(fromPropertyList: plist, format: propertyListFormat, options: 0) | ||
} | ||
case .json: | ||
throw CocoaError(.propertyListWriteInvalid, userInfo: [NSLocalizedDescriptionKey: "An instance of \(type(of: self)) cannot be encoded as JSON. Specify a property list format instead."]) | ||
} | ||
|
||
return try data.withUnsafeBytes(body) | ||
} | ||
} | ||
#endif |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be the first case in the list?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can move it if it bothers you—it's an internal type so it's not going to affect client code.