@@ -171,6 +171,60 @@ extension Runner {
171171    } 
172172  } 
173173
174+   /// Post `testStarted` and `testEnded` (or `testSkipped`) events for the test
175+   /// at the given plan step.
176+   ///
177+   /// - Parameters:
178+   ///   - step: The plan step for which events should be posted.
179+   ///   - configuration: The configuration to use for running.
180+   ///   - body: A function to execute between the started/ended events.
181+   ///
182+   /// - Throws: Whatever is thrown by `body` or while handling any issues
183+   ///   recorded in the process.
184+   ///
185+   /// - Returns: Whatever is returned by `body`.
186+   ///
187+   /// This function does _not_ post the `planStepStarted` and `planStepEnded`
188+   /// events.
189+   private  static  func  _postingTestStartedAndEndedEvents< R> ( for step:  Plan . Step ,  configuration:  Configuration ,  _ body:  @Sendable   ( )  async  throws  ->  R )  async  throws  ->  R  { 
190+     // Whether to send a `.testEnded` event at the end of running this step.
191+     // Some steps' actions may not require a final event to be sent — for
192+     // example, a skip event only sends `.testSkipped`.
193+     let  shouldSendTestEnded :  Bool 
194+ 
195+     // Determine what kind of event to send for this step based on its action.
196+     switch  step. action { 
197+     case  . run: 
198+       Event . post ( . testStarted,  for:  ( step. test,  nil ) ,  configuration:  configuration) 
199+       shouldSendTestEnded =  true 
200+     case  let  . skip( skipInfo) : 
201+       Event . post ( . testSkipped( skipInfo) ,  for:  ( step. test,  nil ) ,  configuration:  configuration) 
202+       shouldSendTestEnded =  false 
203+     case  let  . recordIssue( issue) : 
204+       // Scope posting the issue recorded event such that issue handling
205+       // traits have the opportunity to handle it. This ensures that if a test
206+       // has an issue handling trait _and_ some other trait which caused an
207+       // issue to be recorded, the issue handling trait can process the issue
208+       // even though it wasn't recorded by the test function.
209+       try await  Test . withCurrent ( step. test)  { 
210+         try await  _applyIssueHandlingTraits ( for:  step. test)  { 
211+           // Don't specify `configuration` when posting this issue so that
212+           // traits can provide scope and potentially customize the
213+           // configuration.
214+           Event . post ( . issueRecorded( issue) ,  for:  ( step. test,  nil ) ) 
215+         } 
216+       } 
217+       shouldSendTestEnded =  false 
218+     } 
219+     defer  { 
220+       if  shouldSendTestEnded { 
221+         Event . post ( . testEnded,  for:  ( step. test,  nil ) ,  configuration:  configuration) 
222+       } 
223+     } 
224+ 
225+     return  try await  body ( ) 
226+   } 
227+ 
174228  /// Run this test.
175229  ///
176230  /// - Parameters:
@@ -193,64 +247,34 @@ extension Runner {
193247    // Exit early if the task has already been cancelled.
194248    try Task . checkCancellation ( ) 
195249
196-     // Whether to send a `.testEnded` event at the end of running this step.
197-     // Some steps' actions may not require a final event to be sent — for
198-     // example, a skip event only sends `.testSkipped`.
199-     let  shouldSendTestEnded :  Bool 
200- 
201-     let  configuration  =  _configuration
202- 
203-     // Determine what action to take for this step.
204250    if  let  step =  stepGraph. value { 
251+       let  configuration  =  _configuration
205252      Event . post ( . planStepStarted( step) ,  for:  ( step. test,  nil ) ,  configuration:  configuration) 
206- 
207-       // Determine what kind of event to send for this step based on its action.
208-       switch  step. action { 
209-       case  . run: 
210-         Event . post ( . testStarted,  for:  ( step. test,  nil ) ,  configuration:  configuration) 
211-         shouldSendTestEnded =  true 
212-       case  let  . skip( skipInfo) : 
213-         Event . post ( . testSkipped( skipInfo) ,  for:  ( step. test,  nil ) ,  configuration:  configuration) 
214-         shouldSendTestEnded =  false 
215-       case  let  . recordIssue( issue) : 
216-         // Scope posting the issue recorded event such that issue handling
217-         // traits have the opportunity to handle it. This ensures that if a test
218-         // has an issue handling trait _and_ some other trait which caused an
219-         // issue to be recorded, the issue handling trait can process the issue
220-         // even though it wasn't recorded by the test function.
221-         try await  Test . withCurrent ( step. test)  { 
222-           try await  _applyIssueHandlingTraits ( for:  step. test)  { 
223-             // Don't specify `configuration` when posting this issue so that
224-             // traits can provide scope and potentially customize the
225-             // configuration.
226-             Event . post ( . issueRecorded( issue) ,  for:  ( step. test,  nil ) ) 
227-           } 
228-         } 
229-         shouldSendTestEnded =  false 
230-       } 
231-     }  else  { 
232-       shouldSendTestEnded =  false 
233-     } 
234-     defer  { 
235-       if  let  step =  stepGraph. value { 
236-         if  shouldSendTestEnded { 
237-           Event . post ( . testEnded,  for:  ( step. test,  nil ) ,  configuration:  configuration) 
238-         } 
253+       defer  { 
239254        Event . post ( . planStepEnded( step) ,  for:  ( step. test,  nil ) ,  configuration:  configuration) 
240255      } 
241-     } 
242256
243-     if  let  step =  stepGraph. value,  case . run =  step. action { 
244257      await  Test . withCurrent ( step. test)  { 
245258        _ =  await  Issue . withErrorRecording ( at:  step. test. sourceLocation,  configuration:  configuration)  { 
246-           try await  _applyScopingTraits ( for:  step. test,  testCase:  nil )  { 
247-             // Run the test function at this step (if one is present.)
248-             if  let  testCases =  step. test. testCases { 
249-               try await  _runTestCases ( testCases,  within:  step) 
259+           switch  step. action { 
260+           case  . run: 
261+             try await  _applyScopingTraits ( for:  step. test,  testCase:  nil )  { 
262+               try await  _postingTestStartedAndEndedEvents ( for:  step,  configuration:  configuration)  { 
263+                 // Run the test function at this step (if one is present.)
264+                 if  let  testCases =  step. test. testCases { 
265+                   try await  _runTestCases ( testCases,  within:  step) 
266+                 } 
267+ 
268+                 // Run the children of this test (i.e. the tests in this suite.)
269+                 try await  _runChildren ( of:  stepGraph) 
270+               } 
271+             } 
272+           default : 
273+             // Skipping this step or otherwise not running it. Post appropriate
274+             // started/ended events for the test and walk any child nodes.
275+             try await  _postingTestStartedAndEndedEvents ( for:  step,  configuration:  configuration)  { 
276+               try await  _runChildren ( of:  stepGraph) 
250277            } 
251- 
252-             // Run the children of this test (i.e. the tests in this suite.)
253-             try await  _runChildren ( of:  stepGraph) 
254278          } 
255279        } 
256280      } 
0 commit comments