Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 4 additions & 0 deletions TUnit.AspNetCore.Core/TestWebApplicationFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,16 @@ protected virtual void ConfigureStartupConfiguration(IConfigurationBuilder confi
/// Registers <see cref="CorrelatedTUnitLoggingExtensions.AddCorrelatedTUnitLogging"/> here
/// (rather than in <see cref="CreateHostBuilder"/>) so that minimal API hosts — where
/// <see cref="CreateHostBuilder"/> returns <c>null</c> — also get correlated logging.
/// Also re-aligns <see cref="System.Diagnostics.DistributedContextPropagator.Current"/>
/// to the W3C propagator for the SUT, in case user startup code reset it to the default.
/// Subclasses overriding this method must call <c>base.ConfigureWebHost(builder)</c>.
/// </summary>
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);

PropagatorAlignment.AlignIfDefault();

builder.ConfigureServices(services =>
{
services.AddCorrelatedTUnitLogging();
Expand Down
51 changes: 51 additions & 0 deletions TUnit.Core/PropagatorAlignment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#if NET

using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace TUnit.Core;

/// <summary>
/// Auto-aligns <see cref="DistributedContextPropagator.Current"/> to the W3C composite
/// propagator (traceparent + baggage) when the current propagator is .NET's default
/// <c>LegacyPropagator</c>. Without this, cross-process test correlation baggage
/// (<c>tunit.test.id</c>) is emitted as <c>Correlation-Context</c>, which the OpenTelemetry
/// SDK's <c>BaggagePropagator</c> does not read — the baggage silently drops between
/// the test process and the SUT.
/// </summary>
/// <remarks>
/// Set the environment variable <c>TUNIT_KEEP_LEGACY_PROPAGATOR=1</c> to opt out and
/// retain the default LegacyPropagator. Any user-configured propagator that isn't the
/// runtime default is left untouched.
/// </remarks>
internal static class PropagatorAlignment
{
private const string LegacyPropagatorTypeName = "System.Diagnostics.LegacyPropagator";

// Read once: env vars don't change within a process and GetEnvironmentVariable allocates.
private static readonly bool OptedOut =
Environment.GetEnvironmentVariable("TUNIT_KEEP_LEGACY_PROPAGATOR") == "1";

#pragma warning disable CA2255 // Module initializer is the intended entry point per issue #5592.
[ModuleInitializer]
#pragma warning restore CA2255
internal static void AlignOnModuleLoad() => AlignIfDefault();

/// <summary>
/// Idempotent: re-align only if the current propagator is still the runtime default.
/// </summary>
internal static void AlignIfDefault()
{
if (OptedOut)
{
return;
}

if (DistributedContextPropagator.Current.GetType().FullName == LegacyPropagatorTypeName)
{
DistributedContextPropagator.Current = DistributedContextPropagator.CreateW3CPropagator();

Check failure on line 46 in TUnit.Core/PropagatorAlignment.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (de-DE)

"DistributedContextPropagator" enthält keine Definition für "CreateW3CPropagator".

Check failure on line 46 in TUnit.Core/PropagatorAlignment.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (de-DE)

"DistributedContextPropagator" enthält keine Definition für "CreateW3CPropagator".

Check failure on line 46 in TUnit.Core/PropagatorAlignment.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (de-DE)

"DistributedContextPropagator" enthält keine Definition für "CreateW3CPropagator".

Check failure on line 46 in TUnit.Core/PropagatorAlignment.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (de-DE)

"DistributedContextPropagator" enthält keine Definition für "CreateW3CPropagator".

Check failure on line 46 in TUnit.Core/PropagatorAlignment.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (pl-PL)

'Element „DistributedContextPropagator” nie zawiera definicji „CreateW3CPropagator”.

Check failure on line 46 in TUnit.Core/PropagatorAlignment.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (pl-PL)

'Element „DistributedContextPropagator” nie zawiera definicji „CreateW3CPropagator”.

Check failure on line 46 in TUnit.Core/PropagatorAlignment.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (pl-PL)

'Element „DistributedContextPropagator” nie zawiera definicji „CreateW3CPropagator”.

Check failure on line 46 in TUnit.Core/PropagatorAlignment.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (pl-PL)

'Element „DistributedContextPropagator” nie zawiera definicji „CreateW3CPropagator”.

Check failure on line 46 in TUnit.Core/PropagatorAlignment.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (fr-FR)

'DistributedContextPropagator' ne contient pas de définition pour 'CreateW3CPropagator'

Check failure on line 46 in TUnit.Core/PropagatorAlignment.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (fr-FR)

'DistributedContextPropagator' ne contient pas de définition pour 'CreateW3CPropagator'

Check failure on line 46 in TUnit.Core/PropagatorAlignment.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (fr-FR)

'DistributedContextPropagator' ne contient pas de définition pour 'CreateW3CPropagator'

Check failure on line 46 in TUnit.Core/PropagatorAlignment.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (fr-FR)

'DistributedContextPropagator' ne contient pas de définition pour 'CreateW3CPropagator'

Check failure on line 46 in TUnit.Core/PropagatorAlignment.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (macos-latest)

'DistributedContextPropagator' does not contain a definition for 'CreateW3CPropagator'

Check failure on line 46 in TUnit.Core/PropagatorAlignment.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (macos-latest)

'DistributedContextPropagator' does not contain a definition for 'CreateW3CPropagator'

Check failure on line 46 in TUnit.Core/PropagatorAlignment.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (macos-latest)

'DistributedContextPropagator' does not contain a definition for 'CreateW3CPropagator'

Check failure on line 46 in TUnit.Core/PropagatorAlignment.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (macos-latest)

'DistributedContextPropagator' does not contain a definition for 'CreateW3CPropagator'

Check failure on line 46 in TUnit.Core/PropagatorAlignment.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (ubuntu-latest)

'DistributedContextPropagator' does not contain a definition for 'CreateW3CPropagator'

Check failure on line 46 in TUnit.Core/PropagatorAlignment.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (ubuntu-latest)

'DistributedContextPropagator' does not contain a definition for 'CreateW3CPropagator'

Check failure on line 46 in TUnit.Core/PropagatorAlignment.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (windows-latest)

'DistributedContextPropagator' does not contain a definition for 'CreateW3CPropagator'

Check failure on line 46 in TUnit.Core/PropagatorAlignment.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (windows-latest)

'DistributedContextPropagator' does not contain a definition for 'CreateW3CPropagator'
}
}
}

#endif
56 changes: 56 additions & 0 deletions TUnit.UnitTests/PropagatorAlignmentTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#if NET
using System.Diagnostics;
using TUnit.Core;

namespace TUnit.UnitTests;

// Tests mutate DistributedContextPropagator.Current (process-global) — must not run concurrently.
[NotInParallel(nameof(PropagatorAlignmentTests))]
public class PropagatorAlignmentTests
{
[Test]
public async Task ModuleInitializer_Replaces_Default_Legacy_Propagator()
{
// Module init runs on first touch of any TUnit.Core type, so by now the default
// LegacyPropagator must already be gone; otherwise cross-process baggage breaks.
var current = DistributedContextPropagator.Current.GetType().FullName;
await Assert.That(current).IsNotEqualTo("System.Diagnostics.LegacyPropagator");
}

[Test]
public async Task AlignIfDefault_Leaves_Custom_Propagator_Untouched()
{
var original = DistributedContextPropagator.Current;
var custom = DistributedContextPropagator.CreatePassThroughPropagator();

try
{
DistributedContextPropagator.Current = custom;
PropagatorAlignment.AlignIfDefault();
await Assert.That(DistributedContextPropagator.Current).IsSameReferenceAs(custom);
}
finally
{
DistributedContextPropagator.Current = original;
}
}

[Test]
public async Task AlignIfDefault_Does_Not_Replace_Existing_W3C_Propagator()
{
var original = DistributedContextPropagator.Current;
var w3c = DistributedContextPropagator.CreateW3CPropagator();

try
{
DistributedContextPropagator.Current = w3c;
PropagatorAlignment.AlignIfDefault();
await Assert.That(DistributedContextPropagator.Current).IsSameReferenceAs(w3c);
}
finally
{
DistributedContextPropagator.Current = original;
}
}
}
#endif
4 changes: 3 additions & 1 deletion docs/docs/examples/opentelemetry.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,9 @@ Two common causes.

**1. The parent span isn't exported to the same backend.** The test-side `test case` span lives in the test process. If you only export from the SUT, the backend sees a child whose parent it has never seen. Either export the `"TUnit"` source from the test process too, or rely on the `tunit.test.id` tag (above) instead of trace hierarchy.

**2. The two processes use different baggage formats.** .NET defaults to `Correlation-Context`. The OpenTelemetry SDK reads W3C `baggage`. The two don't speak to each other. Use the same propagator on both sides:
**2. The two processes use different baggage formats.** .NET defaults to `Correlation-Context`. The OpenTelemetry SDK reads W3C `baggage`. Since TUnit 0.x (issue #5592), the test process auto-aligns `DistributedContextPropagator.Current` to W3C on module load, and `TestWebApplicationFactory<T>` re-applies this for in-process SUTs — no manual wiring needed. Set `TUNIT_KEEP_LEGACY_PROPAGATOR=1` to opt out.

For an **out-of-process** SUT that doesn't reference `TUnit.Core`, you still need to align it yourself:

```csharp
using OpenTelemetry;
Expand Down
8 changes: 5 additions & 3 deletions docs/docs/guides/distributed-tracing.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,11 @@ For cross-process correlation (your test calling your SUT), use `tunit.test.id`.

## When tracing across processes

If your test process and your SUT are different processes (or you're using `WebApplicationFactory` heavily), make sure both sides agree on the propagator:
Cross-process baggage propagation (e.g. `tunit.test.id` reaching your SUT) depends on both sides using the W3C `baggage` header rather than .NET's default `Correlation-Context`.

TUnit handles this automatically: a module initializer in `TUnit.Core` replaces the default `DistributedContextPropagator.LegacyPropagator` with `DistributedContextPropagator.CreateW3CPropagator()`. Any custom propagator you set yourself is left alone. If you want to retain the legacy behavior, set `TUNIT_KEEP_LEGACY_PROPAGATOR=1`.

For the SUT side, if it shares the test process (e.g. `TestWebApplicationFactory<T>`), alignment flows automatically. For out-of-process SUTs that don't reference `TUnit.Core`, align the propagator yourself on startup — either match `DistributedContextPropagator.Current` or, if you use the OpenTelemetry SDK:

```csharp
using OpenTelemetry;
Expand All @@ -112,8 +116,6 @@ Sdk.SetDefaultTextMapPropagator(new CompositeTextMapPropagator(
]));
```

Without this, .NET's default propagator emits `Correlation-Context`, but the OpenTelemetry SDK only reads W3C `baggage`. The mismatch silently drops baggage and you lose `tunit.test.id` on the SUT side.

## Limitations

### Static `ActivitySource` in third-party libraries
Expand Down
Loading