Skip to content
Open
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//

#if canImport(Foundation) && !SWT_NO_CODABLE
public import Testing
@_spi(ForToolsIntegrationOnly) public import Testing
public import Foundation

// This implementation is necessary to let the compiler disambiguate when a type
Expand All @@ -25,7 +25,8 @@ public import Foundation
extension Attachable where Self: Encodable & NSSecureCoding {
@_documentation(visibility: private)
public func withUnsafeBytes<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
try _Testing_Foundation.withUnsafeBytes(encoding: self, for: attachment, body)
let attachment = try Attachment(encoding: attachment.attachableValue, as: nil as AttachableEncodingFormat?, named: attachment.preferredName, sourceLocation: attachment.sourceLocation)
return try attachment.withUnsafeBytes(body)
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -9,47 +9,9 @@
//

#if canImport(Foundation) && !SWT_NO_CODABLE
public import Testing
@_spi(ForToolsIntegrationOnly) public import Testing
private import Foundation

/// A common implementation of ``withUnsafeBytes(for:_:)`` that is used when a
/// type conforms to `Encodable`, whether or not it also conforms to
/// `NSSecureCoding`.
///
/// - Parameters:
/// - attachableValue: The value to encode.
/// - 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 withUnsafeBytes<E, R>(encoding attachableValue: borrowing E, for attachment: borrowing Attachment<E>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R where E: 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.
Expand Down Expand Up @@ -96,7 +58,8 @@ extension Attachable where Self: Encodable {
/// @Available(Xcode, introduced: 26.0)
/// }
public func withUnsafeBytes<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
try _Testing_Foundation.withUnsafeBytes(encoding: self, for: attachment, body)
let attachment = try Attachment(encoding: attachment.attachableValue, named: attachment.preferredName, sourceLocation: attachment.sourceLocation)
return try attachment.withUnsafeBytes(body)
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//

#if canImport(Foundation)
public import Testing
@_spi(ForToolsIntegrationOnly) public import Testing
public import Foundation

// As with Encodable, implement the protocol requirements for
Expand Down Expand Up @@ -56,28 +56,8 @@ extension Attachable where Self: NSSecureCoding {
/// @Available(Xcode, introduced: 26.0)
/// }
public func withUnsafeBytes<R>(for attachment: borrowing 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)
let attachment = try Attachment(encoding: self, named: attachment.preferredName, sourceLocation: attachment.sourceLocation)
return try attachment.withUnsafeBytes(body)
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//
// 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)
import Testing
public import Foundation

#if SWT_TARGET_OS_APPLE && canImport(UniformTypeIdentifiers)
import UniformTypeIdentifiers
#endif

/// An enumeration describing the encoding formats that you can use when
/// attaching a value that conforms to [`Encodable`](https://developer.apple.com/documentation/swift/encodable).
///
/// Pass an instance of this type to ``Testing/Attachment/init(encoding:as:named:sourceLocation:)``
/// to specify what encoder and format to use when the testing library saves the
/// resulting attachment.
///
/// If you want to attach a value that conforms to [`NSSecureCoding`](https://developer.apple.com/documentation/foundation/nssecurecoding),
/// use [`PropertyListFormat`](https://developer.apple.com/documentation/foundation/propertylistserialization/propertylistformat)
/// instead.
@_spi(Experimental)
public struct AttachableEncodingFormat: Sendable {
/// An enumeration describing the various kinds of encoding format the testing
/// library supports.
enum Kind: Sendable {
/// A property list format.
///
/// - Parameters:
/// - format: The corresponding property list format.
case propertyListFormat(_ format: PropertyListSerialization.PropertyListFormat)

/// The JSON format.
case json
}

/// The kind of encoding format represented by this instance.
var kind: Kind

/// Create an instance of this type representing a property list format.
///
/// - Parameters:
/// - format: The corresponding property list format.
///
/// - Returns: An instance of this type representing `format`.
public static func propertyListFormat(_ format: PropertyListSerialization.PropertyListFormat) -> Self {
.init(kind: .propertyListFormat(format))
}

/// An instance of this type representing the JSON format.
public static var json: Self {
.init(kind: .json)
}

#if SWT_TARGET_OS_APPLE && canImport(UniformTypeIdentifiers)
/// The content type corresponding to this instance.
var contentType: UTType {
switch kind {
case .propertyListFormat(.binary):
.binaryPropertyList
case .propertyListFormat(.xml):
.xmlPropertyList
case .propertyListFormat:
.propertyList
case .json:
.json
}
}
#else
/// The preferred path extension corresponding to this instance.
var preferredPathExtension: String {
switch kind {
case .propertyListFormat:
"plist"
case .json:
"json"
}
}
#endif

/// Construct an attachment name based on a suggested name and this encoding
/// format.
///
/// - Parameters:
/// - suggestedName: A suggested name to use as the basis of the preferred
/// name.
///
/// - Returns: The preferred name for an attachment. The result may or may not
/// equal `suggestedName`.
func preferredName(basedOn suggestedName: String) -> String {
#if SWT_TARGET_OS_APPLE && canImport(UniformTypeIdentifiers)
return (suggestedName as NSString).appendingPathExtension(for: contentType)
#else
let pathExtension = (suggestedName as NSString).pathExtension
guard pathExtension.isEmpty else {
// The developer specified a path extension. This path extension may
// reflect some file format that uses Encodable for serialization, so use
// it verbatim.
return suggestedName
}
return (suggestedName as NSString).appendingPathExtension(preferredPathExtension) ?? suggestedName
#endif
}
}

extension AttachableEncodingFormat: Equatable {}
extension AttachableEncodingFormat.Kind: Equatable {}
#endif
Loading
Loading