Skip to content

Commit bcf7ff8

Browse files
committed
Merge branch 'main' into jgrynspan/promote-windows-image-attachments-to-api
2 parents 0dad370 + 0b47e51 commit bcf7ff8

File tree

6 files changed

+107
-21
lines changed

6 files changed

+107
-21
lines changed

Sources/Testing/SourceAttribution/Backtrace.swift

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,24 @@ extension Backtrace {
324324
}
325325
#endif
326326

327+
#if !hasFeature(Embedded) && SWT_TARGET_OS_APPLE && !SWT_NO_DYNAMIC_LINKING
328+
/// A function provided by Core Foundation that copies the captured backtrace
329+
/// from storage inside `CFError` or `NSError`.
330+
///
331+
/// - Parameters:
332+
/// - error: The error whose backtrace is desired.
333+
///
334+
/// - Returns: The backtrace (as an instance of `NSArray`) captured by `error`
335+
/// when it was created, or `nil` if none was captured. The caller is
336+
/// responsible for releasing this object when done.
337+
///
338+
/// This function was added in an internal Foundation PR and is not available
339+
/// on older systems.
340+
private static let _CFErrorCopyCallStackReturnAddresses = symbol(named: "_CFErrorCopyCallStackReturnAddresses").map {
341+
castCFunction(at: $0, to: (@convention(c) (_ error: any Error) -> Unmanaged<AnyObject>?).self)
342+
}
343+
#endif
344+
327345
/// Whether or not Foundation provides a function that triggers the capture of
328346
/// backtaces when instances of `NSError` or `CFError` are created.
329347
///
@@ -339,7 +357,11 @@ extension Backtrace {
339357
/// first time the value of this property is read.
340358
static let isFoundationCaptureEnabled = {
341359
#if !hasFeature(Embedded) && _runtime(_ObjC) && !SWT_NO_DYNAMIC_LINKING
342-
if Environment.flag(named: "SWT_FOUNDATION_ERROR_BACKTRACING_ENABLED") == true {
360+
// Check the environment variable; if it isn't set, enable if and only if
361+
// the Core Foundation getter function is implemented.
362+
let foundationBacktracesEnabled = Environment.flag(named: "SWT_FOUNDATION_ERROR_BACKTRACING_ENABLED")
363+
?? (_CFErrorCopyCallStackReturnAddresses != nil)
364+
if foundationBacktracesEnabled {
343365
let _CFErrorSetCallStackCaptureEnabled = symbol(named: "_CFErrorSetCallStackCaptureEnabled").map {
344366
castCFunction(at: $0, to: (@convention(c) (DarwinBoolean) -> DarwinBoolean).self)
345367
}
@@ -422,11 +444,19 @@ extension Backtrace {
422444
#if !hasFeature(Embedded)
423445
@inline(never)
424446
init?(forFirstThrowOf error: any Error, checkFoundation: Bool = true) {
425-
if checkFoundation && Self.isFoundationCaptureEnabled,
426-
let userInfo = error._userInfo as? [String: Any],
427-
let addresses = userInfo["NSCallStackReturnAddresses"] as? [Address], !addresses.isEmpty {
428-
self.init(addresses: addresses)
429-
return
447+
if checkFoundation && Self.isFoundationCaptureEnabled {
448+
#if !hasFeature(Embedded) && SWT_TARGET_OS_APPLE && !SWT_NO_DYNAMIC_LINKING
449+
if let addresses = Self._CFErrorCopyCallStackReturnAddresses?(error)?.takeRetainedValue() as? [Address] {
450+
self.init(addresses: addresses)
451+
return
452+
}
453+
#endif
454+
455+
if let userInfo = error._userInfo as? [String: Any],
456+
let addresses = userInfo["NSCallStackReturnAddresses"] as? [Address], !addresses.isEmpty {
457+
self.init(addresses: addresses)
458+
return
459+
}
430460
}
431461

432462
let entry = Self._errorMappingCache.withLock { cache in

Sources/Testing/Traits/ConditionTrait+Macro.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,27 @@ extension Trait where Self == ConditionTrait {
124124
sourceLocation: sourceLocation
125125
)
126126
}
127+
128+
/// Create a trait controlling availability of a test based on an
129+
/// `@_unavailableInEmbedded` attribute applied to it.
130+
///
131+
/// - Parameters:
132+
/// - sourceLocation: The source location of the test.
133+
///
134+
/// - Returns: A trait.
135+
///
136+
/// - Warning: This function is used to implement the `@Test` macro. Do not
137+
/// call it directly.
138+
public static func __unavailableInEmbedded(sourceLocation: SourceLocation) -> Self {
139+
#if hasFeature(Embedded)
140+
let isEmbedded = true
141+
#else
142+
let isEmbedded = false
143+
#endif
144+
return Self(
145+
kind: .unconditional(!isEmbedded),
146+
comments: ["Marked @_unavailableInEmbedded"],
147+
sourceLocation: sourceLocation
148+
)
149+
}
127150
}

Sources/TestingMacros/Support/Additions/FunctionDeclSyntaxAdditions.swift

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,8 @@ extension FunctionDeclSyntax {
8787
var xcTestCompatibleSelector: ObjCSelectorPieceListSyntax? {
8888
// First, look for an @objc attribute with an explicit selector, and use
8989
// that if found.
90-
let objcAttribute = attributes.lazy
91-
.compactMap {
92-
if case let .attribute(attribute) = $0 {
93-
return attribute
94-
}
95-
return nil
96-
}.first { $0.attributeNameText == "objc" }
97-
if let objcAttribute, case let .objCName(objCName) = objcAttribute.arguments {
90+
if let objcAttribute = attributes(named: "objc", inModuleNamed: "Swift").first,
91+
case let .objCName(objCName) = objcAttribute.arguments {
9892
if true == objCName.first?.name?.textWithoutBackticks.hasPrefix("test") {
9993
return objCName
10094
}

Sources/TestingMacros/Support/Additions/WithAttributesSyntaxAdditions.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,13 @@ extension WithAttributesSyntax {
105105
/// The first `@available(*, noasync)` or `@_unavailableFromAsync` attribute
106106
/// on this instance, if any.
107107
var noasyncAttribute: AttributeSyntax? {
108-
availability(when: .noasync).first?.attribute ?? attributes.lazy
109-
.compactMap { attribute in
110-
if case let .attribute(attribute) = attribute {
111-
return attribute
112-
}
113-
return nil
114-
}.first { $0.attributeNameText == "_unavailableFromAsync" }
108+
availability(when: .noasync).first?.attribute
109+
?? attributes(named: "_unavailableFromAsync", inModuleNamed: "Swift").first
110+
}
111+
112+
/// The first `@_unavailableInEmbedded` attribute on this instance, if any.
113+
var unavailableInEmbeddedAttribute: AttributeSyntax? {
114+
attributes(named: "_unavailableInEmbedded", inModuleNamed: "Swift").first
115115
}
116116

117117
/// Find all attributes on this node, if any, with the given name.

Sources/TestingMacros/Support/AvailabilityGuards.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,11 @@ func createAvailabilityTraitExprs(
169169
_createAvailabilityTraitExpr(from: availability, when: .obsoleted, in: context)
170170
}
171171

172+
if let attribute = decl.unavailableInEmbeddedAttribute {
173+
let sourceLocationExpr = createSourceLocationExpr(of: attribute, context: context)
174+
result += [".__unavailableInEmbedded(sourceLocation: \(sourceLocationExpr))"]
175+
}
176+
172177
return result
173178
}
174179

@@ -290,5 +295,16 @@ func createSyntaxNode(
290295
}
291296
}
292297

298+
// Handle Embedded Swift.
299+
if decl.unavailableInEmbeddedAttribute != nil {
300+
result = """
301+
#if !hasFeature(Embedded)
302+
\(result)
303+
#else
304+
\(exitStatement)
305+
#endif
306+
"""
307+
}
308+
293309
return result
294310
}

Tests/TestingTests/RunnerTests.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,29 @@ final class RunnerTests: XCTestCase {
819819
await fulfillment(of: [testStarted], timeout: 0.0)
820820
}
821821

822+
@Suite(.hidden) struct UnavailableInEmbeddedTests {
823+
@Test(.hidden)
824+
@_unavailableInEmbedded
825+
func embedded() {}
826+
}
827+
828+
func testUnavailableInEmbeddedAttribute() async throws {
829+
let testStarted = expectation(description: "Test started")
830+
#if !hasFeature(Embedded)
831+
testStarted.expectedFulfillmentCount = 3
832+
#else
833+
testStarted.isInverted = true
834+
#endif
835+
var configuration = Configuration()
836+
configuration.eventHandler = { event, _ in
837+
if case .testStarted = event.kind {
838+
testStarted.fulfill()
839+
}
840+
}
841+
await runTest(for: UnavailableInEmbeddedTests.self, configuration: configuration)
842+
await fulfillment(of: [testStarted], timeout: 0.0)
843+
}
844+
822845
#if !SWT_NO_GLOBAL_ACTORS
823846
@TaskLocal static var isMainActorIsolationEnforced = false
824847

0 commit comments

Comments
 (0)