@@ -201,47 +201,75 @@ extension Runner.Plan {
201201 /// - Parameters:
202202 /// - test: The test whose action will be determined.
203203 ///
204- /// - Returns: A tuple containing the action to take for `test` as well as any
205- /// error that was thrown during trait evaluation. If more than one error
206- /// was thrown, the first-caught error is returned.
207- private static func _determineAction ( for test : Test ) async -> ( Action , ( any Error ) ? ) {
204+ /// - Returns:The action to take for `test`.
205+ private static func _determineAction ( for test : inout Test ) async -> Action {
206+ let result : Action
207+
208208 // We use a task group here with a single child task so that, if the trait
209209 // code calls Test.cancel() we don't end up cancelling the entire test run.
210210 // We could also model this as an unstructured task except that they aren't
211211 // available in the "task-to-thread" concurrency model.
212212 //
213213 // FIXME: Parallelize this work. Calling `prepare(...)` on all traits and
214214 // evaluating all test arguments should be safely parallelizable.
215- await withTaskGroup ( returning: ( Action , ( any Error ) ? ) . self) { taskGroup in
215+ ( test , result ) = await withTaskGroup ( returning: ( Test , Action ) . self) { [ test ] taskGroup in
216216 taskGroup. addTask {
217+ var test = test
217218 var action = _runAction
218- var firstCaughtError : ( any Error ) ?
219219
220220 await Test . withCurrent ( test) {
221- for trait in test. traits {
221+ do {
222+ var firstCaughtError : ( any Error ) ?
223+
224+ for trait in test. traits {
225+ do {
226+ try await trait. prepare ( for: test)
227+ } catch {
228+ if let skipInfo = SkipInfo ( error) {
229+ action = . skip( skipInfo)
230+ break
231+ } else {
232+ // Only preserve the first caught error
233+ firstCaughtError = firstCaughtError ?? error
234+ }
235+ }
236+ }
237+
238+ // If no trait specified that the test should be skipped, but one
239+ // did throw an error, then the action is to record an issue for
240+ // that error.
241+ if case . run = action, let error = firstCaughtError {
242+ action = . recordIssue( Issue ( for: error) )
243+ }
244+ }
245+
246+ // If the test is still planned to run (i.e. nothing thus far has
247+ // caused it to be skipped), evaluate its test cases now.
248+ //
249+ // The argument expressions of each test are captured in closures so
250+ // they can be evaluated lazily only once it is determined that the
251+ // test will run, to avoid unnecessary work. But now is the
252+ // appropriate time to evaluate them.
253+ if case . run = action {
222254 do {
223- try await trait. prepare ( for: test)
224- } catch let error as SkipInfo {
225- action = . skip( error)
226- break
227- } catch is CancellationError where Task . isCancelled {
228- // Synthesize skip info for this cancellation error.
229- let sourceContext = SourceContext ( backtrace: . current( ) , sourceLocation: nil )
230- let skipInfo = SkipInfo ( comment: nil , sourceContext: sourceContext)
231- action = . skip( skipInfo)
232- break
255+ try await test. evaluateTestCases ( )
233256 } catch {
234- // Only preserve the first caught error
235- firstCaughtError = firstCaughtError ?? error
257+ if let skipInfo = SkipInfo ( error) {
258+ action = . skip( skipInfo)
259+ } else {
260+ action = . recordIssue( Issue ( for: error) )
261+ }
236262 }
237263 }
238264 }
239265
240- return ( action , firstCaughtError )
266+ return ( test , action )
241267 }
242268
243269 return await taskGroup. first { _ in true } !
244270 }
271+
272+ return result
245273 }
246274
247275 /// Construct a graph of runner plan steps for the specified tests.
@@ -309,36 +337,12 @@ extension Runner.Plan {
309337 return nil
310338 }
311339
312- var action = runAction
313- var firstCaughtError : ( any Error ) ?
314-
315340 // Walk all the traits and tell each to prepare to run the test.
316341 // If any throw a `SkipInfo` error at this stage, stop walking further.
317342 // But if any throw another kind of error, keep track of the first error
318343 // but continue walking, because if any subsequent traits throw a
319344 // `SkipInfo`, the error should not be recorded.
320- ( action, firstCaughtError) = await _determineAction ( for: test)
321-
322- // If no trait specified that the test should be skipped, but one did
323- // throw an error, then the action is to record an issue for that error.
324- if case . run = action, let error = firstCaughtError {
325- action = . recordIssue( Issue ( for: error) )
326- }
327-
328- // If the test is still planned to run (i.e. nothing thus far has caused
329- // it to be skipped), evaluate its test cases now.
330- //
331- // The argument expressions of each test are captured in closures so they
332- // can be evaluated lazily only once it is determined that the test will
333- // run, to avoid unnecessary work. But now is the appropriate time to
334- // evaluate them.
335- if case . run = action {
336- do {
337- try await test. evaluateTestCases ( )
338- } catch {
339- action = . recordIssue( Issue ( for: error) )
340- }
341- }
345+ var action = await _determineAction ( for: & test)
342346
343347 // If the test is parameterized but has no cases, mark it as skipped.
344348 if case . run = action, let testCases = test. testCases, testCases. first ( where: { _ in true } ) == nil {
0 commit comments