diff --git a/Package.swift b/Package.swift index a82241527..cd73041ef 100644 --- a/Package.swift +++ b/Package.swift @@ -142,9 +142,20 @@ let package = Package( exclude: ["CMakeLists.txt", "Testing.swiftcrossimport"], cxxSettings: .packageSettings, swiftSettings: .packageSettings + .enableLibraryEvolution(), - linkerSettings: [ - .linkedLibrary("execinfo", .when(platforms: [.custom("freebsd"), .openbsd])) - ] + linkerSettings: { + var result = [LinkerSetting]() + result += [ + .linkedLibrary("execinfo", .when(platforms: [.custom("freebsd"), .openbsd])) + ] +#if compiler(>=6.3) + result += [ + .linkedFramework("_TestingInterop", .whenApple()), + .linkedLibrary("_TestingInterop", .whenApple(false)), + ] +#endif + + return result + }() ), .testTarget( name: "TestingTests", diff --git a/Sources/Testing/CMakeLists.txt b/Sources/Testing/CMakeLists.txt index b60688731..bca9e183e 100644 --- a/Sources/Testing/CMakeLists.txt +++ b/Sources/Testing/CMakeLists.txt @@ -31,6 +31,7 @@ add_library(Testing Attachments/Attachment.swift Events/Clock.swift Events/Event.swift + Events/Event+FallbackHandler.swift Events/Recorder/Event.AdvancedConsoleOutputRecorder.swift Events/Recorder/Event.ConsoleOutputRecorder.swift Events/Recorder/Event.HumanReadableOutputRecorder.swift diff --git a/Sources/Testing/Events/Event+FallbackHandler.swift b/Sources/Testing/Events/Event+FallbackHandler.swift new file mode 100644 index 000000000..51ca2b3d6 --- /dev/null +++ b/Sources/Testing/Events/Event+FallbackHandler.swift @@ -0,0 +1,48 @@ +// +// 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 +// + +private import _TestingInternals + +extension Event { + private static let _fallbackEventHandler: FallbackEventHandler? = { + _swift_testing_getFallbackEventHandler() + }() + + /// Post this event to the currently-installed fallback event handler. + /// + /// - Parameters: + /// - context: The context associated with this event. + /// + /// - Returns: Whether or not the fallback event handler was invoked. If the + /// currently-installed handler belongs to the testing library, returns + /// `false`. + borrowing func postToFallbackHandler(in context: borrowing Context) -> Bool { +#if canImport(_TestingInterop) + guard let fallbackEventHandler = Self._fallbackEventHandler else { + return false + } + + // Encode the event as JSON and pass it to the handler. + let encodeAndInvoke = ABI.CurrentVersion.eventHandler(encodeAsJSONLines: false) { + recordJSON in + fallbackEventHandler( + String(describing: ABI.CurrentVersion.versionNumber), + recordJSON.baseAddress!, + recordJSON.count, + nil + ) + } + encodeAndInvoke(self, context) + return true +#else + return false +#endif + } +} diff --git a/Sources/Testing/Events/Event.swift b/Sources/Testing/Events/Event.swift index c6285f4f4..275182043 100644 --- a/Sources/Testing/Events/Event.swift +++ b/Sources/Testing/Events/Event.swift @@ -326,6 +326,8 @@ extension Event { if configuration.eventHandlingOptions.shouldHandleEvent(self) { configuration.handleEvent(self, in: context) } + } else if postToFallbackHandler(in: context) { + // The fallback event handler handled this event. } else { // The current task does NOT have an associated configuration. This event // will be lost! Post it to every registered event handler to avoid that. diff --git a/Sources/_TestingInternals/include/Stubs.h b/Sources/_TestingInternals/include/Stubs.h index 636ea9aff..07f0536cd 100644 --- a/Sources/_TestingInternals/include/Stubs.h +++ b/Sources/_TestingInternals/include/Stubs.h @@ -180,6 +180,32 @@ static int swt_setfdflags(int fd, int flags) { } #endif +// __SWIFT_COMPILER_VERSION is a packed value. The comparison below is >= 6.3 +#if __SWIFT_COMPILER_VERSION >= 6003000000000 && !SWT_NO_INTEROP + +/// 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. +/// Shadows the type with the same name in _TestingInterop. +/// +/// - 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. +typedef void (* FallbackEventHandler)(const char *recordJSONSchemaVersionNumber, + const void *recordJSONBaseAddress, + long recordJSONByteCount, + const void *_Nullable reserved); + +/// Get the current fallback event handler. +/// Shadows the function with the same name in _TestingInterop. +/// +/// - Returns: The currently-set handler function, if any. +SWT_EXTERN FallbackEventHandler _Nullable _swift_testing_getFallbackEventHandler(void); + +#endif + SWT_ASSUME_NONNULL_END #endif diff --git a/Sources/_TestingInterop/FallbackEventHandler.swift b/Sources/_TestingInterop/FallbackEventHandler.swift index 9408bbdfb..eabf31f58 100644 --- a/Sources/_TestingInterop/FallbackEventHandler.swift +++ b/Sources/_TestingInterop/FallbackEventHandler.swift @@ -8,7 +8,7 @@ // See https://swift.org/CONTRIBUTORS.txt for Swift project authors // -#if !SWT_NO_INTEROP +#if compiler(>=6.3) && !SWT_NO_INTEROP #if SWT_TARGET_OS_APPLE && !SWT_NO_OS_UNFAIR_LOCK && !hasFeature(Embedded) private import _TestingInternals #else