@@ -41,6 +41,150 @@ public protocol Trait: Sendable {
41
41
///
42
42
/// By default, the value of this property is an empty array.
43
43
var comments : [ Comment ] { get }
44
+
45
+ /// The type of the test scope provider for this trait.
46
+ ///
47
+ /// The default type is `Never`, which cannot be instantiated. The
48
+ /// ``scopeProvider(for:testCase:)-cjmg`` method for any trait with this
49
+ /// default type must return `nil`, meaning that trait will not provide a
50
+ /// custom scope for the tests it's applied to.
51
+ associatedtype TestScopeProvider : TestScoping = Never
52
+
53
+ /// Get this trait's scope provider for the specified test and/or test case,
54
+ /// if any.
55
+ ///
56
+ /// - Parameters:
57
+ /// - test: The test for which a scope provider is being requested.
58
+ /// - testCase: The test case for which a scope provider is being requested,
59
+ /// if any. When `test` represents a suite, the value of this argument is
60
+ /// `nil`.
61
+ ///
62
+ /// - Returns: A value conforming to ``Trait/TestScopeProvider`` which may be
63
+ /// used to provide custom scoping for `test` and/or `testCase`, or `nil` if
64
+ /// they should not have any custom scope.
65
+ ///
66
+ /// If this trait's type conforms to ``TestScoping``, the default value
67
+ /// returned by this method depends on `test` and/or `testCase`:
68
+ ///
69
+ /// - If `test` represents a suite, this trait must conform to ``SuiteTrait``.
70
+ /// If the value of this suite trait's ``SuiteTrait/isRecursive`` property
71
+ /// is `true`, then this method returns `nil`; otherwise, it returns `self`.
72
+ /// This means that by default, a suite trait will _either_ provide its
73
+ /// custom scope once for the entire suite, or once per-test function it
74
+ /// contains.
75
+ /// - Otherwise `test` represents a test function. If `testCase` is `nil`,
76
+ /// this method returns `nil`; otherwise, it returns `self`. This means that
77
+ /// by default, a trait which is applied to or inherited by a test function
78
+ /// will provide its custom scope once for each of that function's cases.
79
+ ///
80
+ /// A trait may explicitly implement this method to further customize the
81
+ /// default behaviors above. For example, if a trait should provide custom
82
+ /// test scope both once per-suite and once per-test function in that suite,
83
+ /// it may implement the method and return a non-`nil` scope provider under
84
+ /// those conditions.
85
+ ///
86
+ /// A trait may also implement this method and return `nil` if it determines
87
+ /// that it does not need to provide a custom scope for a particular test at
88
+ /// runtime, even if the test has the trait applied. This can improve
89
+ /// performance and make diagnostics clearer by avoiding an unnecessary call
90
+ /// to ``TestScoping/provideScope(for:testCase:performing:)``.
91
+ ///
92
+ /// If this trait's type does not conform to ``TestScoping`` and its
93
+ /// associated ``Trait/TestScopeProvider`` type is the default `Never`, then
94
+ /// this method returns `nil` by default. This means that instances of this
95
+ /// trait will not provide a custom scope for tests to which they're applied.
96
+ func scopeProvider( for test: Test , testCase: Test . Case ? ) -> TestScopeProvider ?
97
+ }
98
+
99
+ /// A protocol that allows providing a custom execution scope for a test
100
+ /// function (and each of its cases) or a test suite by performing custom code
101
+ /// before or after it runs.
102
+ ///
103
+ /// Types conforming to this protocol may be used in conjunction with a
104
+ /// ``Trait``-conforming type by implementing the
105
+ /// ``Trait/scopeProvider(for:testCase:)-cjmg`` method, allowing custom traits
106
+ /// to provide custom scope for tests. Consolidating common set-up and tear-down
107
+ /// logic for tests which have similar needs allows each test function to be
108
+ /// more succinct with less repetitive boilerplate so it can focus on what makes
109
+ /// it unique.
110
+ public protocol TestScoping : Sendable {
111
+ /// Provide custom execution scope for a function call which is related to the
112
+ /// specified test and/or test case.
113
+ ///
114
+ /// - Parameters:
115
+ /// - test: The test under which `function` is being performed.
116
+ /// - testCase: The test case, if any, under which `function` is being
117
+ /// performed. When invoked on a suite, the value of this argument is
118
+ /// `nil`.
119
+ /// - function: The function to perform. If `test` represents a test suite,
120
+ /// this function encapsulates running all the tests in that suite. If
121
+ /// `test` represents a test function, this function is the body of that
122
+ /// test function (including all cases if it is parameterized.)
123
+ ///
124
+ /// - Throws: Whatever is thrown by `function`, or an error preventing this
125
+ /// type from providing a custom scope correctly. An error thrown from this
126
+ /// method is recorded as an issue associated with `test`. If an error is
127
+ /// thrown before `function` is called, the corresponding test will not run.
128
+ ///
129
+ /// When the testing library is preparing to run a test, it starts by finding
130
+ /// all traits applied to that test, including those inherited from containing
131
+ /// suites. It begins with inherited suite traits, sorting them
132
+ /// outermost-to-innermost, and if the test is a function, it then adds all
133
+ /// traits applied directly to that functions in the order they were applied
134
+ /// (left-to-right). It then asks each trait for its scope provider (if any)
135
+ /// by calling ``Trait/scopeProvider(for:testCase:)-cjmg``. Finally, it calls
136
+ /// this method on all non-`nil` scope providers, giving each an opportunity
137
+ /// to perform arbitrary work before or after invoking `function`.
138
+ ///
139
+ /// This method should either invoke `function` once before returning or throw
140
+ /// an error if it is unable to provide a custom scope.
141
+ ///
142
+ /// Issues recorded by this method are associated with `test`.
143
+ func provideScope( for test: Test , testCase: Test . Case ? , performing function: @Sendable ( ) async throws -> Void ) async throws
144
+ }
145
+
146
+ extension Trait where Self: TestScoping {
147
+ /// Get this trait's scope provider for the specified test and/or test case,
148
+ /// if any.
149
+ ///
150
+ /// - Parameters:
151
+ /// - test: The test for which a scope provider is being requested.
152
+ /// - testCase: The test case for which a scope provider is being requested,
153
+ /// if any. When `test` represents a suite, the value of this argument is
154
+ /// `nil`.
155
+ ///
156
+ /// This default implementation is used when this trait type conforms to
157
+ /// ``TestScoping`` and its return value is discussed in
158
+ /// ``Trait/scopeProvider(for:testCase:)-cjmg``.
159
+ public func scopeProvider( for test: Test , testCase: Test . Case ? ) -> Self ? {
160
+ testCase == nil ? nil : self
161
+ }
162
+ }
163
+
164
+ extension SuiteTrait where Self: TestScoping {
165
+ /// Get this trait's scope provider for the specified test and/or test case,
166
+ /// if any.
167
+ ///
168
+ /// - Parameters:
169
+ /// - test: The test for which a scope provider is being requested.
170
+ /// - testCase: The test case for which a scope provider is being requested,
171
+ /// if any. When `test` represents a suite, the value of this argument is
172
+ /// `nil`.
173
+ ///
174
+ /// This default implementation is used when this trait type conforms to
175
+ /// ``TestScoping`` and its return value is discussed in
176
+ /// ``Trait/scopeProvider(for:testCase:)-cjmg``.
177
+ public func scopeProvider( for test: Test , testCase: Test . Case ? ) -> Self ? {
178
+ if test. isSuite {
179
+ isRecursive ? nil : self
180
+ } else {
181
+ testCase == nil ? nil : self
182
+ }
183
+ }
184
+ }
185
+
186
+ extension Never : TestScoping {
187
+ public func provideScope( for test: Test , testCase: Test . Case ? , performing function: @Sendable ( ) async throws -> Void ) async throws { }
44
188
}
45
189
46
190
/// A protocol describing traits that can be added to a test function.
@@ -72,43 +216,26 @@ extension Trait {
72
216
}
73
217
}
74
218
219
+ extension Trait where TestScopeProvider == Never {
220
+ /// Get this trait's scope provider for the specified test and/or test case,
221
+ /// if any.
222
+ ///
223
+ /// - Parameters:
224
+ /// - test: The test for which a scope provider is being requested.
225
+ /// - testCase: The test case for which a scope provider is being requested,
226
+ /// if any. When `test` represents a suite, the value of this argument is
227
+ /// `nil`.
228
+ ///
229
+ /// This default implementation is used when this trait type's associated
230
+ /// ``Trait/TestScopeProvider`` type is the default value of `Never`, and its
231
+ /// return value is discussed in ``Trait/scopeProvider(for:testCase:)-cjmg``.
232
+ public func scopeProvider( for test: Test , testCase: Test . Case ? ) -> Never ? {
233
+ nil
234
+ }
235
+ }
236
+
75
237
extension SuiteTrait {
76
238
public var isRecursive : Bool {
77
239
false
78
240
}
79
241
}
80
-
81
- /// A protocol extending ``Trait`` that offers an additional customization point
82
- /// for trait authors to execute code before and after each test function (if
83
- /// added to the traits of a test function), or before and after each test suite
84
- /// (if added to the traits of a test suite).
85
- @_spi ( Experimental)
86
- public protocol CustomExecutionTrait : Trait {
87
-
88
- /// Execute a function with the effects of this trait applied.
89
- ///
90
- /// - Parameters:
91
- /// - function: The function to perform. If `test` represents a test suite,
92
- /// this function encapsulates running all the tests in that suite. If
93
- /// `test` represents a test function, this function is the body of that
94
- /// test function (including all cases if it is parameterized.)
95
- /// - test: The test under which `function` is being performed.
96
- /// - testCase: The test case, if any, under which `function` is being
97
- /// performed. This is `nil` when invoked on a suite.
98
- ///
99
- /// - Throws: Whatever is thrown by `function`, or an error preventing the
100
- /// trait from running correctly.
101
- ///
102
- /// This function is called for each ``CustomExecutionTrait`` on a test suite
103
- /// or test function and allows additional work to be performed before and
104
- /// after the test runs.
105
- ///
106
- /// This function is invoked once for the test it is applied to, and then once
107
- /// for each test case in that test, if applicable.
108
- ///
109
- /// Issues recorded by this function are recorded against `test`.
110
- ///
111
- /// - Note: If a test function or test suite is skipped, this function does
112
- /// not get invoked by the runner.
113
- func execute( _ function: @Sendable ( ) async throws -> Void , for test: Test , testCase: Test . Case ? ) async throws
114
- }
0 commit comments