diff --git a/Documentation/Porting.md b/Documentation/Porting.md index ce179d53d..8b230ff22 100644 --- a/Documentation/Porting.md +++ b/Documentation/Porting.md @@ -145,8 +145,10 @@ to load that information: + let resourceName: Str255 = switch kind { + case .testContent: + "__swift5_tests" ++#if !SWT_NO_LEGACY_TEST_DISCOVERY + case .typeMetadata: + "__swift5_types" ++#endif + } + + let oldRefNum = CurResFile() @@ -219,14 +221,18 @@ diff --git a/Sources/_TestingInternals/Discovery.cpp b/Sources/_TestingInternals +#elif defined(macintosh) +extern "C" const char testContentSectionBegin __asm__("..."); +extern "C" const char testContentSectionEnd __asm__("..."); ++#if !defined(SWT_NO_LEGACY_TEST_DISCOVERY) +extern "C" const char typeMetadataSectionBegin __asm__("..."); +extern "C" const char typeMetadataSectionEnd __asm__("..."); ++#endif #else #warning Platform-specific implementation missing: Runtime test discovery unavailable (static) static const char testContentSectionBegin = 0; static const char& testContentSectionEnd = testContentSectionBegin; + #if !defined(SWT_NO_LEGACY_TEST_DISCOVERY) static const char typeMetadataSectionBegin = 0; - static const char& typeMetadataSectionEnd = testContentSectionBegin; + static const char& typeMetadataSectionEnd = typeMetadataSectionBegin; + #endif #endif ``` diff --git a/Package.swift b/Package.swift index 11adfc14e..e2257af1a 100644 --- a/Package.swift +++ b/Package.swift @@ -89,10 +89,7 @@ let package = Package( "_Testing_CoreGraphics", "_Testing_Foundation", ], - swiftSettings: .packageSettings + [ - // For testing test content section discovery only - .enableExperimentalFeature("SymbolLinkageMarkers"), - ] + swiftSettings: .packageSettings ), .macro( @@ -205,6 +202,11 @@ extension Array where Element == PackageDescription.SwiftSetting { .enableExperimentalFeature("AccessLevelOnImport"), .enableUpcomingFeature("InternalImportsByDefault"), + // This setting is enabled in the package, but not in the toolchain build + // (via CMake). Enabling it is dependent on acceptance of the @section + // proposal via Swift Evolution. + .enableExperimentalFeature("SymbolLinkageMarkers"), + // When building as a package, the macro plugin always builds as an // executable rather than a library. .define("SWT_NO_LIBRARY_MACRO_PLUGINS"), diff --git a/Sources/Testing/ExitTests/ExitTest.swift b/Sources/Testing/ExitTests/ExitTest.swift index 5b800f0c0..61b35b9fd 100644 --- a/Sources/Testing/ExitTests/ExitTest.swift +++ b/Sources/Testing/ExitTests/ExitTest.swift @@ -296,12 +296,14 @@ extension ExitTest { } } +#if !SWT_NO_LEGACY_TEST_DISCOVERY // Call the legacy lookup function that discovers tests embedded in types. for record in Self.allTypeMetadataBasedTestContentRecords() { if let exitTest = record.load(withHint: id) { return exitTest } } +#endif return nil } diff --git a/Sources/Testing/Test+Discovery+Legacy.swift b/Sources/Testing/Test+Discovery+Legacy.swift index 0be944ad9..711f95e73 100644 --- a/Sources/Testing/Test+Discovery+Legacy.swift +++ b/Sources/Testing/Test+Discovery+Legacy.swift @@ -8,6 +8,7 @@ // See https://swift.org/CONTRIBUTORS.txt for Swift project authors // +#if !SWT_NO_LEGACY_TEST_DISCOVERY @_spi(Experimental) @_spi(ForToolsIntegrationOnly) internal import _TestDiscovery /// A shadow declaration of `_TestDiscovery.TestContentRecordContainer` that @@ -41,3 +42,4 @@ open class __TestContentRecordContainer: TestContentRecordContainer { @available(*, unavailable) extension __TestContentRecordContainer: Sendable {} +#endif diff --git a/Sources/Testing/Test+Discovery.swift b/Sources/Testing/Test+Discovery.swift index a8cc831c4..2568353f8 100644 --- a/Sources/Testing/Test+Discovery.swift +++ b/Sources/Testing/Test+Discovery.swift @@ -65,6 +65,7 @@ extension Test { // the legacy and new mechanisms, but we can set an environment variable // to explicitly select one or the other. When we remove legacy support, // we can also remove this enumeration and environment variable check. +#if !SWT_NO_LEGACY_TEST_DISCOVERY let (useNewMode, useLegacyMode) = switch Environment.flag(named: "SWT_USE_LEGACY_TEST_DISCOVERY") { case .none: (true, true) @@ -73,6 +74,9 @@ extension Test { case .some(false): (true, false) } +#else + let useNewMode = true +#endif // Walk all test content and gather generator functions, then call them in // a task group and collate their results. @@ -86,6 +90,7 @@ extension Test { } } +#if !SWT_NO_LEGACY_TEST_DISCOVERY // Perform legacy test discovery if needed. if useLegacyMode && result.isEmpty { let generators = Generator.allTypeMetadataBasedTestContentRecords().lazy.compactMap { $0.load() } @@ -96,6 +101,7 @@ extension Test { result = await taskGroup.reduce(into: result) { $0.insert($1) } } } +#endif return result } diff --git a/Sources/TestingMacros/ConditionMacro.swift b/Sources/TestingMacros/ConditionMacro.swift index 01cac9a3a..43d28aa53 100644 --- a/Sources/TestingMacros/ConditionMacro.swift +++ b/Sources/TestingMacros/ConditionMacro.swift @@ -11,6 +11,10 @@ public import SwiftSyntax public import SwiftSyntaxMacros +#if !hasFeature(SymbolLinkageMarkers) && SWT_NO_LEGACY_TEST_DISCOVERY +#error("Platform-specific misconfiguration: either SymbolLinkageMarkers or legacy test discovery is required to expand #expect(exitsWith:)") +#endif + /// A protocol containing the common implementation for the expansions of the /// `#expect()` and `#require()` macros. /// @@ -452,42 +456,59 @@ extension ExitTestConditionMacro { // Create a local type that can be discovered at runtime and which contains // the exit test body. - let className = context.makeUniqueName("__🟡$") - let testContentRecordDecl = makeTestContentRecordDecl( - named: .identifier("testContentRecord"), - in: TypeSyntax(IdentifierTypeSyntax(name: className)), - ofKind: .exitTest, - accessingWith: .identifier("accessor") - ) - - decls.append( - """ - @available(*, deprecated, message: "This type is an implementation detail of the testing library. Do not use it directly.") - final class \(className): Testing.__TestContentRecordContainer { - private nonisolated static let accessor: Testing.__TestContentRecordAccessor = { outValue, type, hint in - Testing.ExitTest.__store( - \(exitTestIDExpr), - \(bodyThunkName), - into: outValue, - asTypeAt: type, - withHintAt: hint - ) - } - - \(testContentRecordDecl) + let enumName = context.makeUniqueName("") + do { + // Create the test content record. + let testContentRecordDecl = makeTestContentRecordDecl( + named: .identifier("testContentRecord"), + in: TypeSyntax(IdentifierTypeSyntax(name: enumName)), + ofKind: .exitTest, + accessingWith: .identifier("accessor") + ) + // Create another local type for legacy test discovery. + var recordDecl: DeclSyntax? +#if !SWT_NO_LEGACY_TEST_DISCOVERY + let className = context.makeUniqueName("__🟡$") + recordDecl = """ + private final class \(className): Testing.__TestContentRecordContainer { override nonisolated class var __testContentRecord: Testing.__TestContentRecord { - testContentRecord + \(enumName).testContentRecord } } """ - ) +#endif + + decls.append( + """ + @available(*, deprecated, message: "This type is an implementation detail of the testing library. Do not use it directly.") + enum \(enumName) { + private nonisolated static let accessor: Testing.__TestContentRecordAccessor = { outValue, type, hint in + Testing.ExitTest.__store( + \(exitTestIDExpr), + \(bodyThunkName), + into: outValue, + asTypeAt: type, + withHintAt: hint + ) + } + + \(testContentRecordDecl) + + \(recordDecl) + } + """ + ) + } arguments[trailingClosureIndex].expression = ExprSyntax( ClosureExprSyntax { for decl in decls { - CodeBlockItemSyntax(item: .decl(decl)) - .with(\.trailingTrivia, .newline) + CodeBlockItemSyntax( + leadingTrivia: .newline, + item: .decl(decl), + trailingTrivia: .newline + ) } } ) diff --git a/Sources/TestingMacros/SuiteDeclarationMacro.swift b/Sources/TestingMacros/SuiteDeclarationMacro.swift index b47109291..c90606577 100644 --- a/Sources/TestingMacros/SuiteDeclarationMacro.swift +++ b/Sources/TestingMacros/SuiteDeclarationMacro.swift @@ -11,6 +11,10 @@ public import SwiftSyntax public import SwiftSyntaxMacros +#if !hasFeature(SymbolLinkageMarkers) && SWT_NO_LEGACY_TEST_DISCOVERY +#error("Platform-specific misconfiguration: either SymbolLinkageMarkers or legacy test discovery is required to expand @Suite") +#endif + /// A type describing the expansion of the `@Suite` attribute macro. /// /// This type is used to implement the `@Suite` attribute macro. Do not use it @@ -160,6 +164,7 @@ public struct SuiteDeclarationMacro: MemberMacro, PeerMacro, Sendable { ) ) +#if !SWT_NO_LEGACY_TEST_DISCOVERY // Emit a type that contains a reference to the test content record. let className = context.makeUniqueName("__🟡$") result.append( @@ -172,6 +177,7 @@ public struct SuiteDeclarationMacro: MemberMacro, PeerMacro, Sendable { } """ ) +#endif return result } diff --git a/Sources/TestingMacros/Support/TestContentGeneration.swift b/Sources/TestingMacros/Support/TestContentGeneration.swift index 646bd97d4..391a468b1 100644 --- a/Sources/TestingMacros/Support/TestContentGeneration.swift +++ b/Sources/TestingMacros/Support/TestContentGeneration.swift @@ -62,6 +62,18 @@ func makeTestContentRecordDecl(named name: TokenSyntax, in typeName: TypeSyntax? } return """ + #if hasFeature(SymbolLinkageMarkers) + #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) || os(visionOS) + @_section("__DATA_CONST,__swift5_tests") + #elseif os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) + @_section("swift5_tests") + #elseif os(Windows) + @_section(".sw5test$B") + #else + @__testing(warning: "Platform-specific implementation missing: test content section name unavailable") + #endif + @_used + #endif @available(*, deprecated, message: "This property is an implementation detail of the testing library. Do not use it directly.") private nonisolated \(staticKeyword(for: typeName)) let \(name): Testing.__TestContentRecord = ( \(kindExpr), \(kind.commentRepresentation) diff --git a/Sources/TestingMacros/TestDeclarationMacro.swift b/Sources/TestingMacros/TestDeclarationMacro.swift index 21faed78f..3c6b12fe0 100644 --- a/Sources/TestingMacros/TestDeclarationMacro.swift +++ b/Sources/TestingMacros/TestDeclarationMacro.swift @@ -11,6 +11,10 @@ public import SwiftSyntax public import SwiftSyntaxMacros +#if !hasFeature(SymbolLinkageMarkers) && SWT_NO_LEGACY_TEST_DISCOVERY +#error("Platform-specific misconfiguration: either SymbolLinkageMarkers or legacy test discovery is required to expand @Test") +#endif + /// A type describing the expansion of the `@Test` attribute macro. /// /// This type is used to implement the `@Test` attribute macro. Do not use it @@ -188,17 +192,6 @@ public struct TestDeclarationMacro: PeerMacro, Sendable { return FunctionParameterClauseSyntax(parameters: parameterList) } - /// The `static` keyword, if `typeName` is not `nil`. - /// - /// - Parameters: - /// - typeName: The name of the type containing the macro being expanded. - /// - /// - Returns: A token representing the `static` keyword, or one representing - /// nothing if `typeName` is `nil`. - private static func _staticKeyword(for typeName: TypeSyntax?) -> TokenSyntax { - (typeName != nil) ? .keyword(.static) : .unknown("") - } - /// Create a thunk function with a normalized signature that calls a /// developer-supplied test function. /// @@ -356,7 +349,7 @@ public struct TestDeclarationMacro: PeerMacro, Sendable { let thunkName = context.makeUniqueName(thunking: functionDecl) let thunkDecl: DeclSyntax = """ @available(*, deprecated, message: "This function is an implementation detail of the testing library. Do not use it directly.") - @Sendable private \(_staticKeyword(for: typeName)) func \(thunkName)\(thunkParamsExpr) async throws -> Void { + @Sendable private \(staticKeyword(for: typeName)) func \(thunkName)\(thunkParamsExpr) async throws -> Void { \(thunkBody) } """ @@ -496,6 +489,7 @@ public struct TestDeclarationMacro: PeerMacro, Sendable { ) ) +#if !SWT_NO_LEGACY_TEST_DISCOVERY // Emit a type that contains a reference to the test content record. let className = context.makeUniqueName(thunking: functionDecl, withPrefix: "__🟡$") result.append( @@ -508,6 +502,7 @@ public struct TestDeclarationMacro: PeerMacro, Sendable { } """ ) +#endif return result } diff --git a/Sources/_TestDiscovery/SectionBounds.swift b/Sources/_TestDiscovery/SectionBounds.swift index 1fc379258..212edbfbf 100644 --- a/Sources/_TestDiscovery/SectionBounds.swift +++ b/Sources/_TestDiscovery/SectionBounds.swift @@ -27,8 +27,10 @@ struct SectionBounds: Sendable { /// The test content metadata section. case testContent +#if !SWT_NO_LEGACY_TEST_DISCOVERY /// The type metadata section. case typeMetadata +#endif } /// All section bounds of the given kind found in the current process. @@ -60,8 +62,10 @@ extension SectionBounds.Kind { switch self { case .testContent: ("__DATA_CONST", "__swift5_tests") +#if !SWT_NO_LEGACY_TEST_DISCOVERY case .typeMetadata: ("__TEXT", "__swift5_types") +#endif } } } @@ -186,8 +190,10 @@ private func _sectionBounds(_ kind: SectionBounds.Kind) -> [SectionBounds] { let range = switch context.pointee.kind { case .testContent: sections.swift5_tests +#if !SWT_NO_LEGACY_TEST_DISCOVERY case .typeMetadata: sections.swift5_type_metadata +#endif } let start = UnsafeRawPointer(bitPattern: range.start) let size = Int(clamping: range.length) @@ -276,8 +282,10 @@ private func _sectionBounds(_ kind: SectionBounds.Kind) -> some Sequence
+#if !defined(SWT_NO_LEGACY_TEST_DISCOVERY) #include #include #include +#endif #if defined(SWT_NO_DYNAMIC_LINKING) #pragma mark - Statically-linked section bounds @@ -21,24 +23,32 @@ #if defined(__APPLE__) extern "C" const char testContentSectionBegin __asm("section$start$__DATA_CONST$__swift5_tests"); extern "C" const char testContentSectionEnd __asm("section$end$__DATA_CONST$__swift5_tests"); +#if !defined(SWT_NO_LEGACY_TEST_DISCOVERY) extern "C" const char typeMetadataSectionBegin __asm__("section$start$__TEXT$__swift5_types"); extern "C" const char typeMetadataSectionEnd __asm__("section$end$__TEXT$__swift5_types"); +#endif #elif defined(__wasi__) extern "C" const char testContentSectionBegin __asm__("__start_swift5_tests"); extern "C" const char testContentSectionEnd __asm__("__stop_swift5_tests"); +#if !defined(SWT_NO_LEGACY_TEST_DISCOVERY) extern "C" const char typeMetadataSectionBegin __asm__("__start_swift5_type_metadata"); extern "C" const char typeMetadataSectionEnd __asm__("__stop_swift5_type_metadata"); +#endif #else #warning Platform-specific implementation missing: Runtime test discovery unavailable (static) static const char testContentSectionBegin = 0; static const char& testContentSectionEnd = testContentSectionBegin; +#if !defined(SWT_NO_LEGACY_TEST_DISCOVERY) static const char typeMetadataSectionBegin = 0; static const char& typeMetadataSectionEnd = typeMetadataSectionBegin; #endif +#endif static constexpr const char *const staticallyLinkedSectionBounds[][2] = { { &testContentSectionBegin, &testContentSectionEnd }, +#if !defined(SWT_NO_LEGACY_TEST_DISCOVERY) { &typeMetadataSectionBegin, &typeMetadataSectionEnd }, +#endif }; void swt_getStaticallyLinkedSectionBounds(size_t kind, const void **outSectionBegin, size_t *outByteCount) { @@ -48,6 +58,7 @@ void swt_getStaticallyLinkedSectionBounds(size_t kind, const void **outSectionBe } #endif +#if !defined(SWT_NO_LEGACY_TEST_DISCOVERY) #pragma mark - Swift ABI #if defined(__PTRAUTH_INTRINSICS__) @@ -221,3 +232,4 @@ const void *swt_getTypeFromTypeMetadataRecord(const void *recordAddress, const c return nullptr; } +#endif diff --git a/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift b/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift index 6ac3542a9..b0028c438 100644 --- a/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift +++ b/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift @@ -408,7 +408,12 @@ struct TestDeclarationMacroTests { func differentFunctionTypes(input: String, expectedTypeName: String?, otherCode: String?) throws { let (output, _) = try parse(input) +#if hasFeature(SymbolLinkageMarkers) + #expect(output.contains("@_section")) +#endif +#if !SWT_NO_LEGACY_TEST_DISCOVERY #expect(output.contains("__TestContentRecordContainer")) +#endif if let expectedTypeName { #expect(output.contains(expectedTypeName)) } diff --git a/Tests/TestingTests/MiscellaneousTests.swift b/Tests/TestingTests/MiscellaneousTests.swift index ec7fdd12d..a6c62fdbc 100644 --- a/Tests/TestingTests/MiscellaneousTests.swift +++ b/Tests/TestingTests/MiscellaneousTests.swift @@ -664,4 +664,21 @@ struct MiscellaneousTests { }) } #endif + +#if !SWT_NO_LEGACY_TEST_DISCOVERY && hasFeature(SymbolLinkageMarkers) + @Test("Legacy test discovery finds the same number of tests") func discoveredTestCount() async { + let oldFlag = Environment.variable(named: "SWT_USE_LEGACY_TEST_DISCOVERY") + defer { + Environment.setVariable(oldFlag, named: "SWT_USE_LEGACY_TEST_DISCOVERY") + } + + Environment.setVariable("1", named: "SWT_USE_LEGACY_TEST_DISCOVERY") + let testsWithOldCode = await Array(Test.all).count + + Environment.setVariable("0", named: "SWT_USE_LEGACY_TEST_DISCOVERY") + let testsWithNewCode = await Array(Test.all).count + + #expect(testsWithOldCode == testsWithNewCode) + } +#endif }