diff --git a/Sources/Testing/Test+Macro.swift b/Sources/Testing/Test+Macro.swift index 217eba42..a305ec55 100644 --- a/Sources/Testing/Test+Macro.swift +++ b/Sources/Testing/Test+Macro.swift @@ -113,7 +113,7 @@ extension Test { sourceLocation: SourceLocation ) -> Self { let typeName = _typeName(containingType, qualified: false) - return Self(name: typeName, displayName: displayName, traits: traits, sourceLocation: sourceLocation, containingType: containingType, testCases: nil) + return Self(name: typeName, displayName: displayName, traits: traits, sourceLocation: sourceLocation, containingType: containingType) } } diff --git a/Sources/Testing/Test.Case.Generator.swift b/Sources/Testing/Test.Case.Generator.swift index b977927c..2f30cc3d 100644 --- a/Sources/Testing/Test.Case.Generator.swift +++ b/Sources/Testing/Test.Case.Generator.swift @@ -265,6 +265,53 @@ extension Test.Case.Generator: Sequence { } } -// MARK: - TestCases +// MARK: - Type-erasing to Sequence -extension Test.Case.Generator: TestCases {} +/// A type-erased protocol describing a sequence of ``Test/Case`` instances. +/// +/// This protocol is necessary because it is not currently possible to express +/// `Sequence & Sendable` as an existential (`any`) +/// ([96960993](rdar://96960993)). It is also not possible to have a value of +/// an underlying generic sequence type without specifying its generic +/// parameters. +private protocol _TestCases: Sequence & Sendable {} + +extension Test.Case.Generator: _TestCases {} + +extension Test { + /// A type-erasing wrapper for a `_TestCases`-conforming type. + /// + /// See the documentation for the `_TestCases` protocol explaining why this + /// type erasure is necessary. + struct Cases: Sequence, Sendable { + /// The type-erased sequence of test cases this instance wraps. + private var _sequence: any _TestCases + + init(_ testCases: Test.Case.Generator) { + _sequence = testCases + } + + /// A type-erasing wrapper for an iterator of a `_TestCases`-conforming + /// type. + struct Iterator: IteratorProtocol { + /// The type-erased test case iterator this instance wraps. + private var _iterator: any IteratorProtocol + + fileprivate init(iterator: any IteratorProtocol) { + _iterator = iterator + } + + mutating func next() -> Test.Case? { + _iterator.next() + } + } + + func makeIterator() -> Iterator { + Iterator(iterator: _sequence.makeIterator()) + } + + var underestimatedCount: Int { + _sequence.underestimatedCount + } + } +} diff --git a/Sources/Testing/Test.Case.swift b/Sources/Testing/Test.Case.swift index 3995c86d..61c1030c 100644 --- a/Sources/Testing/Test.Case.swift +++ b/Sources/Testing/Test.Case.swift @@ -96,12 +96,3 @@ extension Test { public var secondName: String? } } - -/// A type-erased protocol describing a sequence of ``Test/Case`` instances. -/// -/// This protocol is necessary because it is not currently possible to express -/// `Sequence & Sendable` as an existential (`any`) -/// ([96960993](rdar://96960993)). It is also not possible to have a value of -/// an underlying generic sequence type without specifying its generic -/// parameters. -protocol TestCases: Sequence & Sendable {} diff --git a/Sources/Testing/Test.swift b/Sources/Testing/Test.swift index 6138ac8c..647a103a 100644 --- a/Sources/Testing/Test.swift +++ b/Sources/Testing/Test.swift @@ -95,7 +95,7 @@ public struct Test: Sendable { public var xcTestCompatibleSelector: __XCTestCompatibleSelector? /// Storage for the ``testCases`` property. - private var _testCases: (any TestCases)? + private var _testCases: Test.Cases? /// The set of test cases associated with this test, if any. /// @@ -103,11 +103,9 @@ public struct Test: Sendable { /// combination of parameterized inputs. For non-parameterized tests, a single /// test case is synthesized. For test suite types (as opposed to test /// functions), the value of this property is `nil`. - /// - /// The value of this property is guaranteed to be `Sendable`. @_spi(ExperimentalParameterizedTesting) - public var testCases: (any Sequence)? { - _testCases as? any Sequence + public var testCases: (some Sequence & Sendable)? { + _testCases } /// Whether or not this test is parameterized. @@ -139,15 +137,31 @@ public struct Test: Sendable { containingType != nil && testCases == nil } + /// Initialize an instance of this type representing a test suite type. init( + name: String, + displayName: String? = nil, + traits: [any Trait], + sourceLocation: SourceLocation, + containingType: Any.Type + ) { + self.name = name + self.displayName = displayName + self.traits = traits + self.sourceLocation = sourceLocation + self.containingType = containingType + } + + /// Initialize an instance of this type representing a test function. + init( name: String, displayName: String? = nil, traits: [any Trait], sourceLocation: SourceLocation, containingType: Any.Type? = nil, xcTestCompatibleSelector: __XCTestCompatibleSelector? = nil, - testCases: (any TestCases)? = nil, - parameters: [ParameterInfo]? = nil + testCases: Test.Case.Generator, + parameters: [ParameterInfo] ) { self.name = name self.displayName = displayName @@ -155,7 +169,7 @@ public struct Test: Sendable { self.sourceLocation = sourceLocation self.containingType = containingType self.xcTestCompatibleSelector = xcTestCompatibleSelector - self._testCases = testCases + self._testCases = Test.Cases(testCases) self.parameters = parameters } }