1010
1111private import _TestingInternals
1212
13+ @_spi ( Experimental) @_spi ( ForToolsIntegrationOnly)
14+ public enum Interop : Sendable { }
15+
16+ extension Interop {
17+ public enum Mode : String , Sendable , Codable , CaseIterable {
18+ /// The interop feature is not active.
19+ case none
20+
21+ /// Show runtime warnings for assertion failures caused by primitives
22+ /// from other test libraries. The overall test case success/failure is
23+ /// therefore not affected.
24+ ///
25+ /// Show runtime warning issues for XCTest API usage when running a
26+ /// Swift Testing test.
27+ case limited
28+
29+ /// Show assertion failures caused by primitives from other test
30+ /// libraries.
31+ ///
32+ /// Show runtime warning issues for XCTest API usage when running a
33+ /// Swift Testing test.
34+ case complete
35+
36+ /// Show assertion failures caused by primitives from other test
37+ /// libraries.
38+ ///
39+ /// `fatalError` upon any XCTest assertion failures when running a
40+ /// Swift Testing test.
41+ case strict
42+ }
43+ }
44+
45+ extension Interop {
46+ /// Name of the environment variable flag to set when opting-in to the
47+ /// experimental interop feature.
48+ static let experimentalOptInKey = " SWT_EXPERIMENTAL_INTEROP_ENABLED "
49+ }
50+
51+ extension Interop . Mode {
52+ /// The name for the environment variable which if set, overrides the default
53+ /// interop mode.
54+ static let interopModeEnvKey = " SWIFT_TESTING_XCTEST_INTEROP_MODE "
55+
56+ /// Whether this interop mode causes Swift Testing to install a fallback event
57+ /// handler ahead of running tests.
58+ var requiresInstallation : Bool {
59+ Environment . flag ( named: Interop . experimentalOptInKey) == true && self != . none
60+ }
61+
62+ /// Current interop mode, which should not be changed after tests start
63+ /// running.
64+ @_spi ( Experimental) @_spi ( ForToolsIntegrationOnly)
65+ public static let current : Self = {
66+ Environment . variable ( named: interopModeEnvKey) . flatMap ( Interop . Mode. init) ?? . limited
67+ } ( )
68+ }
69+
1370extension Event {
1471 /// Attempt to handle an event encoded as JSON as if it had been generated in
1572 /// the current testing context.
@@ -28,22 +85,39 @@ extension Event {
2885 where V: ABI . Version {
2986 let record = try JSON . decode ( ABI . Record< V> . self , from: recordJSON)
3087 guard case . event( let event) = record. kind,
31- let issue = Issue ( decoding: event) else {
88+ var issue = Issue ( decoding: event)
89+ else {
3290 return
3391 }
3492
93+ let xctestWarningMessage =
94+ " XCTest API was used in a Swift Testing test. Adopt Swift Testing primitives, such as #expect, instead. "
95+
3596 // For the time being, assume that foreign test events originate from XCTest
36- let warnForXCTestUsageIssue = {
37- return Issue (
97+ lazy var warnForXCTestUsageIssue =
98+ Issue (
3899 kind: . apiMisused, severity: . warning,
39100 comments: [
40- " XCTest API was used in a Swift Testing test. Adopt Swift Testing primitives, such as #expect, instead. "
41- ] , sourceContext: issue. sourceContext
42- )
43- } ( )
101+ . init( rawValue: xctestWarningMessage)
102+ ] , sourceContext: issue. sourceContext)
44103
45- issue. record ( )
46- warnForXCTestUsageIssue. record ( )
104+ switch Interop . Mode. current {
105+ case . none: return // no-op
106+ case . limited:
107+ issue. severity = . warning
108+ issue. record ( )
109+ warnForXCTestUsageIssue. record ( )
110+ case . complete:
111+ issue. severity = . error
112+ issue. record ( )
113+ warnForXCTestUsageIssue. record ( )
114+ case . strict:
115+ issue. severity = . error
116+ issue. record ( )
117+ fatalError (
118+ " \( xctestWarningMessage) This is a fatal error because strict interop mode is active ( \( Interop . Mode. interopModeEnvKey) =strict) " ,
119+ )
120+ }
47121 }
48122
49123 /// Get the best available source location to use when diagnosing an issue
@@ -54,7 +128,9 @@ extension Event {
54128 ///
55129 /// - Returns: A source location to use when reporting an issue about
56130 /// `recordJSON`.
57- private static func _bestAvailableSourceLocation( forInvalidRecordJSON recordJSON: UnsafeRawBufferPointer ) -> SourceLocation {
131+ private static func _bestAvailableSourceLocation(
132+ forInvalidRecordJSON recordJSON: UnsafeRawBufferPointer
133+ ) -> SourceLocation {
58134 // TODO: try to actually extract a source location from arbitrary JSON?
59135
60136 // If there's a test associated with the current task, it should have a
@@ -66,12 +142,13 @@ extension Event {
66142 return . unknown
67143 }
68144
69- #if !SWT_NO_INTEROP
145+ #if !SWT_NO_INTEROP
70146 /// The fallback event handler to install when Swift Testing is the active
71147 /// testing library.
72148 private static let _ourFallbackEventHandler : SWTFallbackEventHandler = {
73149 recordJSONSchemaVersionNumber, recordJSONBaseAddress, recordJSONByteCount, _ in
74- let version = String ( validatingCString: recordJSONSchemaVersionNumber)
150+ let version =
151+ String ( validatingCString: recordJSONSchemaVersionNumber)
75152 . flatMap ( VersionNumber . init)
76153 . flatMap { ABI . version ( forVersionNumber: $0) } ?? ABI . v0. self
77154 let recordJSON = UnsafeRawBufferPointer (
@@ -104,15 +181,15 @@ extension Event {
104181 ) . record ( )
105182 }
106183 }
107- #endif
184+ #endif
108185
109186 /// The implementation of ``installFallbackEventHandler()``.
110187 private static let _installFallbackEventHandler : Bool = {
111- #if !SWT_NO_INTEROP
112- if Environment . flag ( named : " SWT_EXPERIMENTAL_INTEROP_ENABLED " ) == true {
188+ #if !SWT_NO_INTEROP
189+ if Interop . Mode . current . requiresInstallation {
113190 return _swift_testing_installFallbackEventHandler ( Self . _ourFallbackEventHandler)
114191 }
115- #endif
192+ #endif
116193 return false
117194 } ( )
118195
@@ -141,14 +218,14 @@ extension Event {
141218 /// currently-installed handler belongs to the testing library, returns
142219 /// `false`.
143220 borrowing func postToFallbackEventHandler( in context: borrowing Context ) -> Bool {
144- #if !SWT_NO_INTEROP
221+ #if !SWT_NO_INTEROP
145222 return Self . _postToFallbackEventHandler ? ( self , context) != nil
146- #else
223+ #else
147224 return false
148- #endif
225+ #endif
149226 }
150227
151- #if !SWT_NO_INTEROP
228+ #if !SWT_NO_INTEROP
152229 /// The implementation of ``postToFallbackEventHandler(in:)`` that actually
153230 /// invokes the installed fallback event handler.
154231 ///
@@ -161,7 +238,8 @@ extension Event {
161238 }
162239
163240 let fallbackEventHandlerAddress = castCFunction ( fallbackEventHandler, to: UnsafeRawPointer . self)
164- let ourFallbackEventHandlerAddress = castCFunction ( Self . _ourFallbackEventHandler, to: UnsafeRawPointer . self)
241+ let ourFallbackEventHandlerAddress = castCFunction (
242+ Self . _ourFallbackEventHandler, to: UnsafeRawPointer . self)
165243 if fallbackEventHandlerAddress == ourFallbackEventHandlerAddress {
166244 // The fallback event handler belongs to Swift Testing, so we don't want
167245 // to call it on our own behalf.
@@ -178,5 +256,5 @@ extension Event {
178256 )
179257 }
180258 } ( )
181- #endif
259+ #endif
182260}
0 commit comments