Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: thomhurst/TUnit
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.37.35
Choose a base ref
...
head repository: thomhurst/TUnit
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v1.37.36
Choose a head ref
  • 1 commit
  • 21 files changed
  • 1 contributor

Commits on Apr 22, 2026

  1. fix(telemetry): remove duplicate HTTP client spans (#5668)

    * fix(telemetry): remove duplicate HTTP client spans
    
    Strip span synthesis from both HTTP propagation handlers. .NET's
    System.Net.Http ActivitySource already emits a properly-shaped client
    span for real-socket traffic (Aspire), and the ASP.NET Core server span
    carries HTTP semconv tags for in-memory WAF traffic — synthesizing a
    second client span produced duplicate rows in trace timelines.
    
    Both handlers collapse to a single call to a shared
    HttpActivityPropagator that only injects traceparent + baggage so the
    SUT can correlate requests to the originating test.
    
    TUnitTestCorrelationProcessor now tags in OnEnd as well as OnStart:
    spans with a remote-context parent (ASP.NET Core server spans created
    from extracted traceparent) receive baggage via the propagator after
    StartActivity returns, so OnStart alone couldn't see it — the previous
    topology masked this by tagging the synthesized client span instead.
    
    Public-API impact: AspireHttpSourceName removed (shipped only in the
    unreleased #5666); AspNetCoreHttpSourceName marked [Obsolete] for
    binary compatibility.
    
    * fix(telemetry): address review feedback on TryTag + polling race
    
    - TUnitTestCorrelationProcessor: replace Activity.Current fallback with
      TraceRegistry.GetContextId keyed on the activity's own TraceId. The
      previous ambient-current fallback could cross-attribute a span to a
      different concurrent test if the span was stopped on a thread whose
      Activity.Current had swung to another test's context. Trace-id lookup
      is bound to the span itself and can't mis-attribute.
    - AutoConfigureOpenTelemetryTests: polling loop now uses a monotonic
      deadline instead of CancellationToken threaded into Task.Delay, so
      timeout surfaces as "no tagged span found" rather than a
      TaskCanceledException.
    - Added Processor_FallsBackToTraceRegistry_WhenActivityHasNoBaggage
      covering the new fallback path.
    
    * style: use Activity.Current via System.Diagnostics import
    
    Minor consistency nit surfaced in review — match the import style used
    by the Aspire handler.
    
    * docs: tighten HttpActivityPropagator visibility + OnEnd note
    
    - HttpActivityPropagator.Inject: public -> internal. Class is internal,
      so the method visibility was redundant; explicit internal matches
      other internal helpers in TUnit.
    - TUnitTestCorrelationProcessor: add a remarks paragraph explaining
      that OnEnd tag writes are visible to deferred-serialization exporters
      (BatchExportProcessor, InMemoryExporter). Synchronous exporters need
      this processor to be registered before them to observe the tag.
    
    * refactor(aspire): drop TUnitBaggagePropagationHandler, use runtime DiagnosticsHandler
    
    The Aspire handler injected traceparent before SocketsHttpHandler's internal
    DiagnosticsHandler got to do it, so outgoing requests carried the test body's
    span ID instead of the runtime-emitted client span ID. SUT server spans then
    parented to test body as siblings of the client span rather than as children,
    breaking the standard OTel client/server waterfall.
    
    The fix is to stop injecting. AspireFixture.CreateHttpClient now returns
    new HttpClient(new SocketsHttpHandler { SslOptions = ... }) and lets the
    runtime handle everything:
    
    - DiagnosticsHandler creates the client Activity
    - DistributedContextPropagator.Current.Inject emits traceparent+baggage
      against that client Activity's span ID (W3C)
    - baggage walks the parent chain so tunit.test.id flows to the SUT
    - SUT server span parents correctly under the client span
    
    Also subscribes the test-runner's TracerProvider to System.Net.Http so the
    runtime-emitted client span is actually exported — without this the span is
    created but not visible on dashboards, leaving server spans with orphan
    parents in cross-process traces.
    
    Users with TUNIT_KEEP_LEGACY_PROPAGATOR=1 no longer get the W3C baggage
    belt-and-braces emission that the handler did; the runtime uses whatever
    propagator is configured, which is the correct respect-the-opt-out behavior.
    
    Verified end-to-end against Jaeger: the Aspire trace-demo test now produces
    a clean 11-span waterfall with proper client/server pairing at every HTTP
    boundary.
    
    * style: trim CreateHttpClient/AutoStart comments; narrow _httpHandler type
    
    Drop the WHAT explanation of what the runtime's DiagnosticsHandler does
    in AspireFixture.CreateHttpClient — the behavior is already documented
    on the method's XML summary and is self-evident from the code.
    
    Drop the WHAT sentence at AutoStart's AddSource("System.Net.Http"),
    keep only the WHY (orphan-parent server spans without it).
    
    Narrow `_httpHandler` field from `HttpMessageHandler?` to
    `SocketsHttpHandler?` — accurate to what's actually stored, makes
    SslOptions/PooledConnectionLifetime accessible without casts if we
    need them later.
    
    * docs: call out SimpleExportProcessor ordering in correlation example
    thomhurst authored Apr 22, 2026
    Configuration menu
    Copy the full SHA
    ae24d73 View commit details
    Browse the repository at this point in the history
Loading