diff --git a/Documentation/Porting.md b/Documentation/Porting.md index 4773e2823..365551d4c 100644 --- a/Documentation/Porting.md +++ b/Documentation/Porting.md @@ -193,7 +193,7 @@ to load that information: ``` You will also need to update the `makeTestContentRecordDecl()` function in the -`TestingMacros` target to emit the correct `@_section` attribute for your +`TestingMacros` target to emit the correct `@section` attribute for your platform. If your platform uses the ELF image format and supports the `dl_iterate_phdr()` function, add it to the existing `#elseif os(Linux) || ...` case. Otherwise, add a new case for your platform: @@ -203,7 +203,7 @@ case. Otherwise, add a new case for your platform: +++ b/Sources/TestingMacros/Support/TestContentGeneration.swift // ... + #elseif os(Classic) -+ @_section(".rsrc,swft,__swift5_tests") ++ @section(".rsrc,swft,__swift5_tests") #else @__testing(warning: "Platform-specific implementation missing: test content section name unavailable") #endif @@ -214,6 +214,11 @@ directly into test authors' test targets, so you will not be able to use compiler conditionals defined in the Swift Testing package (including those that start with `"SWT_"`). +> [!NOTE] +> We are not using `objectFormat()` yet to maintain compatibility with the Swift +> 6.2 toolchain. We will migrate to `objectFormat()` when we drop Swift 6.2 +> toolchain support (presumably after Swift 6.3 ships). + ## Runtime test discovery with static linkage If your platform does not support dynamic linking and loading, you will need to diff --git a/Package.swift b/Package.swift index a82241527..19ebefc4c 100644 --- a/Package.swift +++ b/Package.swift @@ -385,11 +385,6 @@ extension Array where Element == PackageDescription.SwiftSetting { .enableUpcomingFeature("MemberImportVisibility"), - // 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"), - .enableUpcomingFeature("InferIsolatedConformances"), // When building as a package, the macro plugin always builds as an diff --git a/Sources/Testing/Discovery+Macro.swift b/Sources/Testing/Discovery+Macro.swift index 35b276efe..f3e598e13 100644 --- a/Sources/Testing/Discovery+Macro.swift +++ b/Sources/Testing/Discovery+Macro.swift @@ -34,7 +34,7 @@ public typealias __TestContentRecordAccessor = @convention(c) ( public typealias __TestContentRecord = ( kind: UInt32, reserved1: UInt32, - accessor: __TestContentRecordAccessor?, + accessor: __TestContentRecordAccessor, context: UInt, reserved2: UInt ) diff --git a/Sources/TestingMacros/ConditionMacro.swift b/Sources/TestingMacros/ConditionMacro.swift index 6ba8ff124..48080f085 100644 --- a/Sources/TestingMacros/ConditionMacro.swift +++ b/Sources/TestingMacros/ConditionMacro.swift @@ -13,10 +13,6 @@ public import SwiftSyntax import SwiftSyntaxBuilder 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(processExitsWith:)") -#endif - /// A protocol containing the common implementation for the expansions of the /// `#expect()` and `#require()` macros. /// @@ -491,12 +487,20 @@ extension ExitTestConditionMacro { named: .identifier("testContentRecord"), in: TypeSyntax(IdentifierTypeSyntax(name: enumName)), ofKind: .exitTest, - accessingWith: .identifier("accessor") + accessingWith: """ + Testing.ExitTest.__store( + \(idExpr), + \(bodyThunkName), + into: outValue, + asTypeAt: type, + withHintAt: hint + ) + """ ) // Create another local type for legacy test discovery. var recordDecl: DeclSyntax? -#if !SWT_NO_LEGACY_TEST_DISCOVERY +#if compiler(<6.3) let legacyEnumName = context.makeUniqueName("__🟡$") recordDecl = """ enum \(legacyEnumName): Testing.__TestContentRecordContainer { @@ -511,16 +515,6 @@ extension ExitTestConditionMacro { """ @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( - \(idExpr), - \(bodyThunkName), - into: outValue, - asTypeAt: type, - withHintAt: hint - ) - } - \(testContentRecordDecl) \(recordDecl) diff --git a/Sources/TestingMacros/SuiteDeclarationMacro.swift b/Sources/TestingMacros/SuiteDeclarationMacro.swift index 4bee4c30f..d462f5ea7 100644 --- a/Sources/TestingMacros/SuiteDeclarationMacro.swift +++ b/Sources/TestingMacros/SuiteDeclarationMacro.swift @@ -13,10 +13,6 @@ public import SwiftSyntax import SwiftSyntaxBuilder 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 @@ -145,28 +141,20 @@ public struct SuiteDeclarationMacro: MemberMacro, PeerMacro, Sendable { """ ) - let accessorName = context.makeUniqueName("accessor") - result.append( - """ - @available(*, deprecated, message: "This property is an implementation detail of the testing library. Do not use it directly.") - private nonisolated static let \(accessorName): Testing.__TestContentRecordAccessor = { outValue, type, _, _ in - Testing.Test.__store(\(generatorName), into: outValue, asTypeAt: type) - } - """ - ) - let testContentRecordName = context.makeUniqueName("testContentRecord") result.append( makeTestContentRecordDecl( named: testContentRecordName, in: declaration.type, ofKind: .testDeclaration, - accessingWith: accessorName, + accessingWith: """ + Testing.Test.__store(\(generatorName), into: outValue, asTypeAt: type) + """, context: attributeInfo.testContentRecordFlags ) ) -#if !SWT_NO_LEGACY_TEST_DISCOVERY +#if compiler(<6.3) // Emit a type that contains a reference to the test content record. let enumName = context.makeUniqueName("__🟡$") result.append( diff --git a/Sources/TestingMacros/Support/TestContentGeneration.swift b/Sources/TestingMacros/Support/TestContentGeneration.swift index 05214d1b8..152998f17 100644 --- a/Sources/TestingMacros/Support/TestContentGeneration.swift +++ b/Sources/TestingMacros/Support/TestContentGeneration.swift @@ -47,15 +47,14 @@ enum TestContentKind: UInt32 { /// - typeName: The name of the type enclosing the resulting declaration, or /// `nil` if it will not be emitted into a type's scope. /// - kind: The kind of test content record being emitted. -/// - accessorName: The Swift name of an `@convention(c)` function to emit -/// into the resulting record. +/// - accessorExpr: The accessor function call expression. /// - context: A value to emit as the `context` field of the test content /// record. /// /// - Returns: A variable declaration that, when emitted into Swift source, will /// cause the linker to emit data in a location that is discoverable at /// runtime. -func makeTestContentRecordDecl(named name: TokenSyntax, in typeName: TypeSyntax? = nil, ofKind kind: TestContentKind, accessingWith accessorName: TokenSyntax, context: UInt32 = 0) -> DeclSyntax { +func makeTestContentRecordDecl(named name: TokenSyntax, in typeName: TypeSyntax? = nil, ofKind kind: TestContentKind, accessingWith accessorExpr: ExprSyntax, context: UInt32 = 0) -> DeclSyntax { let kindExpr = IntegerLiteralExprSyntax(kind.rawValue, radix: .hex) let contextExpr = if context == 0 { IntegerLiteralExprSyntax(0) @@ -68,26 +67,26 @@ func makeTestContentRecordDecl(named name: TokenSyntax, in typeName: TypeSyntax? private nonisolated \(staticKeyword(for: typeName)) let \(name): Testing.__TestContentRecord = ( \(kindExpr), \(kind.commentRepresentation) 0, - unsafe \(accessorName), + { outValue, type, hint, _ in + \(accessorExpr) + }, \(contextExpr), 0 ) """ -#if hasFeature(SymbolLinkageMarkers) +#if compiler(>=6.3) result = """ - #if hasFeature(SymbolLinkageMarkers) #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) || os(visionOS) - @_section("__DATA_CONST,__swift5_tests") + @section("__DATA_CONST,__swift5_tests") #elseif os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) - @_section("swift5_tests") + @section("swift5_tests") #elseif os(Windows) - @_section(".sw5test$B") + @section(".sw5test$B") #else @Testing.__testing(warning: "Platform-specific implementation missing: test content section name unavailable") #endif - @_used - #endif + @used \(result) """ #endif diff --git a/Sources/TestingMacros/TestDeclarationMacro.swift b/Sources/TestingMacros/TestDeclarationMacro.swift index 8007c3aaf..d4ba7f7d0 100644 --- a/Sources/TestingMacros/TestDeclarationMacro.swift +++ b/Sources/TestingMacros/TestDeclarationMacro.swift @@ -13,10 +13,6 @@ public import SwiftSyntax import SwiftSyntaxBuilder 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 @@ -472,28 +468,20 @@ public struct TestDeclarationMacro: PeerMacro, Sendable { """ ) - let accessorName = context.makeUniqueName(thunking: functionDecl, withPrefix: "accessor") - result.append( - """ - @available(*, deprecated, message: "This property is an implementation detail of the testing library. Do not use it directly.") - private \(staticKeyword(for: typeName)) nonisolated let \(accessorName): Testing.__TestContentRecordAccessor = { outValue, type, _, _ in - Testing.Test.__store(\(generatorName), into: outValue, asTypeAt: type) - } - """ - ) - let testContentRecordName = context.makeUniqueName(thunking: functionDecl, withPrefix: "testContentRecord") result.append( makeTestContentRecordDecl( named: testContentRecordName, in: typeName, ofKind: .testDeclaration, - accessingWith: accessorName, + accessingWith: """ + Testing.Test.__store(\(generatorName), into: outValue, asTypeAt: type) + """, context: attributeInfo.testContentRecordFlags ) ) -#if !SWT_NO_LEGACY_TEST_DISCOVERY +#if compiler(<6.3) // Emit a type that contains a reference to the test content record. let enumName = context.makeUniqueName(thunking: functionDecl, withPrefix: "__🟡$") result.append( diff --git a/Sources/_TestDiscovery/TestContentRecord.swift b/Sources/_TestDiscovery/TestContentRecord.swift index b830026e2..1f1bd9c15 100644 --- a/Sources/_TestDiscovery/TestContentRecord.swift +++ b/Sources/_TestDiscovery/TestContentRecord.swift @@ -244,7 +244,7 @@ extension DiscoverableAsTestContent { return SectionBounds.all(.testContent).lazy.flatMap { sb in sb.buffer.withMemoryRebound(to: _TestContentRecord.self) { records in (0 ..< records.count).lazy - .map { (records.baseAddress! + $0) as UnsafePointer<_TestContentRecord> } + .map { records.baseAddress! + $0 } .filter { $0.pointee.kind == kind } .map { TestContentRecord(imageAddress: sb.imageAddress, recordAddress: $0) } } diff --git a/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift b/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift index aec6d2c10..c78fac75d 100644 --- a/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift +++ b/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift @@ -472,10 +472,11 @@ 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 +#if compiler(>=6.3) + #expect(output.contains("@section")) + #expect(!output.contains("__TestContentRecordContainer")) +#else + #expect(!output.contains("@section")) #expect(output.contains("__TestContentRecordContainer")) #endif if let expectedTypeName { diff --git a/Tests/TestingTests/DiscoveryTests.swift b/Tests/TestingTests/DiscoveryTests.swift index 24d2eecfa..2c415e001 100644 --- a/Tests/TestingTests/DiscoveryTests.swift +++ b/Tests/TestingTests/DiscoveryTests.swift @@ -58,7 +58,7 @@ struct DiscoveryTests { } #endif -#if !SWT_NO_DYNAMIC_LINKING && hasFeature(SymbolLinkageMarkers) +#if compiler(>=6.3) && !SWT_NO_DYNAMIC_LINKING struct MyTestContent: DiscoverableAsTestContent { typealias TestContentAccessorHint = UInt32 @@ -81,15 +81,15 @@ struct DiscoveryTests { } #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) || os(visionOS) - @_section("__DATA_CONST,__swift5_tests") + @section("__DATA_CONST,__swift5_tests") #elseif os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) - @_section("swift5_tests") + @section("swift5_tests") #elseif os(Windows) - @_section(".sw5test$B") + @section(".sw5test$B") #else @__testing(warning: "Platform-specific implementation missing: test content section name unavailable") #endif - @_used + @used private static let record: __TestContentRecord = ( 0xABCD1234, 0, @@ -143,21 +143,4 @@ struct DiscoveryTests { }) } #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 }