From 4d57462145c51764c0e8f6bd9d5b5da4323e57b5 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Fri, 24 Oct 2025 15:49:02 -0400 Subject: [PATCH] [WIP] Adopt `Span` and `RawSpan`. --- ...chableImageWrapper+AttachableWrapper.swift | 6 +- .../Attachable+Encodable+NSSecureCoding.swift | 7 +- .../Attachments/Attachable+Encodable.swift | 59 +++++++++--- .../Attachable+NSSecureCoding.swift | 89 ++++++++++++++----- .../Attachments/Data+Attachable.swift | 6 +- .../Attachments/_AttachableURLWrapper.swift | 6 +- .../Testing/ABI/ABI.Record+Streaming.swift | 8 +- Sources/Testing/ABI/ABI.swift | 2 +- .../ABI/Encoded/ABI.EncodedAttachment.swift | 21 +++-- .../ABI/EntryPoints/ABIEntryPoint.swift | 7 +- .../Testing/ABI/EntryPoints/EntryPoint.swift | 6 +- Sources/Testing/Attachments/Attachable.swift | 88 ++++++++++++++++-- Sources/Testing/Attachments/Attachment.swift | 43 +++++++-- Sources/Testing/ExitTests/ExitTest.swift | 16 ++-- .../Support/Additions/ArrayAdditions.swift | 42 +++++++++ Sources/Testing/Support/FileHandle.swift | 25 +++++- Sources/Testing/Support/JSON.swift | 15 ++-- .../Traits/Tags/Tag.Color+Loading.swift | 2 +- Tests/TestingTests/ABIEntryPointTests.swift | 14 +-- Tests/TestingTests/AttachmentTests.swift | 45 ++++++---- Tests/TestingTests/ExitTestTests.swift | 2 +- Tests/TestingTests/SwiftPMTests.swift | 2 +- .../Test.Case.Argument.IDTests.swift | 4 +- .../TestSupport/TestingAdditions.swift | 4 +- 24 files changed, 402 insertions(+), 117 deletions(-) diff --git a/Sources/Overlays/_Testing_CoreGraphics/Attachments/_AttachableImageWrapper+AttachableWrapper.swift b/Sources/Overlays/_Testing_CoreGraphics/Attachments/_AttachableImageWrapper+AttachableWrapper.swift index 3281de11a..f0e6b0f55 100644 --- a/Sources/Overlays/_Testing_CoreGraphics/Attachments/_AttachableImageWrapper+AttachableWrapper.swift +++ b/Sources/Overlays/_Testing_CoreGraphics/Attachments/_AttachableImageWrapper+AttachableWrapper.swift @@ -41,6 +41,10 @@ private import UniformTypeIdentifiers @available(_uttypesAPI, *) extension _AttachableImageWrapper: Attachable, AttachableWrapper where Image: AttachableAsCGImage { public func withUnsafeBytes(for attachment: borrowing Attachment<_AttachableImageWrapper>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { + try default_withUnsafeBytes(for: attachment, body) + } + + public borrowing func withBytes(for attachment: borrowing Attachment<_AttachableImageWrapper>, _ body: (borrowing RawSpan) throws -> R) throws -> R { let data = NSMutableData() // Convert the image to a CGImage. @@ -72,7 +76,7 @@ extension _AttachableImageWrapper: Attachable, AttachableWrapper where Image: At // NSMutableData here so we have to use slightly different API than we would // with an instance of Data. return try withExtendedLifetime(data) { - try body(UnsafeRawBufferPointer(start: data.bytes, count: data.length)) + try body(RawSpan(_unsafeStart: data.bytes, byteCount: data.length)) } } diff --git a/Sources/Overlays/_Testing_Foundation/Attachments/Attachable+Encodable+NSSecureCoding.swift b/Sources/Overlays/_Testing_Foundation/Attachments/Attachable+Encodable+NSSecureCoding.swift index d82c6a6c6..b7740709c 100644 --- a/Sources/Overlays/_Testing_Foundation/Attachments/Attachable+Encodable+NSSecureCoding.swift +++ b/Sources/Overlays/_Testing_Foundation/Attachments/Attachable+Encodable+NSSecureCoding.swift @@ -25,7 +25,12 @@ public import Foundation extension Attachable where Self: Encodable & NSSecureCoding { @_documentation(visibility: private) public func withUnsafeBytes(for attachment: borrowing Attachment, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { - try _Testing_Foundation.withUnsafeBytes(encoding: self, for: attachment, body) + try default_withUnsafeBytes(for: attachment, body) + } + + @_documentation(visibility: private) + public borrowing func withBytes(for attachment: borrowing Attachment, _ body: (borrowing RawSpan) throws -> R) throws -> R { + try body(_data(encoding: self, for: attachment).bytes) } } #endif diff --git a/Sources/Overlays/_Testing_Foundation/Attachments/Attachable+Encodable.swift b/Sources/Overlays/_Testing_Foundation/Attachments/Attachable+Encodable.swift index 747024de3..29c7919bb 100644 --- a/Sources/Overlays/_Testing_Foundation/Attachments/Attachable+Encodable.swift +++ b/Sources/Overlays/_Testing_Foundation/Attachments/Attachable+Encodable.swift @@ -10,24 +10,20 @@ #if canImport(Foundation) public import Testing -private import Foundation +internal 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`. +/// The common implementation of ``withUnsafeBytes(for:_:)`` and +/// ``withBytes(for:_:)`` for types conforming to `Encodable`. /// /// - 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. +/// - attachment: The attachment that is requesting data (that is, the +/// attachment containing `attachableValue`.) /// -/// - Returns: Whatever is returned by `body`. +/// - Returns: An encoded representation `attachableValue`. /// -/// - Throws: Whatever is thrown by `body`, or any error that prevented the -/// creation of the buffer. -func withUnsafeBytes(encoding attachableValue: borrowing E, for attachment: borrowing Attachment, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R where E: Attachable & Encodable { +/// - Throws: Any error that prevented creation of the data. +func _data(encoding attachableValue: borrowing E, for attachment: borrowing Attachment) throws -> Data where E: Attachable & Encodable { let format = try EncodingFormat(for: attachment) let data: Data @@ -47,7 +43,7 @@ func withUnsafeBytes(encoding attachableValue: borrowing E, for attachment data = try JSONEncoder().encode(attachableValue) } - return try data.withUnsafeBytes(body) + return data } // Implement the protocol requirements generically for any encodable value by @@ -96,7 +92,42 @@ extension Attachable where Self: Encodable { /// @Available(Xcode, introduced: 26.0) /// } public func withUnsafeBytes(for attachment: borrowing Attachment, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { - try _Testing_Foundation.withUnsafeBytes(encoding: self, for: attachment, body) + try default_withUnsafeBytes(for: attachment, body) + } + + /// Encode this value into a span 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 span to it. + /// + /// - Parameters: + /// - attachment: The attachment that is requesting a span (that is, the + /// attachment containing this instance.) + /// - body: A function to call. A temporary span 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 span. + /// + /// 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/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`. + public borrowing func withBytes(for attachment: borrowing Attachment, _ body: (borrowing RawSpan) throws -> R) throws -> R { + try body(_data(encoding: self, for: attachment).bytes) } } #endif diff --git a/Sources/Overlays/_Testing_Foundation/Attachments/Attachable+NSSecureCoding.swift b/Sources/Overlays/_Testing_Foundation/Attachments/Attachable+NSSecureCoding.swift index 95002be0a..e51a190d4 100644 --- a/Sources/Overlays/_Testing_Foundation/Attachments/Attachable+NSSecureCoding.swift +++ b/Sources/Overlays/_Testing_Foundation/Attachments/Attachable+NSSecureCoding.swift @@ -21,6 +21,41 @@ public import Foundation /// @Available(Xcode, introduced: 26.0) /// } extension Attachable where Self: NSSecureCoding { + /// The common implementation of ``withUnsafeBytes(for:_:)`` and + /// ``withBytes(for:_:)`` for types conforming to `NSSecureCoding`. + /// + /// - Parameters: + /// - attachment: The attachment that is requesting data (that is, the + /// attachment containing this instance.) + /// + /// - Returns: An encoded representation of this instance. + /// + /// - Throws: Any error that prevented creation of the data. + private func _data(for attachment: borrowing Attachment) throws -> Data { + 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 data + } + /// 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. /// @@ -56,28 +91,40 @@ extension Attachable where Self: NSSecureCoding { /// @Available(Xcode, introduced: 26.0) /// } public func withUnsafeBytes(for attachment: borrowing Attachment, _ 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."]) - } + try default_withUnsafeBytes(for: attachment, body) + } - return try data.withUnsafeBytes(body) + /// Encode this object using [`NSKeyedArchiver`](https://developer.apple.com/documentation/foundation/nskeyedarchiver) + /// into a span, then call a function and pass that span to it. + /// + /// - Parameters: + /// - attachment: The attachment that is requesting a span (that is, the + /// attachment containing this instance.) + /// - body: A function to call. A temporary span 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 span. + /// + /// 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/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`. + public borrowing func withBytes(for attachment: borrowing Attachment, _ body: (borrowing RawSpan) throws -> R) throws -> R { + try body(_data(for: attachment).bytes) } } #endif diff --git a/Sources/Overlays/_Testing_Foundation/Attachments/Data+Attachable.swift b/Sources/Overlays/_Testing_Foundation/Attachments/Data+Attachable.swift index 56f058da3..ae60654c5 100644 --- a/Sources/Overlays/_Testing_Foundation/Attachments/Data+Attachable.swift +++ b/Sources/Overlays/_Testing_Foundation/Attachments/Data+Attachable.swift @@ -22,7 +22,11 @@ extension Data: Attachable { /// @Available(Xcode, introduced: 26.0) /// } public func withUnsafeBytes(for attachment: borrowing Attachment, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { - try withUnsafeBytes(body) + try default_withUnsafeBytes(for: attachment, body) + } + + public borrowing func withBytes(for attachment: borrowing Attachment, _ body: (borrowing RawSpan) throws -> R) throws -> R { + try body(bytes) } } #endif diff --git a/Sources/Overlays/_Testing_Foundation/Attachments/_AttachableURLWrapper.swift b/Sources/Overlays/_Testing_Foundation/Attachments/_AttachableURLWrapper.swift index d6be53c80..a2760bb9d 100644 --- a/Sources/Overlays/_Testing_Foundation/Attachments/_AttachableURLWrapper.swift +++ b/Sources/Overlays/_Testing_Foundation/Attachments/_AttachableURLWrapper.swift @@ -36,7 +36,11 @@ extension _AttachableURLWrapper: AttachableWrapper { } public func withUnsafeBytes(for attachment: borrowing Attachment, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { - try data.withUnsafeBytes(body) + try default_withUnsafeBytes(for: attachment, body) + } + + public borrowing func withBytes(for attachment: borrowing Attachment, _ body: (borrowing RawSpan) throws -> R) throws -> R { + try body(data.bytes) } public borrowing func preferredName(for attachment: borrowing Attachment, basedOn suggestedName: String) -> String { diff --git a/Sources/Testing/ABI/ABI.Record+Streaming.swift b/Sources/Testing/ABI/ABI.Record+Streaming.swift index 1aa1362ec..118d3cfbb 100644 --- a/Sources/Testing/ABI/ABI.Record+Streaming.swift +++ b/Sources/Testing/ABI/ABI.Record+Streaming.swift @@ -14,7 +14,7 @@ private import Foundation extension ABI.Version { static func eventHandler( encodeAsJSONLines: Bool, - forwardingTo eventHandler: @escaping @Sendable (_ recordJSON: UnsafeRawBufferPointer) -> Void + forwardingTo eventHandler: @escaping @Sendable (_ recordJSON: RawSpan) -> Void ) -> Event.Handler { // Encode as JSON Lines if requested. var eventHandlerCopy = eventHandler @@ -44,7 +44,7 @@ extension ABI.Version { extension ABI.Xcode16 { static func eventHandler( encodeAsJSONLines: Bool, - forwardingTo eventHandler: @escaping @Sendable (_ recordJSON: UnsafeRawBufferPointer) -> Void + forwardingTo eventHandler: @escaping @Sendable (_ recordJSON: RawSpan) -> Void ) -> Event.Handler { return { event, context in if case .testDiscovered = event.kind { @@ -63,9 +63,7 @@ extension ABI.Xcode16 { eventContext: Event.Context.Snapshot(snapshotting: context) ) try? JSON.withEncoding(of: snapshot) { eventAndContextJSON in - eventAndContextJSON.withUnsafeBytes { eventAndContextJSON in - eventHandler(eventAndContextJSON) - } + eventHandler(eventAndContextJSON) } } } diff --git a/Sources/Testing/ABI/ABI.swift b/Sources/Testing/ABI/ABI.swift index 7a33970fc..e08667b03 100644 --- a/Sources/Testing/ABI/ABI.swift +++ b/Sources/Testing/ABI/ABI.swift @@ -37,7 +37,7 @@ extension ABI { /// associated context is created and is passed to `eventHandler`. static func eventHandler( encodeAsJSONLines: Bool, - forwardingTo eventHandler: @escaping @Sendable (_ recordJSON: UnsafeRawBufferPointer) -> Void + forwardingTo eventHandler: @escaping @Sendable (_ recordJSON: RawSpan) -> Void ) -> Event.Handler #endif } diff --git a/Sources/Testing/ABI/Encoded/ABI.EncodedAttachment.swift b/Sources/Testing/ABI/Encoded/ABI.EncodedAttachment.swift index 013e129f6..f90011b14 100644 --- a/Sources/Testing/ABI/Encoded/ABI.EncodedAttachment.swift +++ b/Sources/Testing/ABI/Encoded/ABI.EncodedAttachment.swift @@ -45,7 +45,7 @@ extension ABI { _preferredName = attachment.preferredName if path == nil { - _bytes = try? attachment.withUnsafeBytes { bytes in + _bytes = try? attachment.withBytes { bytes in return Bytes(rawValue: [UInt8](bytes)) } } @@ -67,10 +67,12 @@ extension ABI.EncodedAttachment.Bytes: Codable { func encode(to encoder: any Encoder) throws { #if canImport(Foundation) // If possible, encode this structure as Base64 data. - try rawValue.withUnsafeBytes { rawValue in - let data = Data(bytesNoCopy: .init(mutating: rawValue.baseAddress!), count: rawValue.count, deallocator: .none) - var container = encoder.singleValueContainer() - try container.encode(data) + try rawValue.withBytes { bytes in + try bytes.withUnsafeBytes { bytes in + let data = Data(bytesNoCopy: .init(mutating: bytes.baseAddress!), count: rawValue.count, deallocator: .none) + var container = encoder.singleValueContainer() + try container.encode(data) + } } #else // Otherwise, it's an array of integers. @@ -108,8 +110,12 @@ extension ABI.EncodedAttachment: Attachable { fileprivate struct BytesUnavailableError: Error {} borrowing func withUnsafeBytes(for attachment: borrowing Attachment, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { + try default_withUnsafeBytes(for: attachment, body) + } + + borrowing func withBytes(for attachment: borrowing Attachment, _ body: (borrowing RawSpan) throws -> R) throws -> R { if let bytes = _bytes?.rawValue { - return try bytes.withUnsafeBytes(body) + return try bytes.withBytes(body) } #if !SWT_NO_FILE_IO @@ -120,11 +126,12 @@ extension ABI.EncodedAttachment: Attachable { // Leverage Foundation's file-mapping logic since we're using Data anyway. let url = URL(fileURLWithPath: path, isDirectory: false) let bytes = try Data(contentsOf: url, options: [.mappedIfSafe]) + return try body(bytes.bytes) #else let fileHandle = try FileHandle(forReadingAtPath: path) let bytes = try fileHandle.readToEnd() + return try bytes.withBytes(body) #endif - return try bytes.withUnsafeBytes(body) #else // Cannot read the attachment from disk on this platform. throw BytesUnavailableError() diff --git a/Sources/Testing/ABI/EntryPoints/ABIEntryPoint.swift b/Sources/Testing/ABI/EntryPoints/ABIEntryPoint.swift index 2ff10c964..735ea5e97 100644 --- a/Sources/Testing/ABI/EntryPoints/ABIEntryPoint.swift +++ b/Sources/Testing/ABI/EntryPoints/ABIEntryPoint.swift @@ -48,9 +48,12 @@ extension ABI.v0 { public static var entryPoint: EntryPoint { return { configurationJSON, recordHandler in let args = try configurationJSON.map { configurationJSON in - try JSON.decode(__CommandLineArguments_v0.self, from: configurationJSON) + let configurationJSON = RawSpan(_unsafeBytes: configurationJSON) + return try JSON.decode(__CommandLineArguments_v0.self, from: configurationJSON) + } + let eventHandler = try eventHandlerForStreamingEvents(withVersionNumber: args?.eventStreamVersionNumber, encodeAsJSONLines: false) { recordJSON in + recordJSON.withUnsafeBytes(recordHandler) } - let eventHandler = try eventHandlerForStreamingEvents(withVersionNumber: args?.eventStreamVersionNumber, encodeAsJSONLines: false, forwardingTo: recordHandler) switch await Testing.entryPoint(passing: args, eventHandler: eventHandler) { case EXIT_SUCCESS, EXIT_NO_TESTS_FOUND: diff --git a/Sources/Testing/ABI/EntryPoints/EntryPoint.swift b/Sources/Testing/ABI/EntryPoints/EntryPoint.swift index f4f1a751c..b095a2a7d 100644 --- a/Sources/Testing/ABI/EntryPoints/EntryPoint.swift +++ b/Sources/Testing/ABI/EntryPoints/EntryPoint.swift @@ -417,8 +417,8 @@ func parseCommandLineArguments(from args: [String]) throws -> __CommandLineArgum if let path = args.argumentValue(forLabel: "--configuration-path") ?? args.argumentValue(forLabel: "--experimental-configuration-path") { let file = try FileHandle(forReadingAtPath: path) let configurationJSON = try file.readToEnd() - result = try configurationJSON.withUnsafeBufferPointer { configurationJSON in - try JSON.decode(__CommandLineArguments_v0.self, from: .init(configurationJSON)) + result = try configurationJSON.withBytes { configurationJSON in + return try JSON.decode(__CommandLineArguments_v0.self, from: configurationJSON) } // NOTE: We don't return early or block other arguments here: a caller is @@ -687,7 +687,7 @@ public func configurationForEntryPoint(from args: __CommandLineArguments_v0) thr func eventHandlerForStreamingEvents( withVersionNumber versionNumber: VersionNumber?, encodeAsJSONLines: Bool, - forwardingTo targetEventHandler: @escaping @Sendable (UnsafeRawBufferPointer) -> Void + forwardingTo targetEventHandler: @escaping @Sendable (borrowing RawSpan) -> Void ) throws -> Event.Handler { let versionNumber = versionNumber ?? ABI.CurrentVersion.versionNumber guard let abi = ABI.version(forVersionNumber: versionNumber) else { diff --git a/Sources/Testing/Attachments/Attachable.swift b/Sources/Testing/Attachments/Attachable.swift index 8e2c06420..c2827bec2 100644 --- a/Sources/Testing/Attachments/Attachable.swift +++ b/Sources/Testing/Attachments/Attachable.swift @@ -74,12 +74,45 @@ public protocol Attachable: ~Copyable { /// etc., but it would not be idiomatic for the buffer to contain a textual /// description of the image. /// + /// @DeprecationSummary { + /// Implement ``withBytes(for:_:)`` instead. The testing library does not + /// call this function. In the future, the default implementation of this + /// function will automatically call ``withBytes(for:_:)``. + /// } + /// /// @Metadata { /// @Available(Swift, introduced: 6.2) /// @Available(Xcode, introduced: 26.0) /// } + @available(swift, deprecated: 100000.0, message: "Use 'withBytes(for:_:)' instead") borrowing func withUnsafeBytes(for attachment: borrowing Attachment, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R + /// Call a function and pass a span representing this instance to it. + /// + /// - Parameters: + /// - attachment: The attachment that is requesting a span (that is, the + /// attachment containing this instance.) + /// - body: A function to call. A temporary span 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 span. + /// + /// The testing library uses this function when saving an attachment. The + /// format of the span is implementation-defined, but should be "idiomatic" + /// for this type: for example, if this type represents an image, it would be + /// appropriate for the span to contain an image in PNG format, JPEG format, + /// etc., but it would not be idiomatic for the span to contain a textual + /// description of the image. + /// + /// The default implementation of this function calls ``withUnsafeBytes(for:_:)``. + /// In the future, you will need to implement this function instead of + /// ``withUnsafeBytes(for:_:)``, and the default implementation of this + /// function will be removed. + borrowing func withBytes(for attachment: borrowing Attachment, _ body: (borrowing RawSpan) throws -> R) throws -> R + /// Generate a preferred name for the given attachment. /// /// - Parameters: @@ -111,6 +144,21 @@ extension Attachable where Self: ~Copyable { public borrowing func preferredName(for attachment: borrowing Attachment, basedOn suggestedName: String) -> String { suggestedName } + + /// The default implementation of ``withUnsafeBytes(for:_:)`` used by types in + /// the testing library. + package borrowing func default_withUnsafeBytes(for attachment: borrowing Attachment, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { + try withBytes(for: attachment) { bytes in + try bytes.withUnsafeBytes(body) + } + } + + @available(swift, deprecated: 1000000.0, message: "Types that conform to 'Attachable' must implement 'withBytes(for:_:)'") + public borrowing func withBytes(for attachment: borrowing Attachment, _ body: (borrowing RawSpan) throws -> R) throws -> R { + try withUnsafeBytes(for: attachment) { bytes in + try body(RawSpan(_unsafeBytes: bytes)) + } + } } extension Attachable where Self: Collection, Element == UInt8 { @@ -139,36 +187,62 @@ extension Attachable where Self: StringProtocol { // developers can attach raw data when needed. extension Array: Attachable { public func withUnsafeBytes(for attachment: borrowing Attachment, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { - try withUnsafeBytes(body) + try default_withUnsafeBytes(for: attachment, body) + } + + public borrowing func withBytes(for attachment: borrowing Attachment, _ body: (borrowing RawSpan) throws -> R) throws -> R { + try withUnsafeBytes { bytes in + try body(RawSpan(_unsafeBytes: bytes)) + } } } extension ContiguousArray: Attachable { public func withUnsafeBytes(for attachment: borrowing Attachment, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { - try withUnsafeBytes(body) + try default_withUnsafeBytes(for: attachment, body) + } + + public borrowing func withBytes(for attachment: borrowing Attachment, _ body: (borrowing RawSpan) throws -> R) throws -> R { + try withUnsafeBytes { bytes in + try body(RawSpan(_unsafeBytes: bytes)) + } } } extension ArraySlice: Attachable { public func withUnsafeBytes(for attachment: borrowing Attachment, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { - try withUnsafeBytes(body) + try default_withUnsafeBytes(for: attachment, body) + } + + public borrowing func withBytes(for attachment: borrowing Attachment, _ body: (borrowing RawSpan) throws -> R) throws -> R { + try withUnsafeBytes { bytes in + try body(RawSpan(_unsafeBytes: bytes)) + } } } extension String: Attachable { public func withUnsafeBytes(for attachment: borrowing Attachment, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { - var selfCopy = self + try default_withUnsafeBytes(for: attachment, body) + } + + public borrowing func withBytes(for attachment: borrowing Attachment, _ body: (borrowing RawSpan) throws -> R) throws -> R { + var selfCopy = copy self return try selfCopy.withUTF8 { utf8 in - try body(UnsafeRawBufferPointer(utf8)) + try body(RawSpan(_unsafeElements: utf8)) } } } extension Substring: Attachable { public func withUnsafeBytes(for attachment: borrowing Attachment, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { - var selfCopy = self + try default_withUnsafeBytes(for: attachment, body) + } + + public borrowing func withBytes(for attachment: borrowing Attachment, _ body: (borrowing RawSpan) throws -> R) throws -> R { + var selfCopy = copy self return try selfCopy.withUTF8 { utf8 in - try body(UnsafeRawBufferPointer(utf8)) + try body(RawSpan(_unsafeElements: utf8)) } } } diff --git a/Sources/Testing/Attachments/Attachment.swift b/Sources/Testing/Attachments/Attachment.swift index b22911919..225c78483 100644 --- a/Sources/Testing/Attachments/Attachment.swift +++ b/Sources/Testing/Attachments/Attachment.swift @@ -158,7 +158,7 @@ public struct AnyAttachable: AttachableWrapper, Sendable, Copyable { init(_ attachment: Attachment) where A: Attachable & Sendable & ~Copyable { _estimatedAttachmentByteCount = { attachment.attachableValue.estimatedAttachmentByteCount } - _withUnsafeBytes = { try attachment.withUnsafeBytes($0) } + _withBytes = { try attachment.attachableValue.withBytes(for: attachment, $0) } _preferredName = { attachment.attachableValue.preferredName(for: attachment, basedOn: $0) } } @@ -170,13 +170,17 @@ public struct AnyAttachable: AttachableWrapper, Sendable, Copyable { _estimatedAttachmentByteCount() } - /// The implementation of ``withUnsafeBytes(for:_:)`` borrowed from the - /// original attachment. - private var _withUnsafeBytes: @Sendable ((UnsafeRawBufferPointer) throws -> Void) throws -> Void + public borrowing func withUnsafeBytes(for attachment: borrowing Attachment, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { + try default_withUnsafeBytes(for: attachment, body) + } + + /// The implementation of ``withBytes(for:_:)`` borrowed from the original + /// attachment. + private var _withBytes: @Sendable ((borrowing RawSpan) throws -> Void) throws -> Void - public func withUnsafeBytes(for attachment: borrowing Attachment, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { + public borrowing func withBytes(for attachment: borrowing Attachment, _ body: (borrowing RawSpan) throws -> R) throws -> R { var result: R! - try _withUnsafeBytes { bytes in + try _withBytes { bytes in result = try body(bytes) } return result @@ -330,7 +334,7 @@ extension Attachment where AttachableValue: ~Copyable { /// } public static func record(_ attachment: consuming Self, sourceLocation: SourceLocation = #_sourceLocation) { do { - let bufferCopy = try attachment.withUnsafeBytes { Array($0) } + let bufferCopy = try attachment.withBytes { Array($0) } Attachment.record(bufferCopy, sourceLocation: sourceLocation) } catch { let sourceContext = SourceContext(backtrace: .current(), sourceLocation: sourceLocation) @@ -390,9 +394,34 @@ extension Attachment where AttachableValue: ~Copyable { /// @Available(Swift, introduced: 6.2) /// @Available(Xcode, introduced: 26.0) /// } + @available(swift, deprecated: 100000.0, message: "Use 'withBytes(_:)' instead") @inlinable public borrowing func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { try attachableValue.withUnsafeBytes(for: self, body) } + + /// Call a function and pass a span representing the value of this + /// instance's ``attachableValue-2tnj5`` property to it. + /// + /// - Parameters: + /// - 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 span. + /// + /// The testing library uses this function when saving an attachment. This + /// function calls the ``Attachable/withUnsafeBytes(for:_:)`` function on this + /// attachment's ``attachableValue-2tnj5`` property. + /// + /// @Metadata { + /// @Available(Swift, introduced: 6.2) + /// @Available(Xcode, introduced: 26.0) + /// } + @inlinable public borrowing func withBytes(_ body: (borrowing RawSpan) throws -> R) throws -> R { + try attachableValue.withBytes(for: self, body) + } } #if !SWT_NO_FILE_IO diff --git a/Sources/Testing/ExitTests/ExitTest.swift b/Sources/Testing/ExitTests/ExitTest.swift index 56096906f..d15ad95f1 100644 --- a/Sources/Testing/ExitTests/ExitTest.swift +++ b/Sources/Testing/ExitTests/ExitTest.swift @@ -719,7 +719,7 @@ extension ExitTest { } return try? idString.withUTF8 { idBuffer in - try JSON.decode(ExitTest.ID.self, from: UnsafeRawBufferPointer(idBuffer)) + try JSON.decode(ExitTest.ID.self, from: idBuffer.span.bytes) } } @@ -875,7 +875,9 @@ extension ExitTest { // Insert a specific variable that tells the child process which exit test // to run. try JSON.withEncoding(of: exitTest.id) { json in - childEnvironment[Self._idEnvironmentVariableName] = String(decoding: json, as: UTF8.self) + json.withUnsafeBytes { json in + childEnvironment[Self._idEnvironmentVariableName] = String(decoding: json, as: UTF8.self) + } } typealias ResultUpdater = @Sendable (inout ExitTest.Result) -> Void @@ -1012,8 +1014,8 @@ extension ExitTest { for recordJSON in bytes.split(whereSeparator: \.isASCIINewline) where !recordJSON.isEmpty { do { - try recordJSON.withUnsafeBufferPointer { recordJSON in - try Self._processRecord(.init(recordJSON), fromBackChannel: backChannel) + try recordJSON.withBytes { recordJSON in + try Self._processRecord(recordJSON, fromBackChannel: backChannel) } } catch { // NOTE: an error caught here indicates a decoding problem. @@ -1031,7 +1033,7 @@ extension ExitTest { /// - backChannel: The file handle that `recordJSON` was read from. /// /// - Throws: Any error encountered attempting to decode or process the JSON. - private static func _processRecord(_ recordJSON: UnsafeRawBufferPointer, fromBackChannel backChannel: borrowing FileHandle) throws { + private static func _processRecord(_ recordJSON: borrowing RawSpan, fromBackChannel backChannel: borrowing FileHandle) throws { let record = try JSON.decode(ABI.Record.self, from: recordJSON) guard case let .event(event) = record.kind else { return @@ -1098,7 +1100,7 @@ extension ExitTest { var capturedValue = capturedValue func open(_ type: T.Type) throws -> T where T: Codable & Sendable { - return try capturedValueJSON.withUnsafeBytes { capturedValueJSON in + return try capturedValueJSON.withBytes { capturedValueJSON in try JSON.decode(type, from: capturedValueJSON) } } @@ -1123,7 +1125,7 @@ extension ExitTest { /// This function should only be used when the process was started via the /// `__swiftPMEntryPoint()` function. The effect of using it under other /// configurations is undefined. - private borrowing func _withEncodedCapturedValuesForEntryPoint(_ body: (UnsafeRawBufferPointer) throws -> Void) throws -> Void { + private borrowing func _withEncodedCapturedValuesForEntryPoint(_ body: (borrowing RawSpan) throws -> Void) throws -> Void { for capturedValue in capturedValues { try JSON.withEncoding(of: capturedValue.wrappedValue!) { capturedValueJSON in try JSON.asJSONLine(capturedValueJSON, body) diff --git a/Sources/Testing/Support/Additions/ArrayAdditions.swift b/Sources/Testing/Support/Additions/ArrayAdditions.swift index eee74037d..49f0837d0 100644 --- a/Sources/Testing/Support/Additions/ArrayAdditions.swift +++ b/Sources/Testing/Support/Additions/ArrayAdditions.swift @@ -20,8 +20,50 @@ extension Array { init(_ optionalValue: Element?) { self = optionalValue.map { [$0] } ?? [] } + + init(_ bytes: borrowing RawSpan) where Element == UInt8 { + self = bytes.withUnsafeBytes { Array($0) } + } + + func withSpan(_ body: (borrowing Span) throws(E) -> R) throws(E) -> R { + try self[...].withSpan(body) + } + + func withBytes(_ body: (borrowing RawSpan) throws(E) -> R) throws(E) -> R where Element: BitwiseCopyable { + try self[...].withBytes(body) + } } +// MARK: - + +extension ArraySlice { + func withSpan(_ body: (borrowing Span) throws(E) -> R) throws(E) -> R { +#if SWT_TARGET_OS_APPLE + do { + return try withUnsafeBufferPointer { buffer in + try body(Span(_unsafeElements: buffer)) + } + } catch { + throw error as! E + } +#else + try body(span) +#endif + } + + func withBytes(_ body: (borrowing RawSpan) throws(E) -> R) throws(E) -> R where Element: BitwiseCopyable { +#if SWT_TARGET_OS_APPLE + try withSpan { span throws(E) in + try body(span.bytes) + } +#else + try body(span.bytes) +#endif + } +} + +// MARK: - + /// Get the number of elements in a parameter pack. /// /// - Parameters: diff --git a/Sources/Testing/Support/FileHandle.swift b/Sources/Testing/Support/FileHandle.swift index d038db101..f2acab638 100644 --- a/Sources/Testing/Support/FileHandle.swift +++ b/Sources/Testing/Support/FileHandle.swift @@ -365,6 +365,23 @@ extension FileHandle { // MARK: - Writing extension FileHandle { + /// Write a span of bytes to this file handle. + /// + /// - Parameters: + /// - bytes: The bytes to write. This untyped span is interpreted as a + /// sequence of `UInt8` values. + /// - flushAfterward: Whether or not to flush the file (with `fflush()`) + /// after writing. If `true`, `fflush()` is called even if an error + /// occurred while writing. + /// + /// - Throws: Any error that occurred while writing `bytes`. If an error + /// occurs while flushing the file, it is not thrown. + func write(_ bytes: borrowing RawSpan, flushAfterward: Bool = true) throws { + try bytes.withUnsafeBytes { bytes in + try write(bytes, flushAfterward: flushAfterward) + } + } + /// Write a sequence of bytes to this file handle. /// /// - Parameters: @@ -384,9 +401,11 @@ extension FileHandle { } } - let countWritten = fwrite(bytes.baseAddress!, MemoryLayout.stride, bytes.count, file) - if countWritten < bytes.count { - throw CError(rawValue: swt_errno()) + if let baseAddress = bytes.baseAddress { + let countWritten = fwrite(baseAddress, MemoryLayout.stride, bytes.count, file) + if countWritten < bytes.count { + throw CError(rawValue: swt_errno()) + } } } } diff --git a/Sources/Testing/Support/JSON.swift b/Sources/Testing/Support/JSON.swift index 3d656687f..3bce5987a 100644 --- a/Sources/Testing/Support/JSON.swift +++ b/Sources/Testing/Support/JSON.swift @@ -29,7 +29,7 @@ enum JSON { /// - Returns: Whatever is returned by `body`. /// /// - Throws: Whatever is thrown by `body` or by the encoding process. - static func withEncoding(of value: some Encodable, userInfo: [CodingUserInfoKey: any Sendable] = [:], _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { + static func withEncoding(of value: some Encodable, userInfo: [CodingUserInfoKey: any Sendable] = [:], _ body: (borrowing RawSpan) throws -> R) throws -> R { #if canImport(Foundation) let encoder = JSONEncoder() @@ -44,7 +44,7 @@ enum JSON { encoder.userInfo.merge(userInfo, uniquingKeysWith: { _, rhs in rhs}) let data = try encoder.encode(value) - return try data.withUnsafeBytes(body) + return try body(data.bytes) #else throw SystemError(description: "JSON encoding requires Foundation which is not available in this environment.") #endif @@ -60,14 +60,15 @@ enum JSON { /// - Returns: Whatever is returned by `body`. /// /// - Throws: Whatever is thrown by `body`. - static func asJSONLine(_ json: UnsafeRawBufferPointer, _ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { - if _slowPath(json.contains(where: \.isASCIINewline)) { + static func asJSONLine(_ json: borrowing RawSpan, _ body: (borrowing RawSpan) throws -> R) rethrows -> R { + let hasASCIINewline = json.withUnsafeBytes { $0.contains(where: \.isASCIINewline) } + if _slowPath(hasASCIINewline) { // Remove the newline characters to conform to JSON lines specification. // This is not actually expected to happen in practice with Foundation's // JSON encoder. var json = Array(json) json.removeAll(where: \.isASCIINewline) - return try json.withUnsafeBytes(body) + return try json.withBytes(body) } else { // No newlines found, no need to copy the buffer. return try body(json) @@ -83,9 +84,9 @@ enum JSON { /// - Returns: An instance of `T` decoded from `jsonRepresentation`. /// /// - Throws: Whatever is thrown by the decoding process. - static func decode(_ type: T.Type, from jsonRepresentation: UnsafeRawBufferPointer) throws -> T where T: Decodable { + static func decode(_ type: T.Type, from jsonRepresentation: borrowing RawSpan) throws -> T where T: Decodable { #if canImport(Foundation) - try withExtendedLifetime(jsonRepresentation) { + try jsonRepresentation.withUnsafeBytes { jsonRepresentation in let byteCount = jsonRepresentation.count let data = if byteCount > 0 { Data( diff --git a/Sources/Testing/Traits/Tags/Tag.Color+Loading.swift b/Sources/Testing/Traits/Tags/Tag.Color+Loading.swift index 2ab35b107..bda63ab09 100644 --- a/Sources/Testing/Traits/Tags/Tag.Color+Loading.swift +++ b/Sources/Testing/Traits/Tags/Tag.Color+Loading.swift @@ -113,7 +113,7 @@ func loadTagColors(fromFileInDirectoryAtPath swiftTestingDirectoryPath: String? // nil is a valid decoded color value (representing "no color") that we can // use for merging tag color data from multiple sources, but it is not valid // as an actual tag color, so we have a step here that filters it. - return try tagColorsData.withUnsafeBytes { tagColorsData in + return try tagColorsData.withBytes { tagColorsData in try JSON.decode([Tag: Tag.Color?].self, from: tagColorsData) .compactMapValues { $0 } } diff --git a/Tests/TestingTests/ABIEntryPointTests.swift b/Tests/TestingTests/ABIEntryPointTests.swift index 15b9cc879..2c6b59bbd 100644 --- a/Tests/TestingTests/ABIEntryPointTests.swift +++ b/Tests/TestingTests/ABIEntryPointTests.swift @@ -65,7 +65,7 @@ struct ABIEntryPointTests { private func _invokeEntryPointV0( passing arguments: __CommandLineArguments_v0, - recordHandler: @escaping @Sendable (_ recordJSON: UnsafeRawBufferPointer) -> Void = { _ in } + recordHandler: @escaping @Sendable (_ recordJSON: borrowing RawSpan) -> Void = { _ in } ) async throws -> Bool { #if !(os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android)) && !SWT_NO_DYNAMIC_LINKING // Get the ABI entry point by dynamically looking it up at runtime. @@ -84,8 +84,10 @@ struct ABIEntryPointTests { let abiEntryPoint = unsafeBitCast(abiv0_getEntryPoint(), to: ABI.v0.EntryPoint.self) let argumentsJSON = try JSON.withEncoding(of: arguments) { argumentsJSON in - let result = UnsafeMutableRawBufferPointer.allocate(byteCount: argumentsJSON.count, alignment: 1) - result.copyMemory(from: argumentsJSON) + let result = UnsafeMutableRawBufferPointer.allocate(byteCount: argumentsJSON.byteCount, alignment: 1) + argumentsJSON.withUnsafeBytes { argumentsJSON in + result.copyMemory(from: argumentsJSON) + } return result } defer { @@ -93,12 +95,14 @@ struct ABIEntryPointTests { } // Call the entry point function. - return try await abiEntryPoint(.init(argumentsJSON), recordHandler) + return try await abiEntryPoint(.init(argumentsJSON)) { recordJSON in + recordHandler(RawSpan(_unsafeBytes: recordJSON)) + } } #if canImport(Foundation) @Test func decodeEmptyConfiguration() throws { - let emptyBuffer = UnsafeRawBufferPointer(start: nil, count: 0) + let emptyBuffer = RawSpan() #expect(throws: DecodingError.self) { _ = try JSON.decode(__CommandLineArguments_v0.self, from: emptyBuffer) } diff --git a/Tests/TestingTests/AttachmentTests.swift b/Tests/TestingTests/AttachmentTests.swift index 7695dd634..b2af68499 100644 --- a/Tests/TestingTests/AttachmentTests.swift +++ b/Tests/TestingTests/AttachmentTests.swift @@ -282,8 +282,8 @@ struct AttachmentTests { #expect(attachment.preferredName == temporaryFileName) #expect(throws: Never.self) { - try attachment.withUnsafeBytes { buffer in - #expect(buffer.count == data.count) + try attachment.withBytes { buffer in + #expect(buffer.byteCount == data.count) } } valueAttached() @@ -314,7 +314,8 @@ struct AttachmentTests { } #expect(attachment.preferredName == "\(temporaryDirectoryName).zip") - try! attachment.withUnsafeBytes { buffer in + try! attachment.withBytes { buffer in + let buffer = Array(buffer) #expect(buffer.count > 32) #expect(buffer[0] == UInt8(ascii: "P")) #expect(buffer[1] == UInt8(ascii: "K")) @@ -452,9 +453,9 @@ extension AttachmentTests { func test(_ value: some Attachable) throws { #expect(value.estimatedAttachmentByteCount == 6) let attachment = Attachment(value) - try attachment.withUnsafeBytes { buffer in - #expect(buffer.elementsEqual("abc123".utf8)) - #expect(buffer.count == 6) + try attachment.withBytes { buffer in + #expect(Array(buffer).elementsEqual("abc123".utf8)) + #expect(buffer.byteCount == 6) } } @@ -711,7 +712,7 @@ extension AttachmentTests { } let attachment = Attachment(icon, named: "diamond.jpeg") - try attachment.withUnsafeBytes { buffer in + try attachment.withBytes { buffer in #expect(buffer.count > 32) } } @@ -751,7 +752,7 @@ extension AttachmentTests { } let attachment = Attachment(bitmap, named: "diamond.png") - try attachment.withUnsafeBytes { buffer in + try attachment.withBytes { buffer in #expect(buffer.count > 32) } Attachment.record(attachment) @@ -799,7 +800,7 @@ extension AttachmentTests { } let attachment = Attachment(wicBitmap, named: "diamond.png") - try attachment.withUnsafeBytes { buffer in + try attachment.withBytes { buffer in #expect(buffer.count > 32) } Attachment.record(attachment) @@ -812,7 +813,7 @@ extension AttachmentTests { } let attachment = Attachment(wicBitmapSource, named: "diamond.png") - try attachment.withUnsafeBytes { buffer in + try attachment.withBytes { buffer in #expect(buffer.count > 32) } Attachment.record(attachment) @@ -851,14 +852,18 @@ struct MyAttachable: Attachable, ~Copyable { var string: String var errorToThrow: (any Error)? - func withUnsafeBytes(for attachment: borrowing Attachment, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { + borrowing func withUnsafeBytes(for attachment: borrowing Attachment, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { + try default_withUnsafeBytes(for: attachment, body) + } + + borrowing func withBytes(for attachment: borrowing Attachment, _ body: (borrowing RawSpan) throws -> R) throws -> R { if let errorToThrow { throw errorToThrow } var string = string return try string.withUTF8 { buffer in - try body(.init(buffer)) + try body(RawSpan(_unsafeElements: buffer)) } } } @@ -869,11 +874,15 @@ extension MyAttachable: Sendable {} struct MySendableAttachable: Attachable, Sendable { var string: String - func withUnsafeBytes(for attachment: borrowing Attachment, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { + borrowing func withUnsafeBytes(for attachment: borrowing Attachment, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { + try default_withUnsafeBytes(for: attachment, body) + } + + borrowing func withBytes(for attachment: borrowing Attachment, _ body: (borrowing RawSpan) throws -> R) throws -> R { #expect(attachment.attachableValue.string == string) var string = string return try string.withUTF8 { buffer in - try body(.init(buffer)) + try body(RawSpan(_unsafeElements: buffer)) } } } @@ -881,10 +890,14 @@ struct MySendableAttachable: Attachable, Sendable { struct MySendableAttachableWithDefaultByteCount: Attachable, Sendable { var string: String - func withUnsafeBytes(for attachment: borrowing Attachment, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { + borrowing func withUnsafeBytes(for attachment: borrowing Attachment, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { + try default_withUnsafeBytes(for: attachment, body) + } + + borrowing func withBytes(for attachment: borrowing Attachment, _ body: (borrowing RawSpan) throws -> R) throws -> R { var string = string return try string.withUTF8 { buffer in - try body(.init(buffer)) + try body(RawSpan(_unsafeElements: buffer)) } } } diff --git a/Tests/TestingTests/ExitTestTests.swift b/Tests/TestingTests/ExitTestTests.swift index 5be229266..63cc67246 100644 --- a/Tests/TestingTests/ExitTestTests.swift +++ b/Tests/TestingTests/ExitTestTests.swift @@ -226,7 +226,7 @@ private import _TestingInternals return } #expect(throws: Never.self) { - try attachment.withUnsafeBytes { bytes in + try attachment.withBytes { bytes in #expect(Array(bytes) == Self.attachmentPayload) } } diff --git a/Tests/TestingTests/SwiftPMTests.swift b/Tests/TestingTests/SwiftPMTests.swift index 4668fbb25..43e5d9109 100644 --- a/Tests/TestingTests/SwiftPMTests.swift +++ b/Tests/TestingTests/SwiftPMTests.swift @@ -26,7 +26,7 @@ private func decodedEventStreamRecords(fromPath filePath: String try FileHandle(forReadingAtPath: filePath).readToEnd() .split(whereSeparator: \.isASCIINewline) .map { line in - try line.withUnsafeBytes { line in + try line.withBytes { line in return try JSON.decode(ABI.Record.self, from: line) } } diff --git a/Tests/TestingTests/Test.Case.Argument.IDTests.swift b/Tests/TestingTests/Test.Case.Argument.IDTests.swift index 052213912..f16fee0da 100644 --- a/Tests/TestingTests/Test.Case.Argument.IDTests.swift +++ b/Tests/TestingTests/Test.Case.Argument.IDTests.swift @@ -38,8 +38,8 @@ struct Test_Case_Argument_IDTests { #expect(arguments.count == 1) let argument = try #require(arguments.first) #if canImport(Foundation) - let decodedArgument = try argument.id.bytes.withUnsafeBufferPointer { argumentID in - try JSON.decode(MyCustomTestArgument.self, from: .init(argumentID)) + let decodedArgument = try argument.id.bytes.withBytes { argumentID in + try JSON.decode(MyCustomTestArgument.self, from: argumentID) } #expect(decodedArgument == MyCustomTestArgument(x: 123, y: "abc")) #endif diff --git a/Tests/TestingTests/TestSupport/TestingAdditions.swift b/Tests/TestingTests/TestSupport/TestingAdditions.swift index 05bb05dc8..afc4a0eb9 100644 --- a/Tests/TestingTests/TestSupport/TestingAdditions.swift +++ b/Tests/TestingTests/TestSupport/TestingAdditions.swift @@ -408,9 +408,7 @@ extension JSON { /// - Throws: Whatever is thrown by the decoding process. @_disfavoredOverload static func decode(_ type: T.Type, from jsonRepresentation: Data) throws -> T where T: Decodable { - try jsonRepresentation.withUnsafeBytes { bytes in - try JSON.decode(type, from: bytes) - } + try JSON.decode(type, from: jsonRepresentation.bytes) } #endif }