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.35.2
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.36.0
Choose a head ref
  • 17 commits
  • 140 files changed
  • 2 contributors

Commits on Apr 16, 2026

  1. fix: don't render test's own trace as Linked Trace in HTML report (#5580

    )
    
    The engine auto-registers the test's own traceId in TraceRegistry for
    OTLP cross-process log correlation. HtmlReporter then read it back as
    an "additional" trace, causing the primary trace to render twice —
    once filtered under "Trace Timeline", once raw under "Linked Trace:
    TUnit". Filter out the primary traceId so only user-registered
    external traces (via TestContext.RegisterTrace) appear as Linked
    Traces, matching documented behavior.
    thomhurst authored Apr 16, 2026
    Configuration menu
    Copy the full SHA
    8c823c5 View commit details
    Browse the repository at this point in the history
  2. chore(deps): update tunit to 1.35.2 (#5581)

    Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
    thomhurst and renovate-bot authored Apr 16, 2026
    Configuration menu
    Copy the full SHA
    7a72f9d View commit details
    Browse the repository at this point in the history

Commits on Apr 17, 2026

  1. chore(deps): update dependency typescript to ~6.0.3 (#5582)

    Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
    thomhurst and renovate-bot authored Apr 17, 2026
    Configuration menu
    Copy the full SHA
    4f1033c View commit details
    Browse the repository at this point in the history
  2. Configuration menu
    Copy the full SHA
    b22d2b6 View commit details
    Browse the repository at this point in the history
  3. Configuration menu
    Copy the full SHA
    e9ed809 View commit details
    Browse the repository at this point in the history
  4. fix(docs): benchmark index links 404 (#5587)

    Bare relative links like `[AsyncTests](AsyncTests)` resolved to
    `/docs/AsyncTests` instead of `/docs/benchmarks/AsyncTests`. Use
    `./Name.md` form so Docusaurus resolves to the sibling route.
    
    Fixes #5585
    thomhurst authored Apr 17, 2026
    Configuration menu
    Copy the full SHA
    90bdbd0 View commit details
    Browse the repository at this point in the history
  5. Configuration menu
    Copy the full SHA
    3488eff View commit details
    Browse the repository at this point in the history
  6. docs: expand and clarify distributed tracing setup and troubleshooting (

    #5597)
    
    Adds symptom-driven troubleshooting to the OpenTelemetry guide and a new
    Distributed Tracing guide consolidating per-backend setup, correlation
    strategies, and known limitations. Moves the "use TestWebApplicationFactory"
    guidance into a top-of-page warning callout so users don't silently lose
    trace correlation by inheriting from the vanilla WebApplicationFactory.
    
    Cross-references issues #5589, #5590, #5591, #5592, #5593, #5594, #5595,
    and #5596 which track the planned automation that will let later doc
    revisions trim each manual recipe.
    thomhurst authored Apr 17, 2026
    Configuration menu
    Copy the full SHA
    5d3df9b View commit details
    Browse the repository at this point in the history
  7. fix: auto-suppress ExecutionContext flow for hosted services (#5589) (#…

    …5598)
    
    * fix: auto-suppress ExecutionContext flow for hosted services (#5589)
    
    `TestWebApplicationFactory<T>` now wraps every registered `IHostedService`
    so its `StartAsync` runs under `ExecutionContext.SuppressFlow`. Background
    tasks spawned inside `StartAsync` capture a clean execution context,
    preventing spans from hosted-service work in test B from being attributed
    to test A's `TraceId`.
    
    The wrapper also implements `IHostedLifecycleService` so the Host's
    `StartingAsync`/`StartedAsync`/`StoppingAsync`/`StoppedAsync` hooks keep
    firing for inner services that implement it (the Host uses an `is` check
    against the registered instance).
    
    Override `SuppressHostedServiceExecutionContextFlow` and return `false` to
    preserve ambient context flow.
    
    * fix: wrap StartAsync in Task.Run under SuppressFlow
    
    `using var _ = SuppressFlow(); return inner.StartAsync(ct);` only suppresses
    context capture during the synchronous portion of StartAsync — `Task.Run`
    after a prior `await` re-captures the test's Activity.Current.
    
    Combine SuppressFlow with `Task.Run(() => inner.StartAsync(ct), ct)` so the
    inner hosted service runs on a thread-pool worker whose ExecutionContext was
    captured under suppression. Activity.Current starts null and stays null
    through every await point, so background tasks spawned anywhere inside
    StartAsync inherit a clean context.
    
    Also drops the Limitation xmldoc since it no longer applies, and adds a
    deep-async test (StartAsync_SuppressesFlow_WhenSpawnIsAfterAwait) proving
    the fix holds past the first await.
    thomhurst authored Apr 17, 2026
    Configuration menu
    Copy the full SHA
    76c4d4e View commit details
    Browse the repository at this point in the history
  8. feat: auto-align DistributedContextPropagator to W3C (#5599)

    * feat: auto-align DistributedContextPropagator to W3C (#5592)
    
    .NET's default LegacyPropagator emits Correlation-Context; the OpenTelemetry
    SDK's BaggagePropagator only reads W3C baggage. The mismatch silently drops
    tunit.test.id across processes so test correlation breaks on the SUT side.
    
    Add a module initializer in TUnit.Core that swaps the runtime-default
    LegacyPropagator for CreateW3CPropagator(), leaving user-customised
    propagators untouched. TestWebApplicationFactory.ConfigureWebHost re-applies
    the same alignment so SUT startup code cannot accidentally revert it. Opt
    out via TUNIT_KEEP_LEGACY_PROPAGATOR=1.
    
    Docs updated to reflect automatic alignment; manual OTel SetDefaultTextMapPropagator
    snippet retained only for out-of-process SUTs that don't reference TUnit.Core.
    
    * fix: provide W3C propagator on net8/net9
    
    DistributedContextPropagator.CreateW3CPropagator() was added in .NET 10;
    on net8/net9 supply a minimal in-library W3CBaggagePropagator that delegates
    traceparent/tracestate to the runtime default and emits/parses W3C baggage.
    
    * refactor: dedupe baggage utilities, tighten W3CBaggagePropagator
    
    - Promote BaggageHeader constant onto TUnitActivitySource; consume from
      W3CBaggagePropagator and the ASP.NET Core / Aspire propagation handlers.
    - Reuse TUnitActivitySource.TryBuildBaggageHeader in W3CBaggagePropagator
      instead of duplicating the URI-escape/comma-join logic.
    - Rename PropagatorAlignment.CreateW3CPropagator -> CreateAlignedPropagator
      to avoid shadowing the BCL static.
    - Parse baggage via span-walker (no Split allocation), lazy-init the result
      list so empty headers return null.
    
    * refactor: register PropagatorAlignment via IStartupFilter
    
    ConfigureWebHost callbacks register builder actions that run before user
    Program.cs/Startup code; calling AlignIfDefault() there lets SUT startup
    clobber the propagator again. IStartupFilter runs when the request
    pipeline is built, after all service registration and startup
    assignments, so alignment wins.
    
    Also drop the "TUnit 0.x (issue #5592)" placeholder from the
    OpenTelemetry docs — the auto-alignment is just how it works now.
    thomhurst authored Apr 17, 2026
    Configuration menu
    Copy the full SHA
    e7fde9b View commit details
    Browse the repository at this point in the history
  9. chore(deps): update dependency coverlet.collector to v10 (#5600)

    Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
    thomhurst and renovate-bot authored Apr 17, 2026
    Configuration menu
    Copy the full SHA
    e6d42c3 View commit details
    Browse the repository at this point in the history
  10. feat: TUnit0064 analyzer + code fix for direct WebApplicationFactory …

    …inheritance (#5601)
    
    * feat: add TUnit0064 analyzer + code fix for direct WebApplicationFactory inheritance
    
    Flags classes inheriting directly from `Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory<T>`
    and offers a code fix to rewrite the base to `TUnit.AspNetCore.TestWebApplicationFactory<T>`,
    preserving distributed tracing, per-test logging correlation, and `TestContext.Current`
    resolution inside request handlers.
    
    Closes #5596
    
    * fix: detect namespace-scoped TUnit.AspNetCore usings when applying code fix
    
    Also guards against ignoring pre-existing top-level usings and adds tests
    for both cases (top-level + namespace-scoped) so BatchFixer can't emit
    duplicate directives.
    
    * fix: skip analysis when TestWebApplicationFactory<T> is unavailable
    
    If `TUnit.AspNetCore` isn't referenced, warning a user to migrate to a type
    they can't resolve is unhelpful and would make the code fix produce a
    compile error. Bail out of the analyzer early in that case.
    
    Also adds tests for file-scoped-namespace using deduplication and partial
    class declarations.
    thomhurst authored Apr 17, 2026
    Configuration menu
    Copy the full SHA
    f394eef View commit details
    Browse the repository at this point in the history
  11. feat: auto-propagate test trace context through IHttpClientFactory (#…

    …5603)
    
    * feat: auto-propagate test trace context through IHttpClientFactory
    
    Registers an IHttpMessageHandlerBuilderFilter in TestWebApplicationFactory so that
    every IHttpClientFactory pipeline built inside the SUT (AddHttpClient<T>(), named
    clients, typed clients) automatically carries the test's traceparent, baggage, and
    X-TUnit-TestId headers on outbound calls. Opt out per-test via
    WebApplicationTestOptions.AutoPropagateHttpClientFactory = false.
    
    Closes #5590.
    
    * address review: document statelessness + tighten opt-out assertion
    
    - Document that both handler types inserted by TUnitHttpClientFilter must
      remain stateless/thread-safe because IHttpClientFactory caches pipelines
      and shares handler instances across concurrent parallel-test requests.
    - Explain the outermost-insert intent so a future refactor doesn't reverse
      the order.
    - Opt-out test now also asserts the downstream hop does not carry baggage,
      mirroring the positive test.
    thomhurst authored Apr 17, 2026
    Configuration menu
    Copy the full SHA
    b8f9bb5 View commit details
    Browse the repository at this point in the history
  12. feat: TUnit.OpenTelemetry zero-config tracing package (#5602)

    * chore: scaffold TUnit.OpenTelemetry project
    
    * chore: scaffold TUnit.OpenTelemetry.Tests project
    
    * feat(otel): add TUnitOpenTelemetry.Configure hook
    
    * feat(otel): auto-wire TracerProvider at TestDiscovery with coexistence
    
    * test(otel): cover TUNIT_OTEL_AUTOSTART=0 opt-out
    
    * test(otel): cover TUnitTestCorrelationProcessor baggage-to-tag behavior
    
    * feat(otel): skip auto-wire when no endpoint and no user Configure
    
    * feat(otel): set default service.name resource attribute
    
    * test(otel): add public API snapshot for TUnit.OpenTelemetry
    
    * refactor(aspire): delegate TracerProvider to TUnit.OpenTelemetry
    
    * test(otel): end-to-end span export smoke test
    
    * docs(otel): document TUnit.OpenTelemetry zero-config setup
    
    * chore(otel): wire TUnit.OpenTelemetry into pipeline (pack + test)
    
    * refactor(otel): reuse TagTestId constant, extract env-var names, release lock during Build
    
    - TUnitTestCorrelationProcessor now references TUnit.Core.TUnitActivitySource.TagTestId
      instead of duplicating the "tunit.test.id" literal.
    - AutoStart extracts AutoStartEnvVar / OtlpEndpointEnvVar / ServiceNameEnvVar as
      internal consts; tests reference them by name rather than raw strings.
    - Environment.GetEnvironmentVariable("OTEL_EXPORTER_OTLP_ENDPOINT") is read once
      and reused by both the gate check and AddOtlpExporter branch.
    - TracerProvider.Build() and user Configure callbacks now execute outside the
      AutoStart lock; a CAS-like re-check inside the lock disposes the loser if two
      Start calls race.
    
    * refactor(otel): address PR review — tighten TOCTOU, drop test-only wrapper
    
    - AutoStart.Start moves the HasListeners/endpoint/HasConfiguration checks
      inside the existing _lock section so the listener probe and the provider
      assignment are no longer separated by a gap (PR #5602 issue 2). Build()
      still runs outside the lock; the post-Build re-check disposes the loser
      on a race.
    - provider.Dispose() is called unconditionally after Build — the local is
      known non-null, so the ?. was misleading (issue 3).
    - TestTraceExporter.CreateTracerProvider is removed — it only existed for
      one Aspire test that duplicated AddToBuilder_ExportsTracesForRegisteredSource
      anyway. Production code uses AddToBuilder (issue 4).
    
    Issue 1 (AutoStart hard-coding "TUnit.AspNetCore.Http") is acknowledged
    but not acted on: the plan explicitly requires TUnit.AspNetCore to stay
    OTel-agnostic, so the suggested self-registration pattern would create the
    very dep we want to avoid. Users who disagree can already use
    TUnitOpenTelemetry.Configure(b => b.AddSource(...)) to add/override sources.
    thomhurst authored Apr 17, 2026
    Configuration menu
    Copy the full SHA
    b700a16 View commit details
    Browse the repository at this point in the history
  13. fix: restore [Obsolete] members removed in v1.27 (#5539) (#5605)

    * fix: restore [Obsolete] members removed in v1.27 (#5539)
    
    PR #5384 deleted previously [Obsolete]-marked public APIs in a minor
    release, breaking semver. Restore them with [Obsolete] reapplied so v1.x
    consumers can upgrade without compile errors. Actual deletion is
    deferred to the v2 major bump (tracked in #5604).
    
    Restored:
    - TUnit.Assertions: CountWrapper, LengthWrapper, HasCount/HasLength
      overloads on CollectionAssertionBase / AssertionExtensions
    - TUnit.Core: ObjectBag on TestBuilderContext + TestRegisteredContext,
      Timing record, ITestOutput.Timings + RecordTiming bridged to internal
      TimingEntry storage with new _timingsLock for user-facing concurrent
      RecordTiming calls
    - PublicAPI snapshots regenerated for net8/9/10/472
    
    * refactor: address PR review feedback
    
    - Extract GetCount/MapToCount helpers in CountWrapper to remove 6x duplication
    - Use <inheritdoc cref="StateBag"/> on ObjectBag aliases (drop duplicated XML)
    - Clarify _timingsLock comment: engine writes are sequential, lock guards user-facing obsolete RecordTiming
    thomhurst authored Apr 17, 2026
    Configuration menu
    Copy the full SHA
    02d2c96 View commit details
    Browse the repository at this point in the history
  14. feat: generalize OTLP receiver for use outside TUnit.Aspire (#5606)

    * feat: generalize OTLP receiver for use outside TUnit.Aspire
    
    Extracts OtlpReceiver + parsers from TUnit.Aspire to TUnit.OpenTelemetry
    and auto-starts them at test discovery. Out-of-process SUTs (spawned
    processes, testcontainers, external services) can now export spans into
    TUnit's HTML report via OTLP/HTTP without Aspire.
    
    ActivityCollector gains RegisterExternalTrace/IngestExternalSpan and a
    process-wide Current pointer so the receiver can route spans without
    explicit wiring. Unknown trace IDs are dropped; registered traces are
    capped at 100 external spans. Opt out with TUNIT_OTEL_RECEIVER=0.
    
    Adds OtlpTraceParser (field-by-field protobuf parser, no external dep)
    so incoming /v1/traces are ingested, not just forwarded.
    
    Closes #5595
    
    * docs: address PR review on OTLP receiver generalization
    
    - Add OTLP proto spec links and field-name comments to OtlpTraceParser and OtlpLogParser so magic field numbers can be cross-referenced.
    - Make external span cap overridable via TUNIT_OTEL_MAX_EXTERNAL_SPANS env var and emit a one-time stderr warning when the cap is first hit.
    
    * refactor: tidy external span cap helpers
    
    - Move TUNIT_OTEL_MAX_EXTERNAL_SPANS into EnvironmentConstants so the env var name isn't duplicated across resolver and warning message.
    - Collapse the MaxExternalSpansPerTest/MaxExternalSpansPerTrace alias pair into a single MaxExternalSpans field — per-test vs per-trace distinction is already clear at the use sites.
    - Use Interlocked.CompareExchange for the one-shot warning latch so we don't re-write the flag on every dropped span once the cap is hit.
    
    * fix: correlate external spans regardless of hex case
    
    OTLP parser emitted uppercase hex IDs (via Convert.ToHexString) while
    System.Diagnostics.Activity serializes lowercase. That meant external spans
    landed in a separate trace bucket from in-process spans on the same logical
    trace, the per-test cap never matched a test case span ID, and users had to
    know to call .ToUpperInvariant() when registering traces manually.
    
    - Parser now emits lowercase via a HexLower helper (uses ToHexStringLower on
      net9+, falls back to ToLowerInvariant on net8) so IDs round-trip with
      Activity's format without caller ceremony.
    - All trace/span ID dictionaries in ActivityCollector are now OrdinalIgnoreCase
      as defense-in-depth against any remaining mixed-case caller.
    - SpanType no longer duplicates Name for external spans — it's a TUnit-only
      classifier and has no OTLP analogue.
    - Regression test IngestExternalSpan_TraceIdCaseMismatch_StillCorrelates.
    - Docs and existing tests updated to drop the now-unnecessary ToUpperInvariant.
    
    * chore: address PR review stragglers
    
    - Log parse failures in ProcessTraces/ProcessLogs via Trace.WriteLine instead of swallowing silently, matching the rest of OtlpReceiver's error paths.
    - Drop the [EditorBrowsable(Never)] attribute on the internal HasReceiverForTesting property — it's a no-op on internal members.
    - Expose ActivityCollector.MaxExternalSpans internal so the cap test reads the runtime value instead of a hardcoded 100, keeping it correct when TUNIT_OTEL_MAX_EXTERNAL_SPANS is set in the environment.
    
    * chore: update PublicAPI snapshots for OTLP receiver generalization
    
    New AutoReceiver class in TUnit.OpenTelemetry and InternalsVisibleTo
    entries for TUnit.OpenTelemetry.Tests/TUnit.Aspire.Tests.
    
    * fix: read proto fixed64 as uint64 before cast
    
    Matches proto semantics (fixed64 is unsigned). Bit pattern unchanged;
    this expresses intent and keeps the cast explicit at the call site.
    thomhurst authored Apr 17, 2026
    Configuration menu
    Copy the full SHA
    0861740 View commit details
    Browse the repository at this point in the history
  15. +semver:minor - feat: auto-configure OpenTelemetry in TestWebApplicat…

    …ionFactory SUT (#5607)
    
    * feat: auto-configure OpenTelemetry in TestWebApplicationFactory SUT (#5594)
    
    TestWebApplicationFactory<T> now augments the SUT's TracerProvider with
    TUnit's AspNetCore activity source, TUnitTestCorrelationProcessor, and
    ASP.NET Core + HttpClient instrumentation. Spans emitted inside the SUT
    stay queryable per-test (tunit.test.id tag) even when third-party libs
    break the parent chain.
    
    Opt out per-test with WebApplicationTestOptions.AutoConfigureOpenTelemetry = false.
    
    TUnitTestCorrelationProcessor moves from TUnit.OpenTelemetry to TUnit.Core
    (NET-only) so the AspNetCore wrapper can reference it without pulling in
    the zero-config package. Public namespace (TUnit.OpenTelemetry) is unchanged.
    
    * refactor: keep TUnit.Core OTel-free; ref TUnit.OpenTelemetry from AspNetCore.Core
    
    Addresses PR review: TUnit.Core should not own an OpenTelemetry dependency
    (every TUnit user would get OTel transitively). Revert the processor move:
    TUnitTestCorrelationProcessor stays in TUnit.OpenTelemetry, and
    TUnit.AspNetCore.Core references that project directly.
    
    Also document zero-config + SUT double-processor safety on the helper.
    
    * test: update Core public API snapshot for AspNetCoreHttpSourceName
    
    Adds the newly introduced public const on TUnitActivitySource to the
    verified PublicAPI snapshots for net8.0, net9.0, and net10.0.
    thomhurst authored Apr 17, 2026
    Configuration menu
    Copy the full SHA
    9e6ca6f View commit details
    Browse the repository at this point in the history
Loading