Skip to content
Closed
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/Running/Runner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public struct Runner: Sendable {
/// - Parameters:
/// - tests: The tests to run.
/// - configuration: The configuration to use for running.
public init(testing tests: [Test], configuration: Configuration = .init()) async {
public init(testing tests: some Sequence<Test>, configuration: Configuration = .init()) async {
self.plan = await Plan(tests: tests, configuration: configuration)
self.configuration = configuration
}
Expand Down
113 changes: 113 additions & 0 deletions Sources/Testing/Test+Discovery.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//

@_implementationOnly import TestingInternals

/// A protocol describing a type that contains tests.
///
/// - Warning: This protocol is used to implement the `@Test` macro. Do not use
/// it directly.
@_alwaysEmitConformanceMetadata
public protocol __TestContainer {
/// The set of tests contained by this type.
static var __tests: [Test] { get }
}

extension Test {
/// A string that appears within all auto-generated types conforming to the
/// `__TestContainer` protocol.
private static let _testContainerTypeNameMagic = "__🟠$test_container__"

/// All available ``Test`` instances in the process, according to the runtime.
///
/// The order of values in this sequence is unspecified.
@_spi(ExperimentalTestDiscovery)
public static var all: some Sequence<Test> {
// Convert the raw sequence of tests to a dictionary keyed by ID.
var result = testsByID(_all)

// Ensure test suite types that don't have the @Suite attribute are still
// represented in the result.
_synthesizeSuiteTypes(into: &result)

return result.values
}

/// All available ``Test`` instances in the process, according to the runtime.
///
/// The order of values in this sequence is unspecified. This sequence may
/// contain duplicates; callers should use ``all`` instead.
private static var _all: some Sequence<Test> {
var result = [Self]()

withUnsafeMutablePointer(to: &result) { result in
swt_enumerateTypes({ typeName, _ in
// strstr() lets us avoid copying either string before comparing.
Self._testContainerTypeNameMagic.withCString { testContainerTypeNameMagic in
nil != strstr(typeName, testContainerTypeNameMagic)
}
}, /*typeEnumerator:*/ { type, context in
if let context, let type = unsafeBitCast(type, to: Any.Type.self) as? any __TestContainer.Type {
let result = context.assumingMemoryBound(to: Array<Self>.self)
result.pointee.append(contentsOf: type.__tests)
}
}, result)
}

return result
}

/// Create a dictionary mapping the IDs of a sequence of tests to those tests.
///
/// - Parameters:
/// - tests: The sequence to convert to a dictionary.
///
/// - Returns: A dictionary containing `tests` keyed by those tests' IDs.
static func testsByID(_ tests: some Sequence<Self>) -> [ID: Self] {
[ID: Self](
tests.lazy.map { ($0.id, $0) },
uniquingKeysWith: { existing, _ in existing }
)
}

/// Synthesize any missing test suite types (that is, types containing test
/// content that do not have the `@Suite` attribute) and add them to a
/// dictionary of tests.
///
/// - Parameters:
/// - tests: A dictionary of tests to amend.
///
/// - Returns: The number of key-value pairs added to `tests`.
///
/// - Bug: This function is necessary because containing type information is
/// not available during expansion of the `@Test` macro.
/// ([105470382](rdar://105470382))
@discardableResult private static func _synthesizeSuiteTypes(into tests: inout [ID: Self]) -> Int {
let originalCount = tests.count

// Find any instances of Test in the input that are *not* suites. We'll be
// checking the containing types of each one.
for test in tests.values where !test.isSuite {
guard let suiteType = test.containingType else {
continue
}
let suiteID = ID(type: suiteType)
if tests[suiteID] == nil {
// If the real test is hidden, so shall the synthesized test be hidden.
// Copy the exact traits from the real test in case they someday carry
// any interesting metadata.
let traits = test.traits.compactMap { $0 as? HiddenTrait }
tests[suiteID] = .__type(suiteType, displayName: nil, traits: traits, sourceLocation: test.sourceLocation)
}
}

return tests.count - originalCount
}
}
105 changes: 0 additions & 105 deletions Sources/Testing/Test+Macro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//

@_implementationOnly import TestingInternals

#if _runtime(_ObjC)
import ObjectiveC

Expand Down Expand Up @@ -532,106 +530,3 @@ public func __invokeXCTestCaseMethod<T>(
return true
}
#endif

// MARK: - Discovery

/// A protocol describing a type that contains tests.
///
/// - Warning: This protocol is used to implement the `@Test` macro. Do not use
/// it directly.
@_alwaysEmitConformanceMetadata
public protocol __TestContainer {
/// The set of tests contained by this type.
static var __tests: [Test] { get }
}

extension Test {
/// A string that appears within all auto-generated types conforming to the
/// `__TestContainer` protocol.
private static let _testContainerTypeNameMagic = "__🟠$test_container__"

/// All available ``Test`` instances in the process, according to the runtime.
///
/// The order of values in this sequence is unspecified.
static var all: some Sequence<Test> {
// Convert the raw sequence of tests to a dictionary keyed by ID.
var result = testsByID(_all)

// Ensure test suite types that don't have the @Suite attribute are still
// represented in the result.
_synthesizeSuiteTypes(into: &result)

return result.values
}

/// All available ``Test`` instances in the process, according to the runtime.
///
/// The order of values in this sequence is unspecified. This sequence may
/// contain duplicates; callers should use ``all`` instead.
private static var _all: some Sequence<Test> {
var result = [Self]()

withUnsafeMutablePointer(to: &result) { result in
swt_enumerateTypes({ typeName, _ in
// strstr() lets us avoid copying either string before comparing.
Self._testContainerTypeNameMagic.withCString { testContainerTypeNameMagic in
nil != strstr(typeName, testContainerTypeNameMagic)
}
}, /*typeEnumerator:*/ { type, context in
if let context, let type = unsafeBitCast(type, to: Any.Type.self) as? any __TestContainer.Type {
let result = context.assumingMemoryBound(to: Array<Self>.self)
result.pointee.append(contentsOf: type.__tests)
}
}, result)
}

return result
}

/// Create a dictionary mapping the IDs of a sequence of tests to those tests.
///
/// - Parameters:
/// - tests: The sequence to convert to a dictionary.
///
/// - Returns: A dictionary containing `tests` keyed by those tests' IDs.
static func testsByID(_ tests: some Sequence<Self>) -> [ID: Self] {
[ID: Self](
tests.lazy.map { ($0.id, $0) },
uniquingKeysWith: { existing, _ in existing }
)
}

/// Synthesize any missing test suite types (that is, types containing test
/// content that do not have the `@Suite` attribute) and add them to a
/// dictionary of tests.
///
/// - Parameters:
/// - tests: A dictionary of tests to amend.
///
/// - Returns: The number of key-value pairs added to `tests`.
///
/// - Bug: This function is necessary because containing type information is
/// not available during expansion of the `@Test` macro.
/// ([105470382](rdar://105470382))
@discardableResult private static func _synthesizeSuiteTypes(into tests: inout [ID: Self]) -> Int {
let originalCount = tests.count

// Find any instances of Test in the input that are *not* suites. We'll be
// checking the containing types of each one.
for test in tests.values where !test.isSuite {
guard let suiteType = test.containingType else {
continue
}
let suiteID = ID(type: suiteType)
if tests[suiteID] == nil {
// If the real test is hidden, so shall the synthesized test be hidden.
// Copy the exact traits from the real test in case they someday carry
// any interesting metadata.
let traits = test.traits.compactMap { $0 as? HiddenTrait }
tests[suiteID] = .__type(suiteType, displayName: nil, traits: traits, sourceLocation: test.sourceLocation)
}
}

return tests.count - originalCount
}
}
2 changes: 1 addition & 1 deletion Tests/TestingTests/MiscellaneousTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//

@testable @_spi(ExperimentalParameterizedTesting) @_spi(ExperimentalTestRunning) import Testing
@testable @_spi(ExperimentalParameterizedTesting) @_spi(ExperimentalTestRunning) @_spi(ExperimentalTestDiscovery) import Testing

@Test(/* name unspecified */ .hidden)
@Sendable func freeSyncFunction() {}
Expand Down
2 changes: 1 addition & 1 deletion Tests/TestingTests/TestSupport/TestingAdditions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//

@testable @_spi(ExperimentalTestRunning) @_spi(ExperimentalParameterizedTesting) import Testing
@testable @_spi(ExperimentalTestRunning) @_spi(ExperimentalParameterizedTesting) @_spi(ExperimentalTestDiscovery) import Testing
#if canImport(XCTest)
import XCTest
#endif
Expand Down