Skip to content

Commit 6a4a3a9

Browse files
authored
Create fallback event handler library for Swift Testing and XCTest interop (#1369)
This library will eventually be visible in the toolchain, and either testing library (Swift Testing or XCTest) will be able to use it to pass along unhandled issues to a test runner from the other framework, enabling interoperability. ### Modifications: No tests are included in this change because the interop library must make it into the toolchain first before we can write tests against it. Tests will be coming in the near future! ### Checklist: - [x] Code and documentation should follow the style of the [Style Guide](https://github.com/apple/swift-testing/blob/main/Documentation/StyleGuide.md). - [x] If public symbols are renamed or modified, DocC references should be updated. - [x] Toolchain build succeeds - [x] Expected symbols are visible in the toolchain build* Ubuntu: ``` ~/Downloads/usr ❯ nm lib/swift/linux/lib_TestingInterop.so | rg handler • <stdin> 1:0000000000002140 b $s15_TestingInterop21_fallbackEventHandler33_AF82E98923290C49994CD1CA5A7BD647LL15Synchronization6AtomicVySVSgGvp 2:0000000000000b50 T $s15_TestingInterop38_swift_testing_getFallbackEventHandlerySPys4Int8VG_SVSiSVSgtYbXCSgyF 3:0000000000000b70 T $s15_TestingInterop42_swift_testing_installFallbackEventHandlerySbySPys4Int8VG_SVSiSVSgtYbXCF 57:0000000000000b40 T _swift_testing_getFallbackEventHandler 58:0000000000000b60 T _swift_testing_installFallbackEventHandler ``` macOS: ``` ~/Downloads/Library ❯ nm Developer/Toolchains/swift-PR-84971-2110.xctoolchain/usr/lib/swift/macosx/testing/lib_TestingInterop.dylib | rg handler • <stdin> 1:0000000000003bbc t _$s15_TestingInterop21_fallbackEventHandler33_AF82E98923290C49994CD1CA5A7BD647LL_WZ 2:0000000000008010 b _$s15_TestingInterop21_fallbackEventHandler33_AF82E98923290C49994CD1CA5A7BD647LL_Wz 3:0000000000008018 b _$s15_TestingInterop21_fallbackEventHandler33_AF82E98923290C49994CD1CA5A7BD647LLs13ManagedBufferCyySPys4Int8VG_SVSiSVSgtYbXCSgSo16os_unfair_lock_sVGvp 4:0000000000003c50 T _$s15_TestingInterop38_swift_testing_getFallbackEventHandlerySPys4Int8VG_SVSiSVSgtYbXCSgyF 5:0000000000003d24 T _$s15_TestingInterop42_swift_testing_installFallbackEventHandlerySbySPys4Int8VG_SVSiSVSgtYbXCF 46:0000000000003bf0 T __swift_testing_getFallbackEventHandler 47:0000000000003cb0 T __swift_testing_installFallbackEventHandler ``` *Windows does not have the interop lib yet since it requires changes to https://github.com/swiftlang/swift-installer-scripts. This will be done in a follow-up :)
1 parent 38067d6 commit 6a4a3a9

File tree

3 files changed

+131
-0
lines changed

3 files changed

+131
-0
lines changed

Sources/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ endif()
104104
include(AvailabilityDefinitions)
105105
include(CompilerSettings)
106106
add_subdirectory(_TestDiscovery)
107+
add_subdirectory(_TestingInterop)
107108
add_subdirectory(_TestingInternals)
108109
add_subdirectory(Overlays)
109110
add_subdirectory(Testing)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# This source file is part of the Swift.org open source project
2+
#
3+
# Copyright (c) 2025 Apple Inc. and the Swift project authors
4+
# Licensed under Apache License v2.0 with Runtime Library Exception
5+
#
6+
# See http://swift.org/LICENSE.txt for license information
7+
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors
8+
9+
add_library(_TestingInterop
10+
FallbackEventHandler.swift)
11+
12+
target_link_libraries(_TestingInterop PRIVATE
13+
_TestingInternals)
14+
if(NOT BUILD_SHARED_LIBS)
15+
# When building a static library, tell clients to autolink the internal
16+
# libraries.
17+
target_compile_options(_TestingInterop PRIVATE
18+
"SHELL:-Xfrontend -public-autolink-library -Xfrontend _TestingInternals")
19+
endif()
20+
target_compile_options(_TestingInterop PRIVATE
21+
-enable-library-evolution
22+
-emit-module-interface -emit-module-interface-path $<TARGET_PROPERTY:_TestingInterop,Swift_MODULE_DIRECTORY>/_TestingInterop.swiftinterface)
23+
24+
_swift_testing_install_target(_TestingInterop)
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
//
2+
// This source file is part of the Swift.org open source project
3+
//
4+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
5+
// Licensed under Apache License v2.0 with Runtime Library Exception
6+
//
7+
// See https://swift.org/LICENSE.txt for license information
8+
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
//
10+
11+
#if !SWT_NO_INTEROP
12+
#if SWT_TARGET_OS_APPLE && !SWT_NO_OS_UNFAIR_LOCK
13+
private import _TestingInternals
14+
#else
15+
private import Synchronization
16+
#endif
17+
18+
#if SWT_TARGET_OS_APPLE && !SWT_NO_OS_UNFAIR_LOCK
19+
/// The installed event handler.
20+
private nonisolated(unsafe) let _fallbackEventHandler = {
21+
let result = ManagedBuffer<FallbackEventHandler?, os_unfair_lock>.create(
22+
minimumCapacity: 1,
23+
makingHeaderWith: { _ in nil }
24+
)
25+
result.withUnsafeMutablePointerToHeader { $0.initialize(to: nil) }
26+
return result
27+
}()
28+
#else
29+
/// The installed event handler.
30+
private nonisolated(unsafe) let _fallbackEventHandler = Atomic<UnsafeRawPointer?>(nil)
31+
#endif
32+
33+
/// A type describing a fallback event handler that testing API can invoke as an
34+
/// alternate method of reporting test events to the current test runner.
35+
///
36+
/// For example, an `XCTAssert` failure in the body of a Swift Testing test
37+
/// cannot record issues directly with the Swift Testing runner. Instead, the
38+
/// framework packages the assertion failure as a JSON `Event` and invokes this
39+
/// handler to report the failure.
40+
///
41+
/// - Parameters:
42+
/// - recordJSONSchemaVersionNumber: The JSON schema version used to encode
43+
/// the event record.
44+
/// - recordJSONBaseAddress: A pointer to the first byte of the encoded event.
45+
/// - recordJSONByteCount: The size of the encoded event in bytes.
46+
/// - reserved: Reserved for future use.
47+
@usableFromInline
48+
package typealias FallbackEventHandler = @Sendable @convention(c) (
49+
_ recordJSONSchemaVersionNumber: UnsafePointer<CChar>,
50+
_ recordJSONBaseAddress: UnsafeRawPointer,
51+
_ recordJSONByteCount: Int,
52+
_ reserved: UnsafeRawPointer?
53+
) -> Void
54+
55+
/// Get the current fallback event handler.
56+
///
57+
/// - Returns: The currently-set handler function, if any.
58+
@_cdecl("_swift_testing_getFallbackEventHandler")
59+
@usableFromInline
60+
package func _swift_testing_getFallbackEventHandler() -> FallbackEventHandler? {
61+
#if SWT_TARGET_OS_APPLE && !SWT_NO_OS_UNFAIR_LOCK
62+
return _fallbackEventHandler.withUnsafeMutablePointers { fallbackEventHandler, lock in
63+
os_unfair_lock_lock(lock)
64+
defer {
65+
os_unfair_lock_unlock(lock)
66+
}
67+
return fallbackEventHandler.pointee
68+
}
69+
#else
70+
return _fallbackEventHandler.load(ordering: .sequentiallyConsistent).flatMap { fallbackEventHandler in
71+
unsafeBitCast(fallbackEventHandler, to: FallbackEventHandler?.self)
72+
}
73+
#endif
74+
}
75+
76+
/// Set the current fallback event handler if one has not already been set.
77+
///
78+
/// - Parameters:
79+
/// - handler: The handler function to set.
80+
///
81+
/// - Returns: Whether or not `handler` was installed.
82+
///
83+
/// The fallback event handler can only be installed once per process, typically
84+
/// by the first testing library to run. If this function has already been
85+
/// called and the handler set, it does not replace the previous handler.
86+
@_cdecl("_swift_testing_installFallbackEventHandler")
87+
@usableFromInline
88+
package func _swift_testing_installFallbackEventHandler(_ handler: FallbackEventHandler) -> CBool {
89+
#if SWT_TARGET_OS_APPLE && !SWT_NO_OS_UNFAIR_LOCK
90+
return _fallbackEventHandler.withUnsafeMutablePointers { fallbackEventHandler, lock in
91+
os_unfair_lock_lock(lock)
92+
defer {
93+
os_unfair_lock_unlock(lock)
94+
}
95+
guard fallbackEventHandler.pointee == nil else {
96+
return false
97+
}
98+
fallbackEventHandler.pointee = handler
99+
return true
100+
}
101+
#else
102+
let handler = unsafeBitCast(handler, to: UnsafeRawPointer.self)
103+
return _fallbackEventHandler.compareExchange(expected: nil, desired: handler, ordering: .sequentiallyConsistent).exchanged
104+
#endif
105+
}
106+
#endif

0 commit comments

Comments
 (0)