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.29.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.30.0
Choose a head ref
  • 18 commits
  • 114 files changed
  • 2 contributors

Commits on Apr 7, 2026

  1. chore(deps): update tunit to 1.29.0 (#5446)

    Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
    thomhurst and renovate-bot authored Apr 7, 2026
    Configuration menu
    Copy the full SHA
    fe7002a View commit details
    Browse the repository at this point in the history
  2. perf: eliminate locks from mock invocation and verification hot paths (

    …#5422)
    
    * perf: eliminate locks from mock invocation and verification hot paths
    
    Replace the shared MockEngine.Lock with per-member lock-free call recording
    and lock-free verification reads. This targets the three benchmark categories
    where TUnit.Mocks loses to source-generated competitors (Imposter, Mockolate):
    
    **Callback (60% slower than Imposter)**
    - StoreCallRecord no longer acquires MockEngine.Lock on the hot path
    - CallRecordBuffer uses a lightweight per-buffer lock instead of the shared
      engine lock, eliminating contention between setup and invocation
    - Buffers are pre-created during setup so invocations never allocate or
      initialize under lock
    
    **Setup (18% slower than Mockolate)**
    - AddBehavior uses Interlocked.CompareExchange for the common single-behavior
      case, avoiding Lock object allocation and lock acquisition entirely
    
    **Verification (13% slower than Imposter)**
    - Single-pass verification: count and mark verified in one iteration instead
      of two. Only unmarks on failure (rare path).
    - GetCallBufferFor returns the internal buffer directly, eliminating the
      .ToArray() copy allocation on the verification happy path
    - GetCallsFor, MarkCallsVerified, GetAllCalls, GetUnverifiedCalls are now
      lock-free reads
    
    All 822 mock tests pass (761 + 31 + 30).
    
    * fix: address review feedback on lock-free hot paths
    
    - Replace lock(this) with private _syncRoot object in CallRecordBuffer
    - Add GetSnapshot() to cache array reference for tight iteration loops,
      eliminating N volatile reads in CountAndMarkBuffer
    - Remove misleading AggressiveInlining on Add (JIT can't inline lock bodies)
    - Add double-check after CAS in AddBehavior to prevent race with concurrent
      AddBehaviorSlow on the same MethodSetup
    - Fix inaccurate "single-writer"/"lock-free" comment on _callsByMemberId
    - Remove unnecessary WHAT-style doc comments
    
    * fix: address PR review — bundle volatile refs, fix snapshot ordering, restore count-first verification
    
    - Bundle _callsByMemberId/_callCountByMemberId into single volatile CallArrays
      reference to eliminate TOCTOU race between the two fields
    - Fix GetSnapshot() read order: items before count ensures count <= items.Length
      after Grow() swaps the array
    - Restore count-first, mark-on-success verification pattern to avoid corrupting
      prior verifications on assertion failure
    
    * docs: address remaining review items — volatility rationale and snapshot safety
    
    - Document why _singleBehavior/_behaviors aren't volatile (CS0420 with
      Interlocked.CompareExchange) and that all accesses use Volatile.Read/Write
    - Document CAS full-barrier safety in AddBehavior fast path
    - Document CollectCallRecords snapshot semantics for concurrent capacity growth
    
    * fix: correct GetSnapshot read order, fix XML docs, remove dead guard
    
    - GetSnapshot: read count before items (count-first is safe because writer
      updates items/Grow before incrementing count — seeing new count guarantees
      items.Length >= count)
    - Fix duplicate <summary> tag → <remarks>
    - Remove dead _matchers.Length == 0 guard in CountMatchingBuffer/MarkMatchingBuffer
      (already handled by the fast path in WasCalled)
    
    * perf: use System.Threading.Lock for CallRecordBuffer per-buffer lock
    
    Consistent with MockEngine; faster on .NET 9+ and better diagnostics.
    thomhurst authored Apr 7, 2026
    Configuration menu
    Copy the full SHA
    630182a View commit details
    Browse the repository at this point in the history

Commits on Apr 8, 2026

  1. Configuration menu
    Copy the full SHA
    83840f3 View commit details
    Browse the repository at this point in the history
  2. Configuration menu
    Copy the full SHA
    8da52cd View commit details
    Browse the repository at this point in the history
  3. chore(deps): update react to ^19.2.5 (#5457)

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

    Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
    thomhurst and renovate-bot authored Apr 8, 2026
    Configuration menu
    Copy the full SHA
    9500f16 View commit details
    Browse the repository at this point in the history
  5. chore(deps): update dependency qs to v6.15.1 (#5458)

    Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
    thomhurst and renovate-bot authored Apr 8, 2026
    Configuration menu
    Copy the full SHA
    526a540 View commit details
    Browse the repository at this point in the history
  6. feat: TUnit0074 analyzer for redundant hook attributes on overrides (#…

    …5459)
    
    * feat: TUnit0074 analyzer for redundant hook attributes on overrides
    
    When a method declares [Before(Test)] / [After(Test)] and overrides a base
    method that already declares the same hook attribute, both registrations are
    invoked via virtual dispatch on the same instance — the override's body runs
    twice per test. The previous fix (#5428 / #5441) deduplicated this at runtime
    via MethodInfo.GetBaseDefinition(), which silently hid the duplication instead
    of surfacing it to the author and didn't address the [InheritsTests] variant
    in #5450.
    
    Replace the runtime dedup with a compile-time analyzer (TUnit0074, Error)
    plus a code fix that removes the redundant attribute. The analyzer walks the
    full IMethodSymbol.OverriddenMethod chain so transitive cases (gap in the
    middle of the override chain) are also caught, and the [InheritsTests] +
    abstract intermediate shape from #5450 fails to compile at the intermediate's
    override.
    
    Revert the runtime dedup: drop InstanceHookMethod.BaseDefinition,
    ResolveBaseDefinition / IsOverriddenByMoreDerivedHook from HookDelegateBuilder,
    and the GetBaseDefinition() init in ReflectionHookDiscoveryService. PublicAPI
    snapshots updated accordingly.
    
    Regression tests (VirtualHookOverrideTests + Bugs/5450) rewritten to the
    "attribute only on base, override without attribute" shape — the only shape
    TUnit0074 still allows. Assertions are inlined inside the hook bodies because
    After-hooks are sorted derived-class-first across the type hierarchy, so a
    separate verification hook on the derived class would run before the base
    teardown.
    
    * address review: combine null guards in analyzer + KeepLeadingTrivia in code fix
    
    - Merge the IsStandardHook/HookLevel/hookType checks into one if so the
      compiler tracks nullability without needing the null-forgiving operator.
    - Use KeepLeadingTrivia when removing the attribute list so any leading
      comment above the attribute is preserved on the method declaration.
    thomhurst authored Apr 8, 2026
    Configuration menu
    Copy the full SHA
    dd333ee View commit details
    Browse the repository at this point in the history
  7. fix(mocks): respect generic type argument accessibility (#5453) (#5460)

    Extends the effective-accessibility check from #5426 to also walk
    generic type arguments and array element types. Previously, mocking a
    public generic interface closed over an internal type argument (e.g.
    Mock.Of<ILogger<InternalClass>>()) emitted a public wrapper whose base
    signature leaked the internal type, producing CS9338/CS0051.
    
    IsEffectivelyPublic now switches on the type kind and recurses into
    TypeArguments/ElementType, collapsing the previous two helpers into one.
    thomhurst authored Apr 8, 2026
    Configuration menu
    Copy the full SHA
    617c4f0 View commit details
    Browse the repository at this point in the history
  8. fix(mocks): skip inaccessible internal accessors when mocking Azure.R…

    …esponse (#5461)
    
    Azure.Response.IsError is declared `public virtual bool IsError { get; internal set; }`.
    The mock generator was emitting `override bool IsError { get; set; }` because it only
    checked symbol presence (`property.SetMethod is not null`), not accessor-level
    accessibility. External assemblies can't see the internal setter, producing CS0115:
    'ResponseMockImpl.IsError.set': no suitable method found to override.
    
    Adds an `IsAccessorAccessible` helper to `MemberDiscovery` and threads the compilation
    assembly through `CreatePropertyModel`, `CreateIndexerModel`, and `MergePropertyAccessors`
    so `HasGetter`/`HasSetter`/`SetterMemberId` reflect whether each accessor is actually
    reachable from the user's assembly, across both class- and interface-discovery paths.
    
    Fixes #5455
    thomhurst authored Apr 8, 2026
    Configuration menu
    Copy the full SHA
    538572d View commit details
    Browse the repository at this point in the history
  9. docs: fix hook execution order in OpenTelemetry guide

    Before(TestDiscovery) runs before Before(TestSession), not the other way around.
    thomhurst committed Apr 8, 2026
    Configuration menu
    Copy the full SHA
    782c9ce View commit details
    Browse the repository at this point in the history
  10. Configuration menu
    Copy the full SHA
    269521b View commit details
    Browse the repository at this point in the history
  11. docs: fix API drift found in documentation audit

    Corrects stale property names, missing API references, and incorrect
    assertion method names across the docs. Also adds a single-arg
    InconclusiveTestException constructor so the documented usage compiles.
    thomhurst committed Apr 8, 2026
    Configuration menu
    Copy the full SHA
    e1a1ab1 View commit details
    Browse the repository at this point in the history

Commits on Apr 9, 2026

  1. docs: fix API drift across docs from audit

    Parallel audit of all docs against source uncovered fabricated APIs,
    reversed semantics, and stale signatures. Fixes include:
    
    - writing-tests/generic-attributes.md: rewrite DataSourceGeneratorAttribute,
      AsyncDataSourceGeneratorAttribute, and TypedDataSourceAttribute sections
      to match real abstract method signatures
    - guides/performance.md: correct reversed [ParallelGroup] semantics
      (same group runs in parallel, different groups run sequentially) and
      replace invalid [Arguments] matrix-explosion example with [MatrixDataSource]
    - examples/fsharp-interactive.md: replace fabricated TUnitRunner API with
      real TestApplication.CreateBuilderAsync + AddTUnit entry point
    - examples/aspnet.md: move body-capture options to AddHttpExchangeCapture
      service registration; HttpCapture.All -> Exchanges
    - migration/testcontext-interface-organization.md: correct ITestOutput,
      ITestExecution, ITestMetadata, ITestDependencies interface snippets
    - migration/nunit.md, mstest.md: IsInAscendingOrder -> IsInOrder,
      AllSatisfy(predicate) -> All().Satisfy(src => ...)
    - assertions/regex-assertions.md: remove fabricated .When/.AtIndex/.Length
      chaining, fix RegexOptions usage
    - assertions/combining-assertions.md: warn that .And/.Or cannot be mixed
    - assertions/collections.md: IsStructurallyEqualTo -> IsEquivalentTo
    - assertions/datetime.md: WithinDays fluent form, .NET 8+ framework gate
    - reference/command-line-flags.md: add missing --output-json,
      --report-html, --junit-output-path, --detailed-stacktrace flags
    - reference/environment-variables.md: add TUNIT_EXECUTION_MODE,
      TUNIT_DISCOVERY_DIAGNOSTICS, TUNIT_DIAGNOSTIC_CAST
    - execution/ci-cd-reporting.md: replace fabricated CI environment-detection
      table with real GITHUB_ACTIONS/GITLAB_CI/CI_SERVER vars; replace VSTest
      --logger syntax with MTP --output Detailed
    - benchmarks/methodology.md: fix [Matrix] usage to require [MatrixDataSource]
      with parameter-level attributes
    - writing-tests/artifacts.md: testContext.Result -> testContext.Execution.Result,
      TestState.TimedOut -> Timeout
    - extending/display-names.md, exception-handling.md, extension-points.md:
      remove fake \$\$ escape, use public Metadata.TestDetails accessor,
      fix TestClass -> ClassType
    thomhurst committed Apr 9, 2026
    Configuration menu
    Copy the full SHA
    db13cfc View commit details
    Browse the repository at this point in the history
  2. chore(engine): remove unused parallelism-strategy and adaptive-metric…

    …s flags
    
    Both --parallelism-strategy and --adaptive-metrics CLI flags were
    registered with command-line validation but their values were never
    read anywhere in the engine. The ParallelismStrategy enum had no
    consumers either.
    
    Removes:
    - ParallelismStrategyCommandProvider
    - AdaptiveMetricsCommandProvider
    - ParallelismStrategy enum
    - AddProvider registration calls in TestApplicationBuilderExtensions
    thomhurst committed Apr 9, 2026
    Configuration menu
    Copy the full SHA
    65f94d9 View commit details
    Browse the repository at this point in the history
  3. Configuration menu
    Copy the full SHA
    0892b46 View commit details
    Browse the repository at this point in the history
  4. Configuration menu
    Copy the full SHA
    447b4ed View commit details
    Browse the repository at this point in the history
  5. +semver:minor - fix: apply CultureAttribute and STAThreadExecutorAttr…

    …ibute to hooks (#5452) (#5463)
    
    * fix: apply CultureAttribute and STAThreadExecutorAttribute to hooks (#5452)
    
    CultureAttribute, STAThreadExecutorAttribute and TestExecutorAttribute were
    only wired into the test executor pipeline. Lifecycle hooks (Before/After
    Test, Class, Assembly, Session) ran on the default executor regardless of
    scope, so a class-level [Culture] never affected its hooks.
    
    Fix:
    - Implement IHookRegisteredEventReceiver on all three attributes so
      class/assembly/session hooks get the custom executor.
    - Also call SetHookExecutor in OnTestRegistered so per-test hooks share the
      same executor as the test body.
    - Add settable HookExecutor on HookRegisteredContext, applied back to the
      hook method by EventReceiverOrchestrator (mirrors the Timeout pattern).
    - Cache one executor instance per attribute so the test and hook executor
      roles share the same object instead of allocating twice.
    
    Also fixes a precedence bug surfaced by the change: BeforeTestHookMethod,
    AfterTestHookMethod and HookTimeoutHelper preferred TestContext.CustomHookExecutor
    over the hook's own HookExecutor unconditionally. A hook with an explicit
    [HookExecutor<T>] now wins; CustomHookExecutor is only a fallback when the
    hook is still on DefaultExecutor. This preserves the #2666 SetHookExecutor
    scenario without overriding explicit method-level declarations.
    
    Follow-up #5462 tracks the inverse gap: HookExecutorAttribute at class or
    assembly level is still ignored by discovery.
    
    * refactor: extract hook executor precedence helper and fix reflection-mode sentinel
    
    Consolidates the duplicated "hook's own HookExecutor wins over
    CustomHookExecutor" precedence logic into HookMethod.ResolveEffectiveExecutor,
    collapsing BeforeTestHookMethod / AfterTestHookMethod / HookTimeoutHelper to
    single-line executor calls.
    
    Also fixes a pre-existing reflection-mode regression: the precedence check uses
    ReferenceEquals(_, DefaultExecutor.Instance), but ReflectionHookDiscoveryService
    was returning a fresh DefaultHookExecutor per hook, breaking the #2666
    SetHookExecutor scenario under --reflection. Reusing DefaultExecutor.Instance
    restores parity with source-gen.
    
    * fix: align instance hook executor precedence and assert After(Class) culture
    
    Instance hooks went through a separate execution path in
    HookDelegateBuilder.CreateInstanceHookDelegateAsync that unconditionally
    preferred CustomHookExecutor, letting it silently override an explicit
    [HookExecutor<T>] on an instance before/after hook (the opposite of the static
    path's precedence). Route InstanceHookMethod.ExecuteAsync through
    ResolveEffectiveExecutor and collapse the delegate builder's custom-executor
    branch so both static and instance hooks share one precedence rule — and, as a
    bonus, instance hooks now honour Timeout when a custom hook executor is in use.
    
    Also assert CultureInfo.CurrentCulture inside CultureHookTests_ClassLevel's
    After(Class) hook so the After(Class) path is actually covered (no subsequent
    test can read a field captured after the class tears down).
    
    * docs: note single-test invariant in CultureHookTests_MethodLevelOverride
    
    The BeforeTest assertion only holds because this fixture contains exactly one
    test with [Culture("fr-FR")]. Adding a test without the override would cause
    BeforeTest to fail against de-AT. Cross-reference the inherits-class fixture
    to keep the two cases obvious.
    thomhurst authored Apr 9, 2026
    Configuration menu
    Copy the full SHA
    96c1214 View commit details
    Browse the repository at this point in the history
Loading