diff --git a/Sources/Testing/Traits/ConditionTrait+Macro.swift b/Sources/Testing/Traits/ConditionTrait+Macro.swift index dbddcfc1f..fd489b9e6 100644 --- a/Sources/Testing/Traits/ConditionTrait+Macro.swift +++ b/Sources/Testing/Traits/ConditionTrait+Macro.swift @@ -124,4 +124,27 @@ extension Trait where Self == ConditionTrait { sourceLocation: sourceLocation ) } + + /// Create a trait controlling availability of a test based on an + /// `@_unavailableInEmbedded` attribute applied to it. + /// + /// - Parameters: + /// - sourceLocation: The source location of the test. + /// + /// - Returns: A trait. + /// + /// - Warning: This function is used to implement the `@Test` macro. Do not + /// call it directly. + public static func __unavailableInEmbedded(sourceLocation: SourceLocation) -> Self { +#if hasFeature(Embedded) + let isEmbedded = true +#else + let isEmbedded = false +#endif + return Self( + kind: .unconditional(!isEmbedded), + comments: ["Marked @_unavailableInEmbedded"], + sourceLocation: sourceLocation + ) + } } diff --git a/Sources/TestingMacros/Support/Additions/FunctionDeclSyntaxAdditions.swift b/Sources/TestingMacros/Support/Additions/FunctionDeclSyntaxAdditions.swift index fa390775a..8065d299e 100644 --- a/Sources/TestingMacros/Support/Additions/FunctionDeclSyntaxAdditions.swift +++ b/Sources/TestingMacros/Support/Additions/FunctionDeclSyntaxAdditions.swift @@ -87,14 +87,8 @@ extension FunctionDeclSyntax { var xcTestCompatibleSelector: ObjCSelectorPieceListSyntax? { // First, look for an @objc attribute with an explicit selector, and use // that if found. - let objcAttribute = attributes.lazy - .compactMap { - if case let .attribute(attribute) = $0 { - return attribute - } - return nil - }.first { $0.attributeNameText == "objc" } - if let objcAttribute, case let .objCName(objCName) = objcAttribute.arguments { + if let objcAttribute = attributes(named: "objc", inModuleNamed: "Swift").first, + case let .objCName(objCName) = objcAttribute.arguments { if true == objCName.first?.name?.textWithoutBackticks.hasPrefix("test") { return objCName } diff --git a/Sources/TestingMacros/Support/Additions/WithAttributesSyntaxAdditions.swift b/Sources/TestingMacros/Support/Additions/WithAttributesSyntaxAdditions.swift index 52d85bbd4..aa778a00c 100644 --- a/Sources/TestingMacros/Support/Additions/WithAttributesSyntaxAdditions.swift +++ b/Sources/TestingMacros/Support/Additions/WithAttributesSyntaxAdditions.swift @@ -105,13 +105,13 @@ extension WithAttributesSyntax { /// The first `@available(*, noasync)` or `@_unavailableFromAsync` attribute /// on this instance, if any. var noasyncAttribute: AttributeSyntax? { - availability(when: .noasync).first?.attribute ?? attributes.lazy - .compactMap { attribute in - if case let .attribute(attribute) = attribute { - return attribute - } - return nil - }.first { $0.attributeNameText == "_unavailableFromAsync" } + availability(when: .noasync).first?.attribute + ?? attributes(named: "_unavailableFromAsync", inModuleNamed: "Swift").first + } + + /// The first `@_unavailableInEmbedded` attribute on this instance, if any. + var unavailableInEmbeddedAttribute: AttributeSyntax? { + attributes(named: "_unavailableInEmbedded", inModuleNamed: "Swift").first } /// Find all attributes on this node, if any, with the given name. diff --git a/Sources/TestingMacros/Support/AvailabilityGuards.swift b/Sources/TestingMacros/Support/AvailabilityGuards.swift index e9f4ba762..deb3a0f8b 100644 --- a/Sources/TestingMacros/Support/AvailabilityGuards.swift +++ b/Sources/TestingMacros/Support/AvailabilityGuards.swift @@ -169,6 +169,11 @@ func createAvailabilityTraitExprs( _createAvailabilityTraitExpr(from: availability, when: .obsoleted, in: context) } + if let attribute = decl.unavailableInEmbeddedAttribute { + let sourceLocationExpr = createSourceLocationExpr(of: attribute, context: context) + result += [".__unavailableInEmbedded(sourceLocation: \(sourceLocationExpr))"] + } + return result } @@ -290,5 +295,16 @@ func createSyntaxNode( } } + // Handle Embedded Swift. + if decl.unavailableInEmbeddedAttribute != nil { + result = """ + #if !hasFeature(Embedded) + \(result) + #else + \(exitStatement) + #endif + """ + } + return result } diff --git a/Tests/TestingTests/RunnerTests.swift b/Tests/TestingTests/RunnerTests.swift index 335f8be37..c254e5ba9 100644 --- a/Tests/TestingTests/RunnerTests.swift +++ b/Tests/TestingTests/RunnerTests.swift @@ -819,6 +819,29 @@ final class RunnerTests: XCTestCase { await fulfillment(of: [testStarted], timeout: 0.0) } + @Suite(.hidden) struct UnavailableInEmbeddedTests { + @Test(.hidden) + @_unavailableInEmbedded + func embedded() {} + } + + func testUnavailableInEmbeddedAttribute() async throws { + let testStarted = expectation(description: "Test started") +#if !hasFeature(Embedded) + testStarted.expectedFulfillmentCount = 3 +#else + testStarted.isInverted = true +#endif + var configuration = Configuration() + configuration.eventHandler = { event, _ in + if case .testStarted = event.kind { + testStarted.fulfill() + } + } + await runTest(for: UnavailableInEmbeddedTests.self, configuration: configuration) + await fulfillment(of: [testStarted], timeout: 0.0) + } + #if !SWT_NO_GLOBAL_ACTORS @TaskLocal static var isMainActorIsolationEnforced = false