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.25.0
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.27.0
Choose a head ref
  • 18 commits
  • 249 files changed
  • 3 contributors

Commits on Apr 4, 2026

  1. chore(deps): update dependency mockolate to 2.3.0 (#5370)

    Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
    thomhurst and renovate-bot authored Apr 4, 2026
    Configuration menu
    Copy the full SHA
    eef7a70 View commit details
    Browse the repository at this point in the history
  2. Fix Dependabot security vulnerabilities in docs site (#5372)

    * Fix 15 Dependabot security vulnerabilities in docs site
    
    Add yarn resolutions to docs/package.json to force patched versions
    of vulnerable transitive dependencies in the Docusaurus docs site.
    
    * Scope path-to-regexp resolution to express only
    
    Only express uses the vulnerable 0.1.x range. react-router (^1.7.0)
    and serve-handler (3.3.0) have incompatible APIs and were incorrectly
    downgraded by the global resolution.
    
    * Fix broken anchor link to troubleshooting page
    
    The link pointed to #test-discovery-issues but the heading is
    "Tests Not Discovered" (#tests-not-discovered).
    thomhurst authored Apr 4, 2026
    Configuration menu
    Copy the full SHA
    677f618 View commit details
    Browse the repository at this point in the history
  3. chore(deps): update tunit to 1.25.0 (#5371)

    Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
    thomhurst and renovate-bot authored Apr 4, 2026
    Configuration menu
    Copy the full SHA
    f3ad994 View commit details
    Browse the repository at this point in the history
  4. chore(deps): update dependency minimatch to v9.0.9 (#5375)

    Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
    thomhurst and renovate-bot authored Apr 4, 2026
    Configuration menu
    Copy the full SHA
    a8207e3 View commit details
    Browse the repository at this point in the history
  5. fix: use 0.0.0-scrubbed sentinel version in snapshot scrubber to avoi…

    …d false Dependabot alerts (#5374)
    
    * fix: scrub snapshot versions to 99.99.99 to avoid false Dependabot alerts
    
    The template snapshot test scrubber was replacing package versions with
    1.0.0, which triggered Dependabot security alerts for packages with
    vulnerabilities fixed above 1.0.0 (e.g. OpenTelemetry.Instrumentation).
    Using 99.99.99 instead ensures scrubbed versions are always above any
    patched version threshold while still stabilizing snapshots.
    
    * refactor: use 0.0.0-scrubbed as sentinel version in snapshot scrubber
    
    Pre-release label makes the version structurally outside Dependabot's
    scan range and self-documenting, rather than relying on 99.99.99 being
    above all patched versions.
    
    * refactor: extract scrubbed version constant and remove redundant filter
    
    - Extract magic string to ScrubbedVersion constant
    - Remove redundant .Where(m => m.Success) — Matches() only returns
      successful matches
    thomhurst authored Apr 4, 2026
    Configuration menu
    Copy the full SHA
    fd84fda View commit details
    Browse the repository at this point in the history
  6. Speed up Engine.Tests and fix missing [Test] attribute (#5379)

    Remove ProcessorCountParallelLimit from TUnit.Engine.Tests assembly - these
    tests are I/O-bound subprocess launches, not CPU-bound, so capping parallelism
    at processor count unnecessarily serialises ~119 tests into sequential batches.
    
    Also add missing [Test] attribute to TimeoutTests1.Test() which was silently
    not running.
    thomhurst authored Apr 4, 2026
    Configuration menu
    Copy the full SHA
    5875588 View commit details
    Browse the repository at this point in the history
  7. Configuration menu
    Copy the full SHA
    64327fe View commit details
    Browse the repository at this point in the history
  8. chore(deps): update dependency path-to-regexp to v0.2.5 (#5376)

    Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
    thomhurst and renovate-bot authored Apr 4, 2026
    Configuration menu
    Copy the full SHA
    d466b96 View commit details
    Browse the repository at this point in the history
  9. ci: add concurrency groups to cancel redundant workflow runs (#5373)

    Prevents redundant CI runs when new commits are pushed to a PR branch,
    saving runner time and reducing queue contention.
    thomhurst authored Apr 4, 2026
    Configuration menu
    Copy the full SHA
    022b93a View commit details
    Browse the repository at this point in the history
  10. Add scope-aware initialization and disposal OpenTelemetry spans to tr…

    …ace timeline and HTML report (#5339)
    
    * Track initialization and disposal times with OpenTelemetry activity spans
    
    - Move "test case" activity span to start before data source initialization
    - Add "data source initialization" child activity span in TestExecutor
    - Add "test instance disposal" activity span in TestCoordinator
    - Set TestStart before initialization so HTML report duration includes init time
    - Simplify TestInitializer now that test case span covers initialization
    
    Agent-Logs-Url: https://github.com/thomhurst/TUnit/sessions/7e7dc4cb-23dd-4723-ba82-6355cfc6d354
    
    Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com>
    
    * Address code review: rename activity, add explanatory comments
    
    - Rename "data source initialization" to "test object initialization" for accuracy
    - Add comment explaining TestStart semantic change (now includes init time)
    - Add comment explaining why disposal activity is parented under class activity
    
    Agent-Logs-Url: https://github.com/thomhurst/TUnit/sessions/7e7dc4cb-23dd-4723-ba82-6355cfc6d354
    
    Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com>
    
    * Address PR review: extract disposal helper, add error recording, fix stale comment
    
    - Extract disposal span logic into DisposeTestInstanceWithSpanAsync() helper to
      reduce #if NET nesting in the finally block (review issue #1)
    - Add TUnitActivitySource.RecordException() calls on disposal span when OnDispose
      or DisposeTestInstance throws (review issue #2)
    - Fix stale comment in TestInitializer.cs referencing "data source initialization"
      instead of "test object initialization" (review issue #4)
    
    Agent-Logs-Url: https://github.com/thomhurst/TUnit/sessions/3802acb9-a7fe-4976-8c3f-5bb246c098d2
    
    Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com>
    
    * Move init/disposal spans outside test case, add type names, restore TestStart
    
    - Initialization span now parented under session activity (not test case) so slow
      infrastructure setup doesn't inflate the test's reported duration
    - Restore TestStart to just before test body execution
    - Span names include the class type: "initialize {TypeName}", "dispose {TypeName}"
    - Restore Activity.Current swapping in TestInitializer.cs for session-level parenting
    - Error recording preserved on both init and disposal spans
    
    Agent-Logs-Url: https://github.com/thomhurst/TUnit/sessions/26cf2c58-e50d-44f7-b5d4-34f5bdc54f52
    
    Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com>
    
    * Address latest review: fix dead code, add missing tags, set Activity.Current, use FullName
    
    - Remove dead RecordException for DisposeTestInstance (swallows all exceptions)
    - Add tunit.test.class tag to dispose span for consistent trace filtering
    - Set Activity.Current = disposalActivity during disposal for child span parenting
    - Use FullName ?? Name for span names to handle nested/generic types
    - Restore Activity.Current after disposal completes
    
    Agent-Logs-Url: https://github.com/thomhurst/TUnit/sessions/172f0096-6317-4169-8b89-b8bc122de149
    
    Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com>
    
    * Add initialization and disposal spans to HTML report execution timeline
    
    Include 'initialize {TypeName}' and 'dispose {TypeName}' spans in the
    global Execution Timeline alongside session, assembly, and suite spans.
    Extracted isGlobalTimelineSpan() helper for readability.
    
    Agent-Logs-Url: https://github.com/thomhurst/TUnit/sessions/472d120e-6d02-435d-bc01-4fbeb030c0ff
    
    Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com>
    
    * Remove full name from class type in TestInitializer
    
    * Restore FullName ?? Name in TestInitializer.cs to fix regression
    
    Agent-Logs-Url: https://github.com/thomhurst/TUnit/sessions/ad55c3d7-39fe-46bd-b02e-b217cf7ec4c8
    
    Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com>
    
    * Implement scope-aware init/dispose tracing based on SharedType
    
    - Add TraceScopeRegistry to map data source objects to their SharedType
    - Register scope in ClassDataSources.Get based on SharedType
    - Move test case activity start before initialization so per-test
      init spans can be children of the test case
    - Create per-object init spans with scope-aware parent selection:
      - PerTestSession → session activity
      - PerAssembly → assembly activity
      - PerClass → class activity
      - None/Keyed/default → test case activity
    - Add tunit.trace.scope tag to init/dispose spans
    - Update HTML report isGlobalTimelineSpan() to show only shared
      init spans in global timeline (not per-test ones)
    - Simplify TestInitializer (tracing now in ObjectLifecycleService)
    
    Agent-Logs-Url: https://github.com/thomhurst/TUnit/sessions/ccdbc811-2561-405a-87e0-7152014e4e69
    
    Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com>
    
    * Address code review: extract GetScopeTag helper, fix null safety, document Keyed scope
    
    Agent-Logs-Url: https://github.com/thomhurst/TUnit/sessions/ccdbc811-2561-405a-87e0-7152014e4e69
    
    Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com>
    
    * Fix TraceScopeRegistry memory leak and redundant registration
    
    - Call TraceScopeRegistry.Clear() in TUnitServiceProvider.DisposeAsync()
      so per-test entries are released at end of session
    - Use TryAdd instead of indexer in Register() to skip redundant writes
      for shared objects that are re-registered on every test
    - Remove unused Unregister() method (cleanup handled by Clear())
    
    * Move TraceScopeRegistry registration from ClassDataSources to engine layer
    
    - Add ITraceScopeProvider interface for data source attributes to declare
      their SharedType per generated object
    - ClassDataSourceAttribute (all variants) implement ITraceScopeProvider
    - Remove TraceScopeRegistry.Register calls from ClassDataSources.Get,
      keeping it as pure user-facing data generation code
    - Add TraceScopeRegistry.RegisterFromDataSource helper that checks if
      a data source implements ITraceScopeProvider and registers objects
    - Call RegisterFromDataSource in TestBuilder (class + method data) and
      PropertyInjector (property data) after objects are produced
    
    * Simplify: remove dead code, fix stringly-typed tag, cache type lookups
    
    - Remove unused TraceScopeRegistry.Register(object, SharedType) — only
      RegisterFromDataSource is called
    - Restore direct return in ClassDataSources.Get (unnecessary local var
      left from removing TraceScopeRegistry.Register)
    - Use GetScopeTag(SharedType.None) instead of hardcoded "test" string
      in DisposeTestInstanceWithSpanAsync for consistency with init spans
    - Cache obj.GetType() and ClassType to avoid repeated property access
      in span creation paths
    
    * Address PR review: fix memory leak, add robustness, tests, and snapshots
    
    - Replace ConcurrentDictionary with ConditionalWeakTable in TraceScopeRegistry
      so per-test data source objects can be GC'd after tests complete
    - Remove TraceScopeRegistry.Clear() from TUnitServiceProvider (no longer needed)
    - Wrap DisposeTestInstance in try/catch with error recording on disposal span
    - Add Activity.Current thread-static limitation comments at restore sites
    - Add comments explaining dual RegisterFromDataSource calls in TestBuilder
    - Update all 4 public API snapshot files with ITraceScopeProvider interface
    - Add 8 unit tests for TraceScopeRegistry covering registration, lookup,
      null handling, duplicate semantics, and reference equality
    
    * Address code review: unify class init path, fix keyed scope tag
    
    - Remove duplicated #if NET class instance init in ObjectLifecycleService;
      call InitializeObjectWithSpanAsync instead (unregistered objects default
      to per-test scope via null SharedType)
    - Map SharedType.Keyed to "session" scope tag to match its parent activity
      selection, eliminating the inconsistency where "keyed" was a non-structural
      scope name
    
    * Improve docs: fix misleading TOCTOU comment, document ITraceScopeProvider contract
    
    * Return defensive copies from GetSharedTypes() and GetKeys()
    
    * Use simple type names in init/dispose span labels for readability
    
    * Deduplicate init spans: only the first caller for a shared object creates a trace span
    
    * Simplify: extract RunWithSpanAsync helper, add tag constants, remove #if NET nesting
    
    * Parent session/keyed init spans under assembly, not session
    
    Session-scoped and keyed init spans were parented under the session
    activity, putting them at the same depth as assemblies. This made
    suite spans (at the next depth level) appear visually nested under
    init spans in both OTel backends and the HTML timeline.
    
    Now all shared init spans (session, assembly, keyed) are parented
    under the assembly activity — siblings of suites. The tunit.trace.scope
    tag still carries the precise lifetime semantics. This removes the
    need for the JS reparenting workaround in the global timeline.
    
    * Make ITraceScopeProvider internal, revert defensive copies
    
    ITraceScopeProvider is an implementation detail of the tracing system —
    only ClassDataSourceAttribute variants implement it, all in the same
    assembly. Making it internal removes it from the public API surface.
    
    The defensive array copies ([..Shared]) are unnecessary since all callers
    are internal and iterate immediately. Revert to the original direct returns.
    
    * Use session span duration for header when available
    
    The header duration was calculated from test execution timing only,
    excluding initialization time. The session span captures the full
    wall-clock time. Falls back to test-based timing on non-.NET or
    when no session span exists.
    
    * Address review: span name constants, disposal error recording, memory leak fix
    
    - Extract span name constants (SpanTestSession, SpanTestAssembly, etc.) into
      TUnitActivitySource and replace all magic strings across C# call sites
    - Record disposal exceptions on the trace span before swallowing them, so
      disposal errors are visible in traces instead of always showing green
    - Clear _spannedObjects in ClearCache() to prevent memory leak for per-test
      objects that were kept alive by hard references
    - Gate _spannedObjects.TryAdd behind HasListeners() to skip work when no
      trace listener is attached
    - Update public API snapshots to remove internal ITraceScopeProvider from
      class declarations
    
    * Fix Activity.Current comment: AsyncLocal, not ThreadStatic
    
    * Use ConditionalWeakTable for span dedup gate, fix Activity.Current comment
    
    - Replace ConcurrentDictionary with ConditionalWeakTable for _spannedObjects
      so per-test objects can be GC'd after their test completes instead of being
      retained for the entire session
    - Use Interlocked.Exchange on StrongBox<int> for atomic first-caller-wins gate
    - Fix Activity.Current comment: it is AsyncLocal, not ThreadStatic
    
    * Polish: readable type names, omit test ID from shared spans, add disposal TODO
    
    - Add GetReadableTypeName helper that strips namespace but preserves nesting
      (Outer.Inner) and cleans generic arity suffixes (MySource`1 → MySource)
    - Use GetReadableTypeName for init and dispose span labels
    - Omit tunit.test.id tag from shared-scope init spans (the triggering test
      is arbitrary for shared objects, making the tag misleading)
    - Add TODO in ObjectTracker.UntrackObject for shared-object disposal spans
    - Add GetReadableTypeNameTests covering simple, nested, generic, and deeply
      nested types
    
    * Wrap GetReadableTypeNameTests in #if NET for net472 compatibility
    
    TUnitActivitySource is guarded by #if NET in TUnit.Core, so these
    tests cannot compile on net472. Matches the conditional compilation
    of the class under test.
    
    ---------
    
    Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
    Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com>
    Copilot and thomhurst authored Apr 4, 2026
    Configuration menu
    Copy the full SHA
    a24a468 View commit details
    Browse the repository at this point in the history
  11. chore(deps): update dependency minimatch to v10 (#5377)

    Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
    thomhurst and renovate-bot authored Apr 4, 2026
    Configuration menu
    Copy the full SHA
    beccae2 View commit details
    Browse the repository at this point in the history
  12. chore(deps): update dependency picomatch to v4 (#5382)

    Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
    thomhurst and renovate-bot authored Apr 4, 2026
    Configuration menu
    Copy the full SHA
    b0921a2 View commit details
    Browse the repository at this point in the history
  13. Add WithInnerExceptions() for fluent AggregateException assertion cha…

    …ining (#5380)
    
    * +semver:minor - Add WithInnerExceptions() for fluent AggregateException assertion chaining (#5345)
    
    * Address PR review: add metadata.Exception guard and fix error message
    
    * Constrain WithInnerExceptions to AggregateException at compile time
    
    Move WithInnerExceptions from instance methods on ThrowsAssertion<T> and
    ThrowsExactlyAssertion<T> to extension methods constrained to
    AggregateException. This prevents misuse at compile time rather than
    runtime. Also simplify WithInnerExceptionsAssertion to a non-generic
    class since TException is always AggregateException.
    
    * Update .gitattributes with EOF best practices
    
    * Regenerate Assertions public API snapshots for WithInnerExceptions changes
    
    * Propagate inner assertion failure message in WithInnerExceptions catch block
    
    Change bare `catch` to `catch (Exception ex)` and include ex.Message
    in the failure string, consistent with MappedSatisfiesAssertion and
    other sibling assertion patterns in the codebase.
    thomhurst authored Apr 4, 2026
    Configuration menu
    Copy the full SHA
    e4c164f View commit details
    Browse the repository at this point in the history
  14. chore(deps): update dependency svgo to v4 (#5383)

    Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
    thomhurst and renovate-bot authored Apr 4, 2026
    Configuration menu
    Copy the full SHA
    bc313ca View commit details
    Browse the repository at this point in the history
  15. Drop net6.0 and net7.0 TFMs, keep net8.0+ and netstandard2.x (#5387)

    Both net6.0 (Nov 2024) and net7.0 (May 2024) are out of support.
    Remove net6.0 from TargetFrameworks in test/nuget-tester projects
    and update all #if NET6_0_OR_GREATER / NET7_0_OR_GREATER preprocessor
    directives to NET8_0_OR_GREATER across 67 files.
    
    .NET Framework users are unaffected (covered by netstandard targets).
    
    Closes #4786
    thomhurst authored Apr 4, 2026
    Configuration menu
    Copy the full SHA
    b9f6286 View commit details
    Browse the repository at this point in the history
  16. Remove all [Obsolete] members and migrate callers (#5384)

    * Remove all [Obsolete] members and migrate callers to current APIs
    
    - Remove ObjectBag properties from TestRegisteredContext and TestBuilderContext (use StateBag)
    - Remove Timing record and ITestOutput.Timings/RecordTiming (replaced by OpenTelemetry spans);
      internal TimingEntry record preserves engine functionality
    - Remove obsolete HasCount()/HasCount(int) from CollectionAssertionBase;
      migrate all callers to Count().IsEqualTo(N) or Count(c => c.IsEqualTo(N))
    - Remove obsolete HasLength()/HasLength(int) from AssertionExtensions;
      migrate all callers to Length().IsEqualTo(N) or Length(l => l.IsEqualTo(N))
    - Add Length(lambda) overload for inline assertions that preserve string
      context for .And chaining, matching the existing Count(lambda) pattern
    - Update docs, code examples, and XUnit migration code fixer
    
    * Extract shared inline assertion helper and propagate inner failure messages
    
    Deduplicate the lambda-assertion execution logic from
    StringLengthWithInlineAssertionAssertion and
    CollectionCountWithInlineAssertionAssertion into a shared
    InlineAssertionHelper. Inner assertion failure messages are now
    propagated instead of being replaced with a generic "count/length was N".
    
    * Fix tests to use Length(lambda) for string-context-preserving chaining
    
    Length() maps context to int, so And/Or chaining after it cannot return
    to string assertions. Use Length(l => l.IsEqualTo(N)) which preserves
    the string context for continued chaining.
    
    * Regenerate public API snapshots after removing obsolete members
    
    * Address PR review: delete dead wrappers, improve inline assertion messages, make helper generic
    
    - Delete CountWrapper and LengthWrapper (dead code after removing HasCount/HasLength no-arg overloads)
    - Make InlineAssertionHelper generic and return inner assertion for expectation delegation
    - Improve GetExpectation() in StringLengthWithInlineAssertionAssertion and CollectionCountWithInlineAssertionAssertion to delegate to inner assertion (e.g. "length to be equal to 5" instead of "to satisfy length assertion")
    - Fix null message from "value was null" to "string was null" for string length assertions
    - Remove unused Wrappers namespace imports
    - Regenerate public API snapshots
    thomhurst authored Apr 4, 2026
    Configuration menu
    Copy the full SHA
    df31e91 View commit details
    Browse the repository at this point in the history
  17. chore(deps): update dependency path-to-regexp to v1 [security] (#5385)

    Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
    thomhurst and renovate-bot authored Apr 4, 2026
    Configuration menu
    Copy the full SHA
    5db8137 View commit details
    Browse the repository at this point in the history
  18. +semver:minor - Add AssertionResult.Failed overload that accepts an E…

    …xception (#5388)
    
    * Add AssertionResult.Failed overload that accepts an Exception (#5381)
    
    Preserve original exceptions when nested/inline assertions fail by
    adding an Exception? property to AssertionResult and threading it
    through to AssertionException as an inner exception. Updated 22 catch
    sites across assertions and mock assertions.
    
    * Add context to GroupAssertion error messages
    
    The bare `Failed(ex.Message, ex)` calls gave no indication which group
    lookup failed. Now includes "failed to get group 'name': ..." for
    consistency with other catch sites.
    
    * Propagate exceptions through all metadata.Exception guards and cache Passed
    
    - Update ~110 remaining metadata.Exception guard sites across 32 files
      to pass the exception as second argument to AssertionResult.Failed()
    - Cache the Passed struct in a static readonly field to avoid repeated
      allocations on the happy path
    
    * Propagate exceptions in remaining guard sites
    
    Fix missed evaluationException guards in ExceptionPropertyAssertions
    (8 sites), ThrowsAssertion, WithInnerExceptionsAssertion, and
    MatchIndexAssertion.
    
    * Unwrap AssertionException in WaitsForAssertion to avoid double-wrapping
    
    WaitsForAssertion catches AssertionException from repeated assertion
    attempts. Passing it directly as inner exception produces a misleading
    AssertionException → AssertionException chain. Unwrap to the original
    root cause instead.
    
    * Address review feedback
    
    - Fix missed propagation site in StringAssertions.cs (line 613)
    - Add Failed(string, Exception?) overload to AssertionResult<T> for
      parity with non-generic AssertionResult
    - Revert WaitsForAssertion unwrapping — keep the full AssertionException
      as inner exception since it carries the formatted assertion message
    thomhurst authored Apr 4, 2026
    Configuration menu
    Copy the full SHA
    46555b5 View commit details
    Browse the repository at this point in the history
Loading