@@ -304,55 +304,69 @@ private async ValueTask ExecuteTestLifecycleAsync(AbstractExecutableTest test, C
304304 }
305305 finally
306306 {
307+ await DisposeTestInstanceWithSpanAsync ( test ) . ConfigureAwait ( false ) ;
308+ }
309+ }
310+
311+ /// <summary>
312+ /// Disposes the test instance and fires OnDispose callbacks, wrapped in an OpenTelemetry
313+ /// activity span for trace timeline visibility.
314+ /// Parented under the class activity because the test case activity has already been stopped
315+ /// by this point (disposal runs after TestExecutor.ExecuteAsync completes).
316+ /// </summary>
317+ private async ValueTask DisposeTestInstanceWithSpanAsync ( AbstractExecutableTest test )
318+ {
307319#if NET
308- Activity ? disposalActivity = null ;
309- if ( TUnitActivitySource . Source . HasListeners ( ) )
310- {
311- // Parented under the class activity because the test case activity has already
312- // been stopped by this point (disposal runs after TestExecutor.ExecuteAsync completes).
313- // The initialization activity is a child of the test case span since it runs within it.
314- disposalActivity = TUnitActivitySource . StartActivity (
315- "test instance disposal" ,
316- ActivityKind . Internal ,
317- test . Context . ClassContext . Activity ? . Context ?? default ,
318- [ new ( "tunit.test.id" , test . Context . Id ) ] ) ;
319- }
320- try
321- {
320+ Activity ? disposalActivity = null ;
321+ if ( TUnitActivitySource . Source . HasListeners ( ) )
322+ {
323+ disposalActivity = TUnitActivitySource . StartActivity (
324+ "test instance disposal" ,
325+ ActivityKind . Internal ,
326+ test . Context . ClassContext . Activity ? . Context ?? default ,
327+ [ new ( "tunit.test.id" , test . Context . Id ) ] ) ;
328+ }
329+ try
330+ {
322331#endif
323- // Dispose test instance and fire OnDispose after each attempt
324- // This ensures each retry gets a fresh instance
325- var onDispose = test . Context . InternalEvents . OnDispose ;
326- if ( onDispose ? . InvocationList != null )
332+ // Dispose test instance and fire OnDispose after each attempt
333+ // This ensures each retry gets a fresh instance
334+ var onDispose = test . Context . InternalEvents . OnDispose ;
335+ if ( onDispose ? . InvocationList != null )
336+ {
337+ foreach ( var invocation in onDispose . InvocationList )
327338 {
328- foreach ( var invocation in onDispose . InvocationList )
339+ try
329340 {
330- try
331- {
332- await invocation . InvokeAsync ( test . Context , test . Context ) . ConfigureAwait ( false ) ;
333- }
334- catch ( Exception disposeEx )
335- {
336- await _logger . LogErrorAsync ( $ "Error during OnDispose for { test . TestId } : { disposeEx } " ) . ConfigureAwait ( false ) ;
337- }
341+ await invocation . InvokeAsync ( test . Context , test . Context ) . ConfigureAwait ( false ) ;
342+ }
343+ catch ( Exception disposeEx )
344+ {
345+ #if NET
346+ TUnitActivitySource . RecordException ( disposalActivity , disposeEx ) ;
347+ #endif
348+ await _logger . LogErrorAsync ( $ "Error during OnDispose for { test . TestId } : { disposeEx } " ) . ConfigureAwait ( false ) ;
338349 }
339350 }
351+ }
340352
341- try
342- {
343- await TestExecutor . DisposeTestInstance ( test ) . ConfigureAwait ( false ) ;
344- }
345- catch ( Exception disposeEx )
346- {
347- await _logger . LogErrorAsync ( $ "Error disposing test instance for { test . TestId } : { disposeEx } ") . ConfigureAwait ( false ) ;
348- }
353+ try
354+ {
355+ await TestExecutor . DisposeTestInstance ( test ) . ConfigureAwait ( false ) ;
356+ }
357+ catch ( Exception disposeEx )
358+ {
349359#if NET
350- }
351- finally
352- {
353- TUnitActivitySource . StopActivity ( disposalActivity ) ;
354- }
360+ TUnitActivitySource . RecordException ( disposalActivity , disposeEx ) ;
355361#endif
362+ await _logger . LogErrorAsync ( $ "Error disposing test instance for { test . TestId } : { disposeEx } ") . ConfigureAwait ( false ) ;
363+ }
364+ #if NET
356365 }
366+ finally
367+ {
368+ TUnitActivitySource . StopActivity ( disposalActivity ) ;
369+ }
370+ #endif
357371 }
358372}
0 commit comments