diff --git a/Sources/CMakeLists.txt b/Sources/CMakeLists.txt index 09e5e9fd6..4fc0847b7 100644 --- a/Sources/CMakeLists.txt +++ b/Sources/CMakeLists.txt @@ -104,6 +104,7 @@ endif() include(AvailabilityDefinitions) include(CompilerSettings) add_subdirectory(_TestDiscovery) +add_subdirectory(_TestingInterop) add_subdirectory(_TestingInternals) add_subdirectory(Overlays) add_subdirectory(Testing) diff --git a/Sources/_TestingInterop/CMakeLists.txt b/Sources/_TestingInterop/CMakeLists.txt new file mode 100644 index 000000000..adbf7037b --- /dev/null +++ b/Sources/_TestingInterop/CMakeLists.txt @@ -0,0 +1,24 @@ +# This source file is part of the Swift.org open source project +# +# Copyright (c) 2025 Apple Inc. and the Swift project authors +# Licensed under Apache License v2.0 with Runtime Library Exception +# +# See http://swift.org/LICENSE.txt for license information +# See http://swift.org/CONTRIBUTORS.txt for Swift project authors + +add_library(_TestingInterop + FallbackEventHandler.swift) + +target_link_libraries(_TestingInterop PRIVATE + _TestingInternals) +if(NOT BUILD_SHARED_LIBS) + # When building a static library, tell clients to autolink the internal + # libraries. + target_compile_options(_TestingInterop PRIVATE + "SHELL:-Xfrontend -public-autolink-library -Xfrontend _TestingInternals") +endif() +target_compile_options(_TestingInterop PRIVATE + -enable-library-evolution + -emit-module-interface -emit-module-interface-path $/_TestingInterop.swiftinterface) + +_swift_testing_install_target(_TestingInterop) diff --git a/Sources/_TestingInterop/FallbackEventHandler.swift b/Sources/_TestingInterop/FallbackEventHandler.swift new file mode 100644 index 000000000..2e33cd04d --- /dev/null +++ b/Sources/_TestingInterop/FallbackEventHandler.swift @@ -0,0 +1,106 @@ +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 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 !SWT_NO_INTEROP +#if SWT_TARGET_OS_APPLE && !SWT_NO_OS_UNFAIR_LOCK +private import _TestingInternals +#else +private import Synchronization +#endif + +#if SWT_TARGET_OS_APPLE && !SWT_NO_OS_UNFAIR_LOCK +/// The installed event handler. +private nonisolated(unsafe) let _fallbackEventHandler = { + let result = ManagedBuffer.create( + minimumCapacity: 1, + makingHeaderWith: { _ in nil } + ) + result.withUnsafeMutablePointerToHeader { $0.initialize(to: nil) } + return result +}() +#else +/// The installed event handler. +private nonisolated(unsafe) let _fallbackEventHandler = Atomic(nil) +#endif + +/// A type describing a fallback event handler that testing API can invoke as an +/// alternate method of reporting test events to the current test runner. +/// +/// For example, an `XCTAssert` failure in the body of a Swift Testing test +/// cannot record issues directly with the Swift Testing runner. Instead, the +/// framework packages the assertion failure as a JSON `Event` and invokes this +/// handler to report the failure. +/// +/// - Parameters: +/// - recordJSONSchemaVersionNumber: The JSON schema version used to encode +/// the event record. +/// - recordJSONBaseAddress: A pointer to the first byte of the encoded event. +/// - recordJSONByteCount: The size of the encoded event in bytes. +/// - reserved: Reserved for future use. +@usableFromInline +package typealias FallbackEventHandler = @Sendable @convention(c) ( + _ recordJSONSchemaVersionNumber: UnsafePointer, + _ recordJSONBaseAddress: UnsafeRawPointer, + _ recordJSONByteCount: Int, + _ reserved: UnsafeRawPointer? +) -> Void + +/// Get the current fallback event handler. +/// +/// - Returns: The currently-set handler function, if any. +@_cdecl("_swift_testing_getFallbackEventHandler") +@usableFromInline +package func _swift_testing_getFallbackEventHandler() -> FallbackEventHandler? { +#if SWT_TARGET_OS_APPLE && !SWT_NO_OS_UNFAIR_LOCK + return _fallbackEventHandler.withUnsafeMutablePointers { fallbackEventHandler, lock in + os_unfair_lock_lock(lock) + defer { + os_unfair_lock_unlock(lock) + } + return fallbackEventHandler.pointee + } +#else + return _fallbackEventHandler.load(ordering: .sequentiallyConsistent).flatMap { fallbackEventHandler in + unsafeBitCast(fallbackEventHandler, to: FallbackEventHandler?.self) + } +#endif +} + +/// Set the current fallback event handler if one has not already been set. +/// +/// - Parameters: +/// - handler: The handler function to set. +/// +/// - Returns: Whether or not `handler` was installed. +/// +/// The fallback event handler can only be installed once per process, typically +/// by the first testing library to run. If this function has already been +/// called and the handler set, it does not replace the previous handler. +@_cdecl("_swift_testing_installFallbackEventHandler") +@usableFromInline +package func _swift_testing_installFallbackEventHandler(_ handler: FallbackEventHandler) -> CBool { +#if SWT_TARGET_OS_APPLE && !SWT_NO_OS_UNFAIR_LOCK + return _fallbackEventHandler.withUnsafeMutablePointers { fallbackEventHandler, lock in + os_unfair_lock_lock(lock) + defer { + os_unfair_lock_unlock(lock) + } + guard fallbackEventHandler.pointee == nil else { + return false + } + fallbackEventHandler.pointee = handler + return true + } +#else + let handler = unsafeBitCast(handler, to: UnsafeRawPointer.self) + return _fallbackEventHandler.compareExchange(expected: nil, desired: handler, ordering: .sequentiallyConsistent).exchanged +#endif +} +#endif