From 3c092d0435c249daf86e4afd90768ea962ce27bc Mon Sep 17 00:00:00 2001 From: Jerry Chen Date: Fri, 21 Nov 2025 19:36:13 -0800 Subject: [PATCH 1/2] Create helper to post test events to fallback event handler As part of the strategy for enabling interoperability, unhandled issues can be turned into events and sent to the fallback event handler. Also link against the _TestingInterop framework/library (depending on the platform), which provides a function to lookup the currently installed fallback event handler. --- Package.swift | 17 +++++-- Sources/Testing/CMakeLists.txt | 1 + .../Events/Event+FallbackHandler.swift | 48 +++++++++++++++++++ Sources/_TestingInternals/include/Stubs.h | 26 ++++++++++ .../FallbackEventHandler.swift | 2 +- 5 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 Sources/Testing/Events/Event+FallbackHandler.swift 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/_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 From 6736ad8d6b9a58dcec4166410bab18fe60edc6a6 Mon Sep 17 00:00:00 2001 From: Jerry Chen Date: Wed, 10 Dec 2025 15:23:46 -0800 Subject: [PATCH 2/2] Try sending event to the fallback handler if no active configuration --- Sources/Testing/Events/Event.swift | 2 ++ 1 file changed, 2 insertions(+) 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.