1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
|
//
// 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
//
extension Runner {
/// A type which collects the task-scoped runtime state for a running
/// ``Runner`` instance, the tests it runs, and other objects it interacts
/// with.
///
/// This type is intended for use via the task-local
/// ``Runner/RuntimeState/current`` property.
fileprivate struct RuntimeState: Sendable {
/// The configuration for the current task, if any.
var configuration: Configuration?
/// The test that is running on the current task, if any.
var test: Test?
/// The test case that is running on the current task, if any.
var testCase: Test.Case?
/// The runtime state related to the runner running on the current task,
/// if any.
@TaskLocal
static var current: Self?
}
}
extension Runner {
/// Modify the event handler of this instance's ``Configuration`` to ensure it
/// is invoked using the current ``RuntimeState`` value.
///
/// This is meant to be called prior to running tests using this instance. It
/// allows any events posted during the call to this instance's event handler
/// to be directed to the previously-configured event handler, if any.
///
/// In practice, the primary scenario where this is important is when running
/// the testing library's own tests.
mutating func configureEventHandlerRuntimeState() {
guard let existingRuntimeState = RuntimeState.current else {
return
}
configuration.eventHandler = { [eventHandler = configuration.eventHandler] event, context in
RuntimeState.$current.withValue(existingRuntimeState) {
eventHandler(event, context)
}
}
}
}
// MARK: - Current configuration
extension Configuration {
/// The configuration for the current task, if any.
public static var current: Self? {
Runner.RuntimeState.current?.configuration
}
/// Call a function while the value of ``Configuration/current`` is set.
///
/// - Parameters:
/// - configuration: The new value to set for ``Configuration/current``.
/// - body: A function to call.
///
/// - Returns: Whatever is returned by `body`.
///
/// - Throws: Whatever is thrown by `body`.
static func withCurrent<R>(_ configuration: Self, perform body: () async throws -> R) async rethrows -> R {
let id = configuration._addToAll()
defer {
configuration._removeFromAll(identifiedBy: id)
}
var runtimeState = Runner.RuntimeState.current ?? .init()
runtimeState.configuration = configuration
return try await Runner.RuntimeState.$current.withValue(runtimeState, operation: body)
}
/// A type containing the mutable state tracked by ``Configuration/_all`` and,
/// indirectly, by ``Configuration/all``.
private struct _All: Sendable {
/// All instances of ``Configuration`` set as current, keyed by their unique
/// identifiers.
var instances = [UInt64: Configuration]()
/// The next available unique identifier for a configuration.
var nextID: UInt64 = 0
}
/// Mutable storage for ``Configuration/all``.
private static let _all = Locked(rawValue: _All())
/// A collection containing all instances of this type that are currently set
/// as the current configuration for a task.
///
/// This property is used when an event is posted in a context where the value
/// of ``Configuration/current`` is `nil`, such as from a detached task.
static var all: some Collection<Self> {
_all.rawValue.instances.values
}
/// Add this instance to ``Configuration/all``.
///
/// - Returns: A unique number identifying `self` that can be
/// passed to `_removeFromAll(identifiedBy:)`` to unregister it.
private func _addToAll() -> UInt64 {
if deliverExpectationCheckedEvents {
Self._deliverExpectationCheckedEventsCount.increment()
}
return Self._all.withLock { all in
let id = all.nextID
all.nextID += 1
all.instances[id] = self
return id
}
}
/// Remove this instance from ``Configuration/all``.
///
/// - Parameters:
/// - id: The unique identifier of this instance, as previously returned by
/// `_addToAll()`.
private func _removeFromAll(identifiedBy id: UInt64) {
let configuration = Self._all.withLock { all in
all.instances.removeValue(forKey: id)
}
if let configuration, configuration.deliverExpectationCheckedEvents {
Self._deliverExpectationCheckedEventsCount.decrement()
}
}
/// An atomic counter that tracks the number of "current" configurations that
/// have set ``deliverExpectationCheckedEvents`` to `true`.
///
/// On older Apple platforms, this property is not available and ``all`` is
/// directly consulted instead (which is less efficient.)
private static let _deliverExpectationCheckedEventsCount = Locked(rawValue: 0)
/// Whether or not events of the kind
/// ``Event/Kind-swift.enum/expectationChecked(_:)`` should be delivered to
/// the event handler of _any_ configuration set as current for a task in the
/// current process.
///
/// To determine if an individual instance of ``Configuration`` is listening
/// for these events, consult the per-instance
/// ``Configuration/deliverExpectationCheckedEvents`` property.
static var deliverExpectationCheckedEvents: Bool {
_deliverExpectationCheckedEventsCount.rawValue > 0
}
}
// MARK: - Current test and test case
extension Test {
/// The test that is running on the current task, if any.
///
/// If the current task is running a test, or is a subtask of another task
/// that is running a test, the value of this property describes that test. If
/// no test is currently running, the value of this property is `nil`.
///
/// If the current task is detached from a task that started running a test,
/// or if the current thread was created without using Swift concurrency (e.g.
/// by using [`Thread.detachNewThread(_:)`](https://developer.apple.com/documentation/foundation/thread/2088563-detachnewthread)
/// or [`DispatchQueue.async(execute:)`](https://developer.apple.com/documentation/dispatch/dispatchqueue/2016103-async)),
/// the value of this property may be `nil`.
public static var current: Self? {
Runner.RuntimeState.current?.test
}
/// Call a function while the value of ``Test/current`` is set.
///
/// - Parameters:
/// - test: The new value to set for ``Test/current``.
/// - body: A function to call.
///
/// - Returns: Whatever is returned by `body`.
///
/// - Throws: Whatever is thrown by `body`.
static func withCurrent<R>(_ test: Self, perform body: () async throws -> R) async rethrows -> R {
var runtimeState = Runner.RuntimeState.current ?? .init()
runtimeState.test = test
return try await Runner.RuntimeState.$current.withValue(runtimeState, operation: body)
}
}
extension Test.Case {
/// The test case that is running on the current task, if any.
///
/// If the current task is running a test, or is a subtask of another task
/// that is running a test, the value of this property describes the test's
/// currently-running case. If no test is currently running, the value of this
/// property is `nil`.
///
/// If the current task is detached from a task that started running a test,
/// or if the current thread was created without using Swift concurrency (e.g.
/// by using [`Thread.detachNewThread(_:)`](https://developer.apple.com/documentation/foundation/thread/2088563-detachnewthread)
/// or [`DispatchQueue.async(execute:)`](https://developer.apple.com/documentation/dispatch/dispatchqueue/2016103-async)),
/// the value of this property may be `nil`.
public static var current: Self? {
Runner.RuntimeState.current?.testCase
}
/// Call a function while the value of ``Test/Case/current`` is set.
///
/// - Parameters:
/// - testCase: The new value to set for ``Test/Case/current``.
/// - body: A function to call.
///
/// - Returns: Whatever is returned by `body`.
///
/// - Throws: Whatever is thrown by `body`.
static func withCurrent<R>(_ testCase: Self, perform body: () async throws -> R) async rethrows -> R {
var runtimeState = Runner.RuntimeState.current ?? .init()
runtimeState.testCase = testCase
return try await Runner.RuntimeState.$current.withValue(runtimeState, operation: body)
}
}
|