Skip to content

Commit 2edf710

Browse files
authored
Move test discovery code out of Test+Macros.swift. (#137)
* Move test discovery code out of Test+Macros.swift. This PR moves test discovery code from Test+Macros.swift to a new Test+Discovery.swift file and tweaks the signature of the supporting C++ function a bit to make it easier to read when used in Swift.
1 parent c6340bd commit 2edf710

File tree

5 files changed

+125
-111
lines changed

5 files changed

+125
-111
lines changed

Sources/Testing/Test+Discovery.swift

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
//
2+
// This source file is part of the Swift.org open source project
3+
//
4+
// Copyright (c) 2023 Apple Inc. and the Swift project authors
5+
// Licensed under Apache License v2.0 with Runtime Library Exception
6+
//
7+
// See https://swift.org/LICENSE.txt for license information
8+
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
//
10+
11+
private import TestingInternals
12+
13+
/// A protocol describing a type that contains tests.
14+
///
15+
/// - Warning: This protocol is used to implement the `@Test` macro. Do not use
16+
/// it directly.
17+
@_alwaysEmitConformanceMetadata
18+
public protocol __TestContainer {
19+
/// The set of tests contained by this type.
20+
static var __tests: [Test] { get async }
21+
}
22+
23+
extension Test {
24+
/// A string that appears within all auto-generated types conforming to the
25+
/// `__TestContainer` protocol.
26+
private static let _testContainerTypeNameMagic = "__🟠$test_container__"
27+
28+
/// All available ``Test`` instances in the process, according to the runtime.
29+
///
30+
/// The order of values in this sequence is unspecified.
31+
static var all: some Sequence<Test> {
32+
get async {
33+
// Convert the raw sequence of tests to a dictionary keyed by ID.
34+
var result = await testsByID(_all)
35+
36+
// Ensure test suite types that don't have the @Suite attribute are still
37+
// represented in the result.
38+
_synthesizeSuiteTypes(into: &result)
39+
40+
return result.values
41+
}
42+
}
43+
44+
/// All available ``Test`` instances in the process, according to the runtime.
45+
///
46+
/// The order of values in this sequence is unspecified. This sequence may
47+
/// contain duplicates; callers should use ``all`` instead.
48+
private static var _all: some Sequence<Self> {
49+
get async {
50+
await withTaskGroup(of: [Self].self) { taskGroup in
51+
swt_enumerateTypes(&taskGroup) { type, context in
52+
if let type = unsafeBitCast(type, to: Any.Type.self) as? any __TestContainer.Type {
53+
let taskGroup = context!.assumingMemoryBound(to: TaskGroup<[Self]>.self)
54+
taskGroup.pointee.addTask {
55+
await type.__tests
56+
}
57+
}
58+
} withNamesMatching: { typeName, _ in
59+
// strstr() lets us avoid copying either string before comparing.
60+
Self._testContainerTypeNameMagic.withCString { testContainerTypeNameMagic in
61+
nil != strstr(typeName, testContainerTypeNameMagic)
62+
}
63+
}
64+
65+
return await taskGroup.reduce(into: [], +=)
66+
}
67+
}
68+
}
69+
70+
/// Create a dictionary mapping the IDs of a sequence of tests to those tests.
71+
///
72+
/// - Parameters:
73+
/// - tests: The sequence to convert to a dictionary.
74+
///
75+
/// - Returns: A dictionary containing `tests` keyed by those tests' IDs.
76+
static func testsByID(_ tests: some Sequence<Self>) -> [ID: Self] {
77+
[ID: Self](
78+
tests.lazy.map { ($0.id, $0) },
79+
uniquingKeysWith: { existing, _ in existing }
80+
)
81+
}
82+
83+
/// Synthesize any missing test suite types (that is, types containing test
84+
/// content that do not have the `@Suite` attribute) and add them to a
85+
/// dictionary of tests.
86+
///
87+
/// - Parameters:
88+
/// - tests: A dictionary of tests to amend.
89+
///
90+
/// - Returns: The number of key-value pairs added to `tests`.
91+
///
92+
/// - Bug: This function is necessary because containing type information is
93+
/// not available during expansion of the `@Test` macro.
94+
/// ([105470382](rdar://105470382))
95+
@discardableResult private static func _synthesizeSuiteTypes(into tests: inout [ID: Self]) -> Int {
96+
let originalCount = tests.count
97+
98+
// Find any instances of Test in the input that are *not* suites. We'll be
99+
// checking the containing types of each one.
100+
for test in tests.values where !test.isSuite {
101+
guard let suiteType = test.containingType else {
102+
continue
103+
}
104+
let suiteID = ID(type: suiteType)
105+
if tests[suiteID] == nil {
106+
// If the real test is hidden, so shall the synthesized test be hidden.
107+
// Copy the exact traits from the real test in case they someday carry
108+
// any interesting metadata.
109+
let traits = test.traits.compactMap { $0 as? HiddenTrait }
110+
tests[suiteID] = .__type(suiteType, displayName: nil, traits: traits, sourceLocation: test.sourceLocation)
111+
}
112+
}
113+
114+
return tests.count - originalCount
115+
}
116+
}

Sources/Testing/Test+Macro.swift

Lines changed: 0 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
99
//
1010

11-
private import TestingInternals
12-
1311
#if _runtime(_ObjC)
1412
public import ObjectiveC
1513

@@ -572,110 +570,3 @@ public func __invokeXCTestCaseMethod<T>(
572570
)
573571
return true
574572
}
575-
576-
// MARK: - Discovery
577-
578-
/// A protocol describing a type that contains tests.
579-
///
580-
/// - Warning: This protocol is used to implement the `@Test` macro. Do not use
581-
/// it directly.
582-
@_alwaysEmitConformanceMetadata
583-
public protocol __TestContainer {
584-
/// The set of tests contained by this type.
585-
static var __tests: [Test] { get async }
586-
}
587-
588-
extension Test {
589-
/// A string that appears within all auto-generated types conforming to the
590-
/// `__TestContainer` protocol.
591-
private static let _testContainerTypeNameMagic = "__🟠$test_container__"
592-
593-
/// All available ``Test`` instances in the process, according to the runtime.
594-
///
595-
/// The order of values in this sequence is unspecified.
596-
static var all: some Sequence<Test> {
597-
get async {
598-
// Convert the raw sequence of tests to a dictionary keyed by ID.
599-
var result = await testsByID(_all)
600-
601-
// Ensure test suite types that don't have the @Suite attribute are still
602-
// represented in the result.
603-
_synthesizeSuiteTypes(into: &result)
604-
605-
return result.values
606-
}
607-
}
608-
609-
/// All available ``Test`` instances in the process, according to the runtime.
610-
///
611-
/// The order of values in this sequence is unspecified. This sequence may
612-
/// contain duplicates; callers should use ``all`` instead.
613-
private static var _all: some Sequence<Self> {
614-
get async {
615-
await withTaskGroup(of: [Self].self) { taskGroup in
616-
swt_enumerateTypes({ typeName, _ in
617-
// strstr() lets us avoid copying either string before comparing.
618-
Self._testContainerTypeNameMagic.withCString { testContainerTypeNameMagic in
619-
nil != strstr(typeName, testContainerTypeNameMagic)
620-
}
621-
}, /*typeEnumerator:*/ { type, context in
622-
if let type = unsafeBitCast(type, to: Any.Type.self) as? any __TestContainer.Type {
623-
let taskGroup = context!.assumingMemoryBound(to: TaskGroup<[Self]>.self)
624-
taskGroup.pointee.addTask {
625-
await type.__tests
626-
}
627-
}
628-
}, &taskGroup)
629-
630-
return await taskGroup.reduce(into: [], +=)
631-
}
632-
}
633-
}
634-
635-
/// Create a dictionary mapping the IDs of a sequence of tests to those tests.
636-
///
637-
/// - Parameters:
638-
/// - tests: The sequence to convert to a dictionary.
639-
///
640-
/// - Returns: A dictionary containing `tests` keyed by those tests' IDs.
641-
static func testsByID(_ tests: some Sequence<Self>) -> [ID: Self] {
642-
[ID: Self](
643-
tests.lazy.map { ($0.id, $0) },
644-
uniquingKeysWith: { existing, _ in existing }
645-
)
646-
}
647-
648-
/// Synthesize any missing test suite types (that is, types containing test
649-
/// content that do not have the `@Suite` attribute) and add them to a
650-
/// dictionary of tests.
651-
///
652-
/// - Parameters:
653-
/// - tests: A dictionary of tests to amend.
654-
///
655-
/// - Returns: The number of key-value pairs added to `tests`.
656-
///
657-
/// - Bug: This function is necessary because containing type information is
658-
/// not available during expansion of the `@Test` macro.
659-
/// ([105470382](rdar://105470382))
660-
@discardableResult private static func _synthesizeSuiteTypes(into tests: inout [ID: Self]) -> Int {
661-
let originalCount = tests.count
662-
663-
// Find any instances of Test in the input that are *not* suites. We'll be
664-
// checking the containing types of each one.
665-
for test in tests.values where !test.isSuite {
666-
guard let suiteType = test.containingType else {
667-
continue
668-
}
669-
let suiteID = ID(type: suiteType)
670-
if tests[suiteID] == nil {
671-
// If the real test is hidden, so shall the synthesized test be hidden.
672-
// Copy the exact traits from the real test in case they someday carry
673-
// any interesting metadata.
674-
let traits = test.traits.compactMap { $0 as? HiddenTrait }
675-
tests[suiteID] = .__type(suiteType, displayName: nil, traits: traits, sourceLocation: test.sourceLocation)
676-
}
677-
}
678-
679-
return tests.count - originalCount
680-
}
681-
}

Sources/TestingInternals/Discovery.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ static void enumerateTypeMetadataSections(const SectionEnumerator& body) {
315315

316316
#pragma mark -
317317

318-
void swt_enumerateTypes(SWTTypeNameFilter nameFilter, SWTTypeEnumerator body, void *context) {
318+
void swt_enumerateTypes(void *context, SWTTypeEnumerator body, SWTTypeNameFilter nameFilter) {
319319
enumerateTypeMetadataSections([=] (const void *section, size_t size) {
320320
auto records = reinterpret_cast<const SWTTypeMetadataRecord *>(section);
321321
size_t recordCount = size / sizeof(SWTTypeMetadataRecord);

Sources/TestingInternals/include/Defines.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,7 @@
2929
/// An attribute that marks some value as being `Sendable` in Swift.
3030
#define SWT_SENDABLE __attribute__((swift_attr("@Sendable")))
3131

32+
/// An attribute that renames a C symbol in Swift.
33+
#define SWT_SWIFT_NAME(name) __attribute__((swift_name(#name)))
34+
3235
#endif // SWT_DEFINES_H

Sources/TestingInternals/include/Discovery.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,11 @@ typedef bool (* SWTTypeNameFilter)(const char *typeName, void *_Null_unspecified
4949
/// it is present in an image's metadata table multiple times, or if it is an
5050
/// Objective-C class implemented in Swift.) Callers are responsible for
5151
/// deduping type metadata pointers passed to `body`.
52-
SWT_EXTERN void swt_enumerateTypes(SWTTypeNameFilter _Nullable nameFilter, SWTTypeEnumerator body, void *_Null_unspecified context);
52+
SWT_EXTERN void swt_enumerateTypes(
53+
void *_Null_unspecified context,
54+
SWTTypeEnumerator body,
55+
SWTTypeNameFilter _Nullable nameFilter
56+
) SWT_SWIFT_NAME(swt_enumerateTypes(_:_:withNamesMatching:));
5357

5458
SWT_ASSUME_NONNULL_END
5559

0 commit comments

Comments
 (0)