Skip to content
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
2 changes: 1 addition & 1 deletion Sources/Testing/Test+Macro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
51 changes: 49 additions & 2 deletions Sources/Testing/Test.Case.Generator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,53 @@ extension Test.Case.Generator: Sequence {
}
}

// MARK: - TestCases
// MARK: - Type-erasing to Sequence<Test.Case>

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<Test.Case> & 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<Test.Case> & 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<S>(_ testCases: Test.Case.Generator<S>) {
_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<Test.Case>

fileprivate init(iterator: any IteratorProtocol<Test.Case>) {
_iterator = iterator
}

mutating func next() -> Test.Case? {
_iterator.next()
}
}

func makeIterator() -> Iterator {
Iterator(iterator: _sequence.makeIterator())
}

var underestimatedCount: Int {
_sequence.underestimatedCount
}
}
}
9 changes: 0 additions & 9 deletions Sources/Testing/Test.Case.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Test.Case> & 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<Test.Case> & Sendable {}
30 changes: 22 additions & 8 deletions Sources/Testing/Test.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,19 +95,17 @@ 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.
///
/// For parameterized tests, each test case is associated with a single
/// 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<Test.Case>)? {
_testCases as? any Sequence<Test.Case>
public var testCases: (some Sequence<Test.Case> & Sendable)? {
_testCases
}

/// Whether or not this test is parameterized.
Expand Down Expand Up @@ -139,23 +137,39 @@ 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<S>(
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<S>,
parameters: [ParameterInfo]
) {
self.name = name
self.displayName = displayName
self.traits = traits
self.sourceLocation = sourceLocation
self.containingType = containingType
self.xcTestCompatibleSelector = xcTestCompatibleSelector
self._testCases = testCases
self._testCases = Test.Cases(testCases)
self.parameters = parameters
}
}
Expand Down