Skip to content

Store test content in a custom metadata section. #880

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion Documentation/Porting.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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
```

Expand Down
10 changes: 6 additions & 4 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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"),
Expand Down
2 changes: 2 additions & 0 deletions Sources/Testing/ExitTests/ExitTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 2 additions & 0 deletions Sources/Testing/Test+Discovery+Legacy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -41,3 +42,4 @@ open class __TestContentRecordContainer: TestContentRecordContainer {

@available(*, unavailable)
extension __TestContentRecordContainer: Sendable {}
#endif
6 changes: 6 additions & 0 deletions Sources/Testing/Test+Discovery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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.
Expand All @@ -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() }
Expand All @@ -96,6 +101,7 @@ extension Test {
result = await taskGroup.reduce(into: result) { $0.insert($1) }
}
}
#endif

return result
}
Expand Down
75 changes: 48 additions & 27 deletions Sources/TestingMacros/ConditionMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -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
)
}
}
)
Expand Down
6 changes: 6 additions & 0 deletions Sources/TestingMacros/SuiteDeclarationMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -172,6 +177,7 @@ public struct SuiteDeclarationMacro: MemberMacro, PeerMacro, Sendable {
}
"""
)
#endif

return result
}
Expand Down
12 changes: 12 additions & 0 deletions Sources/TestingMacros/Support/TestContentGeneration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
19 changes: 7 additions & 12 deletions Sources/TestingMacros/TestDeclarationMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
///
Expand Down Expand Up @@ -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)
}
"""
Expand Down Expand Up @@ -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(
Expand All @@ -508,6 +502,7 @@ public struct TestDeclarationMacro: PeerMacro, Sendable {
}
"""
)
#endif

return result
}
Expand Down
8 changes: 8 additions & 0 deletions Sources/_TestDiscovery/SectionBounds.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
}
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -276,8 +282,10 @@ private func _sectionBounds(_ kind: SectionBounds.Kind) -> some Sequence<Section
let sectionName = switch kind {
case .testContent:
".sw5test"
#if !SWT_NO_LEGACY_TEST_DISCOVERY
case .typeMetadata:
".sw5tymd"
#endif
}
return HMODULE.all.lazy.compactMap { _findSection(named: sectionName, in: $0) }
}
Expand Down
2 changes: 2 additions & 0 deletions Sources/_TestDiscovery/TestContentRecord.swift
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ extension DiscoverableAsTestContent where Self: ~Copyable {
}
}

#if !SWT_NO_LEGACY_TEST_DISCOVERY
// MARK: - Legacy test content discovery

private import _TestingInternals
Expand Down Expand Up @@ -344,3 +345,4 @@ extension DiscoverableAsTestContent where Self: ~Copyable {
return AnySequence(result)
}
}
#endif
Loading