-
Notifications
You must be signed in to change notification settings - Fork 103
/
Copy pathExitTest.CapturedValue.swift
168 lines (150 loc) · 6.63 KB
/
ExitTest.CapturedValue.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
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2023–2025 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
//
#if !SWT_NO_EXIT_TESTS
@_spi(Experimental) @_spi(ForToolsIntegrationOnly)
extension ExitTest {
/// A type representing a value captured by an exit test's body.
///
/// An instance of this type may represent the actual value that was captured
/// when the exit test was invoked. In the child process created by the
/// current exit test handler, instances will initially only have the type of
/// the value, but not the value itself.
///
/// Instances of this type are created automatically by the testing library
/// for all elements in an exit test body's capture list and are stored in the
/// exit test's ``capturedValues`` property. For example, given the following
/// exit test:
///
/// ```swift
/// await #expect(exitsWith: .failure) { [a = a as T, b = b as U, c = c as V] in
/// ...
/// }
/// ```
///
/// There are three captured values in its ``capturedValues`` property. These
/// values are captured at the time the exit test is called, as they would be
/// if the closure were called locally.
///
/// The current exit test handler is responsible for encoding and decoding
/// instances of this type. When the handler is called, it is passed an
/// instance of ``ExitTest``. The handler encodes the values in that
/// instance's ``capturedValues`` property, then passes the encoded forms of
/// those values to the child process. The encoding format and message-passing
/// interface are implementation details of the exit test handler.
///
/// When the child process calls ``ExitTest/find(identifiedBy:)``, it receives
/// an instance of ``ExitTest`` whose ``capturedValues`` property contains
/// type information but no values. The child process decodes the values it
/// encoded in the parent process and then updates the ``wrappedValue``
/// property of each element in the array before calling the exit test's body.
public struct CapturedValue: Sendable {
/// An enumeration of the different states a captured value can have.
private enum _Kind: Sendable {
/// The runtime value of the captured value is known.
case wrappedValue(any Codable & Sendable)
/// Only the type of the captured value is known.
case typeOnly(any (Codable & Sendable).Type)
}
/// The current state of this instance.
private var _kind: _Kind
init(wrappedValue: some Codable & Sendable) {
_kind = .wrappedValue(wrappedValue)
}
init(typeOnly type: (some Codable & Sendable).Type) {
_kind = .typeOnly(type)
}
/// The underlying value captured by this instance at runtime.
///
/// In a child process created by the current exit test handler, the value
/// of this property is `nil` until the entry point sets it.
public var wrappedValue: (any Codable & Sendable)? {
get {
if case let .wrappedValue(wrappedValue) = _kind {
return wrappedValue
}
return nil
}
set {
let type = typeOfWrappedValue
func validate<T, U>(_ newValue: T, is expectedType: U.Type) {
assert(newValue is U, "Attempted to set a captured value to an instance of '\(String(describingForTest: T.self))', but an instance of '\(String(describingForTest: U.self))' was expected.")
}
validate(newValue, is: type)
if let newValue {
_kind = .wrappedValue(newValue)
} else {
_kind = .typeOnly(type)
}
}
}
/// The type of the underlying value captured by this instance.
///
/// This type is known at compile time and is always available, even before
/// this instance's ``wrappedValue`` property is set.
public var typeOfWrappedValue: any (Codable & Sendable).Type {
switch _kind {
case let .wrappedValue(wrappedValue):
type(of: wrappedValue)
case let .typeOnly(type):
type
}
}
}
}
// MARK: - Collection conveniences
extension Array where Element == ExitTest.CapturedValue {
init<each T>(_ wrappedValues: repeat each T) where repeat each T: Codable & Sendable {
self.init()
repeat self.append(ExitTest.CapturedValue(wrappedValue: each wrappedValues))
}
init<each T>(_ typesOfWrappedValues: repeat (each T).Type) where repeat each T: Codable & Sendable {
self.init()
repeat self.append(ExitTest.CapturedValue(typeOnly: (each typesOfWrappedValues).self))
}
}
extension Collection where Element == ExitTest.CapturedValue {
/// Cast the elements in this collection to a tuple of their wrapped values.
///
/// - Returns: A tuple containing the wrapped values of the elements in this
/// collection.
///
/// - Throws: If an expected value could not be found or was not of the
/// type the caller expected.
///
/// This function assumes that the entry point function has already set the
/// ``wrappedValue`` property of each element in this collection.
func takeCapturedValues<each T>() throws -> (repeat each T) {
func nextValue<U>(
as type: U.Type,
from capturedValues: inout SubSequence
) throws -> U {
// Get the next captured value in the collection. If we run out of values
// before running out of parameter pack elements, then something in the
// exit test handler or entry point is likely broken.
guard let wrappedValue = capturedValues.first?.wrappedValue else {
let actualCount = self.count
let expectedCount = parameterPackCount(repeat (each T).self)
fatalError("Found fewer captured values (\(actualCount)) than expected (\(expectedCount)) when passing them to the current exit test.")
}
// Next loop, get the next element. (We're mutating a subsequence, not
// self, so this is generally an O(1) operation.)
capturedValues = capturedValues.dropFirst()
// Make sure the value is of the correct type. If it's not, that's also
// probably a problem with the exit test handler or entry point.
guard let wrappedValue = wrappedValue as? U else {
fatalError("Expected captured value at index \(capturedValues.startIndex) with type '\(String(describingForTest: U.self))', but found an instance of '\(String(describingForTest: Swift.type(of: wrappedValue)))' instead.")
}
return wrappedValue
}
var capturedValues = self[...]
return (repeat try nextValue(as: (each T).self, from: &capturedValues))
}
}
#endif