diff --git a/Sources/TestingMacros/SuiteDeclarationMacro.swift b/Sources/TestingMacros/SuiteDeclarationMacro.swift index 5b9d9985b..c4eaf1239 100644 --- a/Sources/TestingMacros/SuiteDeclarationMacro.swift +++ b/Sources/TestingMacros/SuiteDeclarationMacro.swift @@ -21,7 +21,9 @@ public struct SuiteDeclarationMacro: MemberMacro, PeerMacro, Sendable { providingMembersOf declaration: some DeclGroupSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { - _diagnoseIssues(with: declaration, suiteAttribute: node, in: context) + guard _diagnoseIssues(with: declaration, suiteAttribute: node, in: context) else { + return [] + } return _createTestContainerDecls(for: declaration, suiteAttribute: node, in: context) } @@ -33,7 +35,7 @@ public struct SuiteDeclarationMacro: MemberMacro, PeerMacro, Sendable { // The peer macro expansion of this macro is only used to diagnose misuses // on symbols that are not decl groups. if declaration.asProtocol((any DeclGroupSyntax).self) == nil { - _diagnoseIssues(with: declaration, suiteAttribute: node, in: context) + _ = _diagnoseIssues(with: declaration, suiteAttribute: node, in: context) } return [] } @@ -44,23 +46,31 @@ public struct SuiteDeclarationMacro: MemberMacro, PeerMacro, Sendable { /// - declaration: The type declaration to diagnose. /// - suiteAttribute: The `@Suite` attribute applied to `declaration`. /// - context: The macro context in which the expression is being parsed. + /// + /// - Returns: Whether or not macro expansion should continue (i.e. stopping + /// if a fatal error was diagnosed.) private static func _diagnoseIssues( with declaration: some SyntaxProtocol, suiteAttribute: AttributeSyntax, in context: some MacroExpansionContext - ) { + ) -> Bool { var diagnostics = [DiagnosticMessage]() defer { - diagnostics.forEach(context.diagnose) + context.diagnose(diagnostics) } // The @Suite attribute is only supported on type declarations, all of which // are DeclGroupSyntax types. guard let declaration = declaration.asProtocol((any DeclGroupSyntax).self) else { diagnostics.append(.attributeNotSupported(suiteAttribute, on: declaration)) - return + return false } +#if canImport(SwiftSyntax600) + // Check if the lexical context is appropriate for a suite or test. + diagnostics += diagnoseIssuesWithLexicalContext(containing: declaration, attribute: suiteAttribute, in: context) +#endif + // Generic suites are not supported. if let genericClause = declaration.asProtocol((any WithGenericParametersSyntax).self)?.genericParameterClause { diagnostics.append(.genericDeclarationNotSupported(declaration, whenUsing: suiteAttribute, becauseOf: genericClause)) @@ -115,6 +125,8 @@ public struct SuiteDeclarationMacro: MemberMacro, PeerMacro, Sendable { diagnostics.append(.availabilityAttributeNotSupported(noasyncAttribute, on: declaration, whenUsing: suiteAttribute)) } } + + return !diagnostics.lazy.map(\.severity).contains(.error) } /// Create a declaration for a type that conforms to the `__TestContainer` diff --git a/Sources/TestingMacros/Support/Additions/FunctionDeclSyntaxAdditions.swift b/Sources/TestingMacros/Support/Additions/FunctionDeclSyntaxAdditions.swift index 085452ee8..ab2e3dfa1 100644 --- a/Sources/TestingMacros/Support/Additions/FunctionDeclSyntaxAdditions.swift +++ b/Sources/TestingMacros/Support/Additions/FunctionDeclSyntaxAdditions.swift @@ -94,9 +94,17 @@ extension FunctionDeclSyntax { if signature.effectSpecifiers?.asyncSpecifier != nil { selector += "WithCompletionHandler" colonToken = .colonToken() - } else if signature.effectSpecifiers?.throwsSpecifier != nil { - selector += "AndReturnError" - colonToken = .colonToken() + } else { + let hasThrowsSpecifier: Bool +#if canImport(SwiftSyntax600) + hasThrowsSpecifier = signature.effectSpecifiers?.throwsClause != nil +#else + hasThrowsSpecifier = signature.effectSpecifiers?.throwsSpecifier != nil +#endif + if hasThrowsSpecifier { + selector += "AndReturnError" + colonToken = .colonToken() + } } return ObjCSelectorPieceListSyntax { ObjCSelectorPieceSyntax(name: .identifier(selector), colon: colonToken) diff --git a/Sources/TestingMacros/Support/TagConstraints.swift b/Sources/TestingMacros/Support/DiagnosticMessage+Diagnosing.swift similarity index 83% rename from Sources/TestingMacros/Support/TagConstraints.swift rename to Sources/TestingMacros/Support/DiagnosticMessage+Diagnosing.swift index 0542a515b..faea21f5a 100644 --- a/Sources/TestingMacros/Support/TagConstraints.swift +++ b/Sources/TestingMacros/Support/DiagnosticMessage+Diagnosing.swift @@ -87,3 +87,24 @@ func diagnoseIssuesWithTags(in traitExprs: [ExprSyntax], addedTo attribute: Attr } } } + +#if canImport(SwiftSyntax600) +/// Diagnose issues with the lexical context containing a declaration. +/// +/// - Parameters: +/// - decl: The declaration to inspect. +/// - testAttribute: The `@Test` attribute applied to `decl`. +/// - context: The macro context in which the expression is being parsed. +/// +/// - Returns: An array of zero or more diagnostic messages related to the +/// lexical context containing `decl`. +func diagnoseIssuesWithLexicalContext( + containing decl: some DeclSyntaxProtocol, + attribute: AttributeSyntax, + in context: some MacroExpansionContext +) -> [DiagnosticMessage] { + context.lexicalContext + .filter { !$0.isProtocol((any DeclGroupSyntax).self) } + .map { .containingNodeUnsupported($0, whenUsing: attribute) } +} +#endif diff --git a/Sources/TestingMacros/Support/DiagnosticMessage.swift b/Sources/TestingMacros/Support/DiagnosticMessage.swift index a4d2a8c85..055063b98 100644 --- a/Sources/TestingMacros/Support/DiagnosticMessage.swift +++ b/Sources/TestingMacros/Support/DiagnosticMessage.swift @@ -138,8 +138,13 @@ struct DiagnosticMessage: SwiftDiagnostics.DiagnosticMessage { result = ("subscript", "a") case .enumCaseDecl: result = ("enumeration case", "an") +#if canImport(SwiftSyntax600) + case .typeAliasDecl: + result = ("typealias", "a") +#else case .typealiasDecl: result = ("typealias", "a") +#endif case .macroDecl: result = ("macro", "a") case .protocolDecl: @@ -225,6 +230,27 @@ struct DiagnosticMessage: SwiftDiagnostics.DiagnosticMessage { ) } +#if canImport(SwiftSyntax600) + /// Create a diagnostic message stating that the given attribute cannot be + /// used within a lexical context. + /// + /// - Parameters: + /// - node: The lexical context preventing the use of `attribute`. + /// - attribute: The `@Test` or `@Suite` attribute. + /// + /// - Returns: A diagnostic message. + static func containingNodeUnsupported(_ node: some SyntaxProtocol, whenUsing attribute: AttributeSyntax) -> Self { + // It would be great if the diagnostic pointed to the containing lexical + // context that was unsupported, but that node may be synthesized and does + // not have reliable location information. + Self( + syntax: Syntax(attribute), + message: "The @\(attribute.attributeNameText) attribute cannot be applied within \(_kindString(for: node, includeA: true)).", + severity: .error + ) + } +#endif + /// Create a diagnostic message stating that the given attribute has no effect /// when applied to the given extension declaration. /// @@ -406,7 +432,6 @@ extension MacroExpansionContext { /// - message: The diagnostic message to emit. The `node` and `position` /// arguments to `Diagnostic.init()` are derived from the message's /// `syntax` property. - /// - fixIts: Any Fix-Its to apply. func diagnose(_ message: DiagnosticMessage) { diagnose( Diagnostic( @@ -418,6 +443,16 @@ extension MacroExpansionContext { ) } + /// Emit a sequence of diagnostic messages. + /// + /// - Parameters: + /// - messages: The diagnostic messages to emit. + func diagnose(_ messages: some Sequence) { + for message in messages { + diagnose(message) + } + } + /// Emit a diagnostic message for debugging purposes during development of the /// testing library. /// diff --git a/Sources/TestingMacros/TestDeclarationMacro.swift b/Sources/TestingMacros/TestDeclarationMacro.swift index 98120b1fc..bb8147379 100644 --- a/Sources/TestingMacros/TestDeclarationMacro.swift +++ b/Sources/TestingMacros/TestDeclarationMacro.swift @@ -21,7 +21,9 @@ public struct TestDeclarationMacro: PeerMacro, Sendable { providingPeersOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { - _diagnoseIssues(with: declaration, testAttribute: node, in: context) + guard _diagnoseIssues(with: declaration, testAttribute: node, in: context) else { + return [] + } guard let function = declaration.as(FunctionDeclSyntax.self) else { return [] @@ -45,6 +47,17 @@ public struct TestDeclarationMacro: PeerMacro, Sendable { testAttribute: AttributeSyntax, in context: some MacroExpansionContext ) -> TypeSyntax? { +#if canImport(SwiftSyntax600) + let types = context.lexicalContext + .compactMap { $0.asProtocol((any DeclGroupSyntax).self) } + .map(\.type) + .reversed() + if types.isEmpty { + return nil + } + let typeName = types.map(\.trimmedDescription).joined(separator: ".") + return "\(raw: typeName)" +#else // Find the beginning of the first attribute on the declaration, including // those embedded in #if statements, to account for patterns like // `@MainActor @Test func` where there's a space ahead of @Test, but the @@ -79,6 +92,7 @@ public struct TestDeclarationMacro: PeerMacro, Sendable { return TypeSyntax(IdentifierTypeSyntax(name: .keyword(.Self))) } return nil +#endif } /// Diagnose issues with a `@Test` declaration. @@ -87,22 +101,30 @@ public struct TestDeclarationMacro: PeerMacro, Sendable { /// - declaration: The function declaration to diagnose. /// - testAttribute: The `@Test` attribute applied to `declaration`. /// - context: The macro context in which the expression is being parsed. + /// + /// - Returns: Whether or not macro expansion should continue (i.e. stopping + /// if a fatal error was diagnosed.) private static func _diagnoseIssues( with declaration: some DeclSyntaxProtocol, testAttribute: AttributeSyntax, in context: some MacroExpansionContext - ) { + ) -> Bool { var diagnostics = [DiagnosticMessage]() defer { - diagnostics.forEach(context.diagnose) + context.diagnose(diagnostics) } // The @Test attribute is only supported on function declarations. guard let function = declaration.as(FunctionDeclSyntax.self) else { diagnostics.append(.attributeNotSupported(testAttribute, on: declaration)) - return + return false } +#if canImport(SwiftSyntax600) + // Check if the lexical context is appropriate for a suite or test. + diagnostics += diagnoseIssuesWithLexicalContext(containing: declaration, attribute: testAttribute, in: context) +#endif + // Only one @Test attribute is supported. let suiteAttributes = function.attributes(named: "Test", in: context) if suiteAttributes.count > 1 { @@ -113,13 +135,22 @@ public struct TestDeclarationMacro: PeerMacro, Sendable { // We don't support inout, isolated, or _const parameters on test functions. for parameter in parameterList { - if let specifier = parameter.type.as(AttributedTypeSyntax.self)?.specifier { - switch specifier.tokenKind { - case .keyword(.inout), .keyword(.isolated), .keyword(._const): + let invalidSpecifierKeywords: [TokenKind] = [.keyword(.inout), .keyword(.isolated), .keyword(._const),] + if let parameterType = parameter.type.as(AttributedTypeSyntax.self) { +#if canImport(SwiftSyntax600) + for specifier in parameterType.specifiers { + guard case let .simpleTypeSpecifier(specifier) = specifier else { + continue + } + if invalidSpecifierKeywords.contains(specifier.specifier.tokenKind) { + diagnostics.append(.specifierNotSupported(specifier.specifier, on: parameter, whenUsing: testAttribute)) + } + } +#else + if let specifier = parameterType.specifier, invalidSpecifierKeywords.contains(specifier.tokenKind) { diagnostics.append(.specifierNotSupported(specifier, on: parameter, whenUsing: testAttribute)) - default: - break } +#endif } } @@ -144,6 +175,8 @@ public struct TestDeclarationMacro: PeerMacro, Sendable { } } } + + return !diagnostics.lazy.map(\.severity).contains(.error) } /// Create a function call parameter list used to call a function from its @@ -220,21 +253,41 @@ public struct TestDeclarationMacro: PeerMacro, Sendable { private static func _createCaptureListExpr( from parametersWithLabels: some Sequence<(DeclReferenceExprSyntax, FunctionParameterSyntax)> ) -> ClosureCaptureClauseSyntax { - ClosureCaptureClauseSyntax { - for (label, parameter) in parametersWithLabels { - if case let .keyword(specifierKeyword) = parameter.type.as(AttributedTypeSyntax.self)?.specifier?.tokenKind, - specifierKeyword == .borrowing || specifierKeyword == .consuming { - ClosureCaptureSyntax( - name: label.baseName, - equal: .equalToken(), - expression: CopyExprSyntax( - copyKeyword: .keyword(.copy).with(\.trailingTrivia, .space), - expression: label - ) - ) - } else { - ClosureCaptureSyntax(expression: label) + let specifierKeywordsNeedingCopy: [TokenKind] = [.keyword(.borrowing), .keyword(.consuming),] + let closureCaptures = parametersWithLabels.lazy.map { label, parameter in + var needsCopy = false + if let parameterType = parameter.type.as(AttributedTypeSyntax.self) { +#if canImport(SwiftSyntax600) + needsCopy = parameterType.specifiers.contains { specifier in + guard case let .simpleTypeSpecifier(specifier) = specifier else { + return false + } + return specifierKeywordsNeedingCopy.contains(specifier.specifier.tokenKind) + } +#else + if let specifier = parameterType.specifier { + needsCopy = specifierKeywordsNeedingCopy.contains(specifier.tokenKind) } +#endif + } + + if needsCopy { + return ClosureCaptureSyntax( + name: label.baseName, + equal: .equalToken(), + expression: CopyExprSyntax( + copyKeyword: .keyword(.copy).with(\.trailingTrivia, .space), + expression: label + ) + ) + } else { + return ClosureCaptureSyntax(expression: label) + } + } + + return ClosureCaptureClauseSyntax { + for closureCapture in closureCaptures { + closureCapture } } } @@ -406,6 +459,11 @@ public struct TestDeclarationMacro: PeerMacro, Sendable { ) -> [DeclSyntax] { var result = [DeclSyntax]() +#if canImport(SwiftSyntax600) + // Get the name of the type containing the function for passing to the test + // factory function later. + let typealiasExpr: ExprSyntax = typeName.map { "\($0).self" } ?? "nil" +#else // We cannot directly refer to Self here because it will end up being // resolved as the __TestContainer type we generate. Create a uniquely-named // reference to Self outside the context of the generated type, and use it @@ -415,7 +473,7 @@ public struct TestDeclarationMacro: PeerMacro, Sendable { // inside a static computed property instead of a typealias (where covariant // Self is disallowed.) // - // This "typealias" will not be necessary when rdar://105470382 is resolved. + // This "typealias" is not necessary when swift-syntax-6.0.0 is available. var typealiasExpr: ExprSyntax = "nil" if let typeName { let typealiasName = context.makeUniqueName(thunking: functionDecl) @@ -430,6 +488,7 @@ public struct TestDeclarationMacro: PeerMacro, Sendable { typealiasExpr = "\(typealiasName)" } +#endif // Parse the @Test attribute. let attributeInfo = AttributeInfo(byParsing: testAttribute, on: functionDecl, in: context) diff --git a/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift b/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift index 1bd5df1f1..5b205abce 100644 --- a/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift +++ b/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift @@ -67,7 +67,6 @@ struct TestDeclarationMacroTests { "@Test enum E {}": "Attribute 'Test' cannot be applied to an enumeration", - // Availability "@available(*, unavailable) @Suite struct S {}": "Attribute 'Suite' cannot be applied to this structure because it has been marked '@available(*, unavailable)'", @@ -117,6 +116,26 @@ struct TestDeclarationMacroTests { } } +#if canImport(SwiftSyntax600) + @Test("Error diagnostics emitted for invalid lexical contexts", + arguments: [ + "struct S { func f() { @Test func f() {} } }": + "The @Test attribute cannot be applied within a function.", + "struct S { func f() { @Suite struct S { } } }": + "The @Suite attribute cannot be applied within a function.", + ] + ) + func invalidLexicalContext(input: String, expectedMessage: String) throws { + let (_, diagnostics) = try parse(input) + + #expect(diagnostics.count > 0) + for diagnostic in diagnostics { + #expect(diagnostic.diagMessage.severity == .error) + #expect(diagnostic.message == expectedMessage) + } + } +#endif + @Test("Warning diagnostics emitted on API misuse", arguments: [ // return types @@ -189,13 +208,9 @@ struct TestDeclarationMacroTests { } } - @Test("Different kinds of functions are handled correctly", - arguments: [ + static var functionTypeInputs: [(String, String?, String?)] { + var result: [(String, String?, String?)] = [ ("@Test func f() {}", nil, nil), - ("struct S {\n\t@Test func f() {} }", "Self", "let"), - ("struct S {\n\t@Test mutating func f() {} }", "Self", "var"), - ("struct S {\n\t@Test static func f() {} }", "Self", nil), - ("final class S {\n\t@Test class func f() {} }", "Self", nil), ("@Test @available(*, noasync) @MainActor func f() {}", nil, "MainActor.run"), ("@Test @_unavailableFromAsync @MainActor func f() {}", nil, "MainActor.run"), ("@Test @available(*, noasync) func f() {}", nil, "__requiringTry"), @@ -220,7 +235,27 @@ struct TestDeclarationMacroTests { nil ), ] - ) + +#if canImport(SwiftSyntax600) + result += [ + ("struct S_NAME {\n\t@Test func f() {} }", "S_NAME", "let"), + ("struct S_NAME {\n\t@Test mutating func f() {} }", "S_NAME", "var"), + ("struct S_NAME {\n\t@Test static func f() {} }", "S_NAME", nil), + ("final class C_NAME {\n\t@Test class func f() {} }", "C_NAME", nil), + ] +#else + result += [ + ("struct S {\n\t@Test func f() {} }", "Self", "let"), + ("struct S {\n\t@Test mutating func f() {} }", "Self", "var"), + ("struct S {\n\t@Test static func f() {} }", "Self", nil), + ("final class C {\n\t@Test class func f() {} }", "Self", nil), + ] +#endif + + return result + } + + @Test("Different kinds of functions are handled correctly", arguments: functionTypeInputs) func differentFunctionTypes(input: String, expectedTypeName: String?, otherCode: String?) throws { let (output, _) = try parse(input) diff --git a/Tests/TestingMacrosTests/TestSupport/Parse.swift b/Tests/TestingMacrosTests/TestSupport/Parse.swift index 093bc6e90..ff4f79d7b 100644 --- a/Tests/TestingMacrosTests/TestSupport/Parse.swift +++ b/Tests/TestingMacrosTests/TestSupport/Parse.swift @@ -35,8 +35,17 @@ func parse(_ sourceCode: String, activeMacros activeMacroNames: [String] = [], r } let operatorTable = OperatorTable.standardOperators let originalSyntax = try operatorTable.foldAll(Parser.parse(source: sourceCode)) +#if canImport(SwiftSyntax600) + let context = BasicMacroExpansionContext(lexicalContext: [], expansionDiscriminator: "", sourceFiles: [:]) + let syntax = try operatorTable.foldAll( + originalSyntax.expand(macros: activeMacros) { syntax in + BasicMacroExpansionContext(sharingWith: context, lexicalContext: syntax.allMacroLexicalContexts()) + } + ) +#else let context = BasicMacroExpansionContext(expansionDiscriminator: "", sourceFiles: [:]) let syntax = try operatorTable.foldAll(originalSyntax.expand(macros: activeMacros, in: context)) +#endif var sourceCode = String(describing: syntax.formatted().trimmed) if removeWhitespace { sourceCode = sourceCode.filter { !$0.isWhitespace } diff --git a/Tests/TestingTests/RunnerTests.swift b/Tests/TestingTests/RunnerTests.swift index 9b860b675..27981eadd 100644 --- a/Tests/TestingTests/RunnerTests.swift +++ b/Tests/TestingTests/RunnerTests.swift @@ -431,15 +431,15 @@ final class RunnerTests: XCTestCase { await fulfillment(of: [expectationCheckedAndPassed, expectationCheckedAndFailed], timeout: 0.0) } - func testPoundIfTrueTestFunctionRuns() async throws { - @Suite(.hidden) struct S { + @Suite(.hidden) struct PoundIfTrueTest { #if true - @Test(.hidden) func f() {} - @Test(.hidden) func g() {} + @Test(.hidden) func f() {} + @Test(.hidden) func g() {} #endif - @Test(.hidden) func h() {} - } + @Test(.hidden) func h() {} + } + func testPoundIfTrueTestFunctionRuns() async throws { let testStarted = expectation(description: "Test started") testStarted.expectedFulfillmentCount = 4 var configuration = Configuration() @@ -448,19 +448,19 @@ final class RunnerTests: XCTestCase { testStarted.fulfill() } } - await runTest(for: S.self, configuration: configuration) + await runTest(for: PoundIfTrueTest.self, configuration: configuration) await fulfillment(of: [testStarted], timeout: 0.0) } - func testPoundIfFalseTestFunctionDoesNotRun() async throws { - @Suite(.hidden) struct S { + @Suite(.hidden) struct PoundIfFalseTest { #if false - @Test(.hidden) func f() {} - @Test(.hidden) func g() {} + @Test(.hidden) func f() {} + @Test(.hidden) func g() {} #endif - @Test(.hidden) func h() {} - } + @Test(.hidden) func h() {} + } + func testPoundIfFalseTestFunctionDoesNotRun() async throws { let testStarted = expectation(description: "Test started") testStarted.expectedFulfillmentCount = 2 var configuration = Configuration() @@ -469,21 +469,21 @@ final class RunnerTests: XCTestCase { testStarted.fulfill() } } - await runTest(for: S.self, configuration: configuration) + await runTest(for: PoundIfFalseTest.self, configuration: configuration) await fulfillment(of: [testStarted], timeout: 0.0) } - func testPoundIfFalseElseTestFunctionRuns() async throws { - @Suite(.hidden) struct S { + @Suite(.hidden) struct PoundIfFalseElseTest { #if false #elseif false #else - @Test(.hidden) func f() {} - @Test(.hidden) func g() {} + @Test(.hidden) func f() {} + @Test(.hidden) func g() {} #endif - @Test(.hidden) func h() {} - } + @Test(.hidden) func h() {} + } + func testPoundIfFalseElseTestFunctionRuns() async throws { let testStarted = expectation(description: "Test started") testStarted.expectedFulfillmentCount = 4 var configuration = Configuration() @@ -492,21 +492,21 @@ final class RunnerTests: XCTestCase { testStarted.fulfill() } } - await runTest(for: S.self, configuration: configuration) + await runTest(for: PoundIfFalseElseTest.self, configuration: configuration) await fulfillment(of: [testStarted], timeout: 0.0) } - func testPoundIfFalseElseIfTestFunctionRuns() async throws { - @Suite(.hidden) struct S { + @Suite(.hidden) struct PoundIfFalseElseIfTest { #if false #elseif false #elseif true - @Test(.hidden) func f() {} - @Test(.hidden) func g() {} + @Test(.hidden) func f() {} + @Test(.hidden) func g() {} #endif - @Test(.hidden) func h() {} - } + @Test(.hidden) func h() {} + } + func testPoundIfFalseElseIfTestFunctionRuns() async throws { let testStarted = expectation(description: "Test started") testStarted.expectedFulfillmentCount = 4 var configuration = Configuration() @@ -515,35 +515,35 @@ final class RunnerTests: XCTestCase { testStarted.fulfill() } } - await runTest(for: S.self, configuration: configuration) + await runTest(for: PoundIfFalseElseIfTest.self, configuration: configuration) await fulfillment(of: [testStarted], timeout: 0.0) } - func testNoasyncTestsAreCallable() async throws { - @Suite(.hidden) struct S { - @Test(.hidden) - @available(*, noasync) - func noAsync() {} + @Suite(.hidden) struct NoasyncTestsAreCallableTests { + @Test(.hidden) + @available(*, noasync) + func noAsync() {} - @Test(.hidden) - @available(*, noasync) - func noAsyncThrows() throws {} + @Test(.hidden) + @available(*, noasync) + func noAsyncThrows() throws {} - @Test(.hidden) - @_unavailableFromAsync - func unavailableFromAsync() {} + @Test(.hidden) + @_unavailableFromAsync + func unavailableFromAsync() {} - @Test(.hidden) - @_unavailableFromAsync(message: "") - func unavailableFromAsyncWithMessage() {} + @Test(.hidden) + @_unavailableFromAsync(message: "") + func unavailableFromAsyncWithMessage() {} #if !SWT_NO_GLOBAL_ACTORS - @Test(.hidden) - @available(*, noasync) @MainActor - func noAsyncThrowsMainActor() throws {} + @Test(.hidden) + @available(*, noasync) @MainActor + func noAsyncThrowsMainActor() throws {} #endif - } + } + func testNoasyncTestsAreCallable() async throws { let testStarted = expectation(description: "Test started") #if !SWT_NO_GLOBAL_ACTORS testStarted.expectedFulfillmentCount = 6 @@ -556,7 +556,7 @@ final class RunnerTests: XCTestCase { testStarted.fulfill() } } - await runTest(for: S.self, configuration: configuration) + await runTest(for: NoasyncTestsAreCallableTests.self, configuration: configuration) await fulfillment(of: [testStarted], timeout: 0.0) }