Skip to content
Closed
Show file tree
Hide file tree
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 Nov 6, 2024
b9c95ef
Add accessors through withUnsafeBufferPointer since the type-erased a…
grynspan Nov 8, 2024
764cc6a
Work around rdar://137614425 (again)
grynspan Nov 8, 2024
0f5252e
Work around a compiler crash (again, yes)
grynspan Nov 8, 2024
78770a2
Work around the compiler crash a second time
grynspan Nov 8, 2024
06ab04f
Use a dedicated type to represent any attachable value rather than an…
grynspan Nov 10, 2024
a31910c
Nest AnyAttachable
grynspan Nov 10, 2024
6862d64
Add AttachableContainer protocol for abstracting away box/adapter typ…
grynspan Nov 10, 2024
9aec954
Update comments, make AttachableContainer always Copyable & Sendable
grynspan Nov 10, 2024
173f077
Actually no, make Test.AttachableContainer optionally copyable/sendab…
grynspan Nov 10, 2024
b867258
Add support for attachments to the Foundation cross-import overlay.
grynspan Sep 19, 2024
32c487a
Minor fixes
grynspan Nov 3, 2024
a810dbf
Fix rebase glitch
grynspan Nov 3, 2024
b334529
Remove nonfunctional reserved-file check in a test
grynspan Nov 3, 2024
afdf317
Consistently apply .tgz to the preferred name of a compressed directory
grynspan Nov 3, 2024
18d52d5
Customize documentation for protocol conformances of Encodable and NS…
grynspan Nov 4, 2024
c52b02d
Make MyContiguousCollectionAttachable sendable
grynspan Nov 5, 2024
3b68c32
Add default Attachable implementation for types that conform to BOTH …
grynspan Nov 5, 2024
4443939
Remove ContiguousBytes implementation for limited utility
grynspan Nov 5, 2024
3feb342
Fix a typo
grynspan Nov 5, 2024
311c1c9
Remove ContiguousBytes test
grynspan Nov 5, 2024
9ffc49a
Minor cleanup
grynspan Nov 5, 2024
bb22280
Work around compiler crash
grynspan Nov 5, 2024
184774b
Work around another way?
grynspan Nov 5, 2024
950fa6e
Update to use generic Test.Attachment
grynspan Nov 8, 2024
d039ea0
Don't introduce a new public type for file attachments
grynspan Nov 8, 2024
df272e7
Restore tests
grynspan Nov 10, 2024
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
3 changes: 3 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ let package = Package(
dependencies: [
"Testing",
],
path: "Sources/Overlays/_Testing_Foundation",
swiftSettings: .packageSettings
),
],
Expand Down Expand Up @@ -124,6 +125,7 @@ extension Array where Element == PackageDescription.SwiftSetting {
availabilityMacroSettings + [
.unsafeFlags(["-require-explicit-sendable"]),
.enableUpcomingFeature("ExistentialAny"),
//.enableExperimentalFeature("SuppressedAssociatedTypes"),

.enableExperimentalFeature("AccessLevelOnImport"),
.enableUpcomingFeature("InternalImportsByDefault"),
Expand All @@ -146,6 +148,7 @@ extension Array where Element == PackageDescription.SwiftSetting {
private static var availabilityMacroSettings: Self {
[
.enableExperimentalFeature("AvailabilityMacro=_mangledTypeNameAPI:macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0"),
.enableExperimentalFeature("AvailabilityMacro=_uttypesAPI:macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0"),
.enableExperimentalFeature("AvailabilityMacro=_backtraceAsyncAPI:macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0"),
.enableExperimentalFeature("AvailabilityMacro=_clockAPI:macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0"),
.enableExperimentalFeature("AvailabilityMacro=_regexAPI:macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0"),
Expand Down
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
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`
Copy link
Contributor

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?

Copy link
Contributor Author

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.


/// 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
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
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
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
Loading