|
| 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 | +} |
0 commit comments