Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 32 additions & 17 deletions TUnit.Engine/Services/HookExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,24 +59,13 @@ public async ValueTask ExecuteBeforeTestSessionHooksAsync(CancellationToken canc
}
}

// Start the session activity AFTER hooks have run, because user hooks
// typically set up the TracerProvider / ActivityListener. If we started
// the activity before hooks, the ActivitySource would have no listeners
// and StartActivity would return null - producing no root span.
// Try to start the session activity now. When the user sets up their
// TracerProvider in Before(TestSession), this is the first opportunity
// where HasListeners() returns true. When they set it up earlier (e.g.
// in Before(TestDiscovery)), the activity was already started by
// TryStartSessionActivity() before discovery — this call is a no-op.
#if NET
var sessionContext = _contextProvider.TestSessionContext;

if (TUnitActivitySource.Source.HasListeners())
{
sessionContext.Activity = TUnitActivitySource.StartActivity(
TUnitActivitySource.SpanTestSession,
System.Diagnostics.ActivityKind.Internal,
default,
[
new("tunit.session.id", sessionContext.Id),
new("tunit.filter", sessionContext.TestFilter)
]);
}
TryStartSessionActivity();
#endif
}

Expand Down Expand Up @@ -122,6 +111,32 @@ public async ValueTask<List<Exception>> ExecuteAfterTestSessionHooksAsync(Cancel
}

#if NET
/// <summary>
/// Lazily starts the session activity once an ActivityListener is registered,
/// so discovery and execution spans can parent under it.
/// </summary>
internal void TryStartSessionActivity()
{
var sessionContext = _contextProvider.TestSessionContext;

if (sessionContext.Activity is not null)
{
return;
}

if (TUnitActivitySource.Source.HasListeners())
{
sessionContext.Activity = TUnitActivitySource.StartActivity(
TUnitActivitySource.SpanTestSession,
System.Diagnostics.ActivityKind.Internal,
default,
[
new("tunit.session.id", sessionContext.Id),
new("tunit.filter", sessionContext.TestFilter)
]);
}
}

private void FinishSessionActivity(bool hasErrors)
{
var sessionContext = _contextProvider.TestSessionContext;
Expand Down
8 changes: 8 additions & 0 deletions TUnit.Engine/TestDiscoveryService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ public async Task<TestDiscoveryResult> DiscoverTests(string testSessionId, ITest
{
await _testExecutor.ExecuteBeforeTestDiscoveryHooksAsync(cancellationToken).ConfigureAwait(false);

#if NET
// Start session activity early so discovery spans can parent under it.
if (isForExecution)
{
_testExecutor.TryStartSessionActivity();
}
#endif

var contextProvider = _testExecutor.GetContextProvider();
contextProvider.BeforeTestDiscoveryContext.RestoreExecutionContext();

Expand Down
5 changes: 5 additions & 0 deletions TUnit.Engine/TestExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,11 @@ public ValueTask ExecuteAfterTestDiscoveryHooksAsync(CancellationToken cancellat
return _hookExecutor.ExecuteAfterTestDiscoveryHooksAsync(cancellationToken);
}

#if NET
/// <inheritdoc cref="HookExecutor.TryStartSessionActivity"/>
internal void TryStartSessionActivity() => _hookExecutor.TryStartSessionActivity();
#endif

/// <summary>
/// Get the context provider for accessing test contexts.
/// </summary>
Expand Down
Loading
Loading