Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
11 changes: 11 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ let package = Package(
"_Testing_AppKit",
"_Testing_CoreGraphics",
"_Testing_CoreImage",
"_Testing_CoreTransferable",
"_Testing_Foundation",
"_Testing_UIKit",
"_Testing_WinSDK",
Expand Down Expand Up @@ -264,6 +265,15 @@ let package = Package(
exclude: ["CMakeLists.txt"],
swiftSettings: .packageSettings + .enableLibraryEvolution() + .moduleABIName("_Testing_CoreImage")
),
.target(
name: "_Testing_CoreTransferable",
dependencies: [
"Testing",
],
path: "Sources/Overlays/_Testing_CoreTransferable",
exclude: ["CMakeLists.txt"],
swiftSettings: .packageSettings + .enableLibraryEvolution() + .moduleABIName("_Testing_CoreTransferable")
),
.target(
name: "_Testing_Foundation",
dependencies: [
Expand Down Expand Up @@ -426,6 +436,7 @@ extension Array where Element == PackageDescription.SwiftSetting {
.enableExperimentalFeature("AvailabilityMacro=_uttypesAPI:macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0"),
.enableExperimentalFeature("AvailabilityMacro=_clockAPI:macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0"),
.enableExperimentalFeature("AvailabilityMacro=_typedThrowsAPI:macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0"),
.enableExperimentalFeature("AvailabilityMacro=_transferableAPI:macOS 15.2, iOS 18.2, watchOS 11.2, tvOS 18.2, visionOS 2.2"),
.enableExperimentalFeature("AvailabilityMacro=_castingWithNonCopyableGenerics:macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0"),

.enableExperimentalFeature("AvailabilityMacro=_distantFuture:macOS 99.0, iOS 99.0, watchOS 99.0, tvOS 99.0, visionOS 99.0"),
Expand Down
1 change: 1 addition & 0 deletions Sources/Overlays/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
add_subdirectory(_Testing_AppKit)
add_subdirectory(_Testing_CoreGraphics)
add_subdirectory(_Testing_CoreImage)
add_subdirectory(_Testing_CoreTransferable)
add_subdirectory(_Testing_Foundation)
add_subdirectory(_Testing_UIKit)
add_subdirectory(_Testing_WinSDK)
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2026 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(CoreTransferable)
public import Testing
public import CoreTransferable

public import UniformTypeIdentifiers

@_spi(Experimental)
@available(_transferableAPI, *)
extension Attachment {
/// Initialize an instance of this type that encloses the given transferable
/// value.
///
/// - Parameters:
/// - transferableValue: The value that will be attached to the output of
/// the test run.
/// - contentType: The content type with which to export `transferableValue`.
/// If this argument is `nil`, the testing library calls [`exportedContentTypes(_:)`](https://developer.apple.com/documentation/coretransferable/transferable/exportedcontenttypes(_:))
/// on `transferableValue` and uses the first type the function returns
/// that conforms to [`UTType.data`](https://developer.apple.com/documentation/uniformtypeidentifiers/uttype-swift.struct/data).
/// - preferredName: The preferred name of the attachment to use when saving
/// it. If `nil`, the testing library attempts to generate a reasonable
/// filename for the attached value.
/// - sourceLocation: The source location of the call to this initializer.
/// This value is used when recording issues associated with the
/// attachment.
///
/// - Throws: Any error that occurs while exporting `transferableValue`.
///
/// Use this initializer to create an instance of ``Attachment`` from a value
/// that conforms to the [`Transferable`](https://developer.apple.com/documentation/coretransferable/transferable)
/// protocol.
///
/// ```swift
/// let menu = FoodTruck.menu
/// let attachment = try await Attachment(exporting: menu, as: .pdf)
/// Attachment.record(attachment)
/// ```
///
/// When you call this initializer and pass it a transferable value, it
/// calls [`exported(as:)`](https://developer.apple.com/documentation/coretransferable/transferable/exported(as:))
/// on that value. This operation may take some time, so this initializer
/// suspends the calling task until it is complete.
public init<T>(
exporting transferableValue: T,
as contentType: UTType? = nil,
named preferredName: String? = nil,
sourceLocation: SourceLocation = #_sourceLocation
) async throws where T: Transferable, AttachableValue == _AttachableTransferableWrapper<T> {
let transferableWrapper = try await _AttachableTransferableWrapper(exporting: transferableValue, as: contentType)
self.init(transferableWrapper, named: preferredName, sourceLocation: sourceLocation)
}
}

// MARK: -

/// A type describing errors that can occur when attaching a transferable value.
enum TransferableAttachmentError: Error {
/// The developer did not pass a content type and the value did not list any
/// that conform to `UTType.data`.
case suitableContentTypeNotFound
}

extension TransferableAttachmentError: CustomStringConvertible {
var description: String {
switch self {
case .suitableContentTypeNotFound:
"The value does not list any exported content types that conform to 'UTType.data'."
}
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2026 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(CoreTransferable)
Comment thread
grynspan marked this conversation as resolved.
public import Testing
public import CoreTransferable

private import Foundation
import UniformTypeIdentifiers

/// A wrapper type representing transferable values that can be attached
/// indirectly.
///
/// You do not need to use this type directly. Instead, initialize an instance
/// of ``Attachment`` using an instance of a type conforming to the [`Transferable`](https://developer.apple.com/documentation/coretransferable/transferable)
/// protocol.
@_spi(Experimental)
@available(_transferableAPI, *)
public struct _AttachableTransferableWrapper<T>: Sendable where T: Transferable {
/// The transferable value.
private var _transferableValue: T

/// The content type used to export the transferable value.
private var _contentType: UTType

/// The exported form of the transferable value.
private var _bytes: Data

init(exporting transferableValue: T, as contentType: UTType?) async throws {
let contentType = contentType ?? transferableValue.exportedContentTypes()
.first { $0.conforms(to: .data) }
guard let contentType else {
throw TransferableAttachmentError.suitableContentTypeNotFound
}

_transferableValue = transferableValue
_contentType = contentType
_bytes = try await transferableValue.exported(as: contentType)
}
}

// MARK: -

@_spi(Experimental)
@available(_transferableAPI, *)
extension _AttachableTransferableWrapper: AttachableWrapper {
public var wrappedValue: T {
_transferableValue
}

public func withUnsafeBytes<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
try _bytes.withUnsafeBytes(body)
}

public borrowing func preferredName(for attachment: borrowing Attachment<Self>, basedOn suggestedName: String) -> String {
let baseName = _transferableValue.suggestedFilename ?? suggestedName
return (baseName as NSString).appendingPathExtension(for: _contentType)
}
}
#endif
25 changes: 25 additions & 0 deletions Sources/Overlays/_Testing_CoreTransferable/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# This source file is part of the Swift.org open source project
#
# Copyright (c) 2026 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 (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
include(ModuleABIName)
Comment thread
grynspan marked this conversation as resolved.
Outdated
add_library(_Testing_CoreTransferable
Attachments/_AttachableTransferableWrapper.swift
Attachments/Attachment+Transferable.swift
ReexportTesting.swift)

target_link_libraries(_Testing_CoreTransferable PUBLIC
Testing
_Testing_CoreGraphics)
Comment thread
grynspan marked this conversation as resolved.
Outdated

target_compile_options(_Testing_CoreTransferable PRIVATE
-enable-library-evolution
-emit-module-interface -emit-module-interface-path $<TARGET_PROPERTY:_Testing_CoreTransferable,Swift_MODULE_DIRECTORY>/_Testing_CoreTransferable.swiftinterface)

_swift_testing_install_target(_Testing_CoreTransferable)
endif()
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2026 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
//

@_exported @_spi(Experimental) @_spi(ForToolsIntegrationOnly) public import Testing
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
version: 1
modules:
- name: _Testing_CoreTransferable
65 changes: 64 additions & 1 deletion Tests/TestingTests/AttachmentTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ import CoreGraphics
import CoreImage
import _Testing_CoreImage
#endif
#if canImport(CoreTransferable) && canImport(_Testing_CoreTransferable)
import CoreTransferable
@_spi(Experimental) import _Testing_CoreTransferable
#endif
#if canImport(UIKit) && canImport(_Testing_UIKit)
import UIKit
import _Testing_UIKit
Expand Down Expand Up @@ -258,7 +262,7 @@ struct AttachmentTests {

#expect((attachment.attachableValue as Any) is AnyAttachable.Wrapped)
#expect(attachment.sourceLocation.fileID == #fileID)
valueAttached()
valueAttached()
}

await Test {
Expand Down Expand Up @@ -486,6 +490,45 @@ struct AttachmentTests {
}
}
#endif

#if canImport(CoreTransferable) && canImport(_Testing_CoreTransferable)
@available(_transferableAPI, *)
@Test("Attach Transferable-conformant value")
func transferable() async throws {
let value = MyTransferable()
let attachment = try await Attachment(exporting: value, as: .plainText)
#expect(value == attachment.attachableValue)
try attachment.withUnsafeBytes { bytes in
#expect(Array(bytes) == Array(MyTransferable.stringValue.utf8))
}
}

@available(_transferableAPI, *)
@Test("Attach Transferable-conformant value with a nonsensical type")
func transferableWithNonsensicalType() async throws {
let value = MyTransferable()
await #expect(throws: (any Error).self) {
_ = try await Attachment(exporting: value, as: .gif)
}
}

@available(_transferableAPI, *)
@Test("Preferred name of Transferable-conformant value")
func transferablePreferredName() async throws {
let value = MyTransferable()
let attachment = try await Attachment(exporting: value)
#expect(attachment.preferredName == "untitled.txt")
}

@available(_transferableAPI, *)
@Test("Attach Transferable-conformant value with no available type")
func transferableWithNoAvailableType() async throws {
let value = MyBadTransferable()
await #expect(throws: (any Error).self) {
_ = try await Attachment(exporting: value)
}
}
#endif
}

extension AttachmentTests {
Expand Down Expand Up @@ -993,6 +1036,26 @@ struct MySendableAttachableWithDefaultByteCount: Attachable, Sendable {
}
}

#if canImport(CoreTransferable) && canImport(_Testing_CoreTransferable)
struct MyTransferable: Transferable, Equatable {
static let stringValue = "This isn't even my exported form!"

static var transferRepresentation: some TransferRepresentation {
DataRepresentation(exportedContentType: .plainText) { instance in
Data(Self.stringValue.utf8)
}
}
}

struct MyBadTransferable: Transferable, Equatable {
static var transferRepresentation: some TransferRepresentation {
DataRepresentation(exportedContentType: .directory) { _ in
throw MyError()
}
}
}
#endif

#if canImport(Foundation) && canImport(_Testing_Foundation)
struct MyCodableAttachable: Codable, Attachable, Sendable {
var string: String
Expand Down
1 change: 1 addition & 0 deletions cmake/modules/shared/AvailabilityDefinitions.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ add_compile_options(
"SHELL:$<$<COMPILE_LANGUAGE:Swift>:-Xfrontend -define-availability -Xfrontend \"_uttypesAPI:macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0\">"
"SHELL:$<$<COMPILE_LANGUAGE:Swift>:-Xfrontend -define-availability -Xfrontend \"_clockAPI:macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0\">"
"SHELL:$<$<COMPILE_LANGUAGE:Swift>:-Xfrontend -define-availability -Xfrontend \"_typedThrowsAPI:macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0\">"
"SHELL:$<$<COMPILE_LANGUAGE:Swift>:-Xfrontend -define-availability -Xfrontend \"_transferableAPI:macOS 15.2, iOS 18.2, watchOS 11.2, tvOS 18.2, visionOS 2.2\">"
"SHELL:$<$<COMPILE_LANGUAGE:Swift>:-Xfrontend -define-availability -Xfrontend \"_castingWithNonCopyableGenerics:macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0\">"
"SHELL:$<$<COMPILE_LANGUAGE:Swift>:-Xfrontend -define-availability -Xfrontend \"_distantFuture:macOS 99.0, iOS 99.0, watchOS 99.0, tvOS 99.0, visionOS 99.0\">")