forked from swiftlang/swift-testing
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathRunner.RuntimeState.swift
258 lines (232 loc) · 9.6 KB
/
Runner.RuntimeState.swift
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
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
//
// 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: () throws -> R) rethrows -> R {
let id = configuration._addToAll()
defer {
configuration._removeFromAll(identifiedBy: id)
}
var runtimeState = Runner.RuntimeState.current ?? .init()
runtimeState.configuration = configuration
return try Runner.RuntimeState.$current.withValue(runtimeState, operation: body)
}
/// Call an asynchronous 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)
}
}
/// Get the current test and test case in a single operation.
///
/// - Returns: The current test and test case.
///
/// This function is more efficient than calling both ``Test/current`` and
/// ``Test/Case/current``.
func currentTestAndTestCase() -> (Test?, Test.Case?) {
guard let state = Runner.RuntimeState.current else {
return (nil, nil)
}
return (state.test, state.testCase)
}