-
-
Notifications
You must be signed in to change notification settings - Fork 125
Comparing changes
Open a pull request
base repository: thomhurst/TUnit
base: v1.32.0
head repository: thomhurst/TUnit
compare: v1.33.0
- 7 commits
- 92 files changed
- 2 contributors
Commits on Apr 11, 2026
-
chore(deps): update tunit to 1.32.0 (#5513)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
Configuration menu - View commit details
-
Copy full SHA for a2d9a86 - Browse repository at this point
Copy the full SHA a2d9a86View commit details
Commits on Apr 12, 2026
-
Configuration menu - View commit details
-
Copy full SHA for 336ac83 - Browse repository at this point
Copy the full SHA 336ac83View commit details -
Configuration menu - View commit details
-
Copy full SHA for c6915ca - Browse repository at this point
Copy the full SHA c6915caView commit details -
perf: engine-wide performance optimizations (#5520)
* perf: engine-wide performance optimizations across discovery, scheduling, and reporting Fix critical hash degradation, eliminate O(n²) algorithms, reduce allocations, and improve thread safety across 14 files in TUnit.Core and TUnit.Engine. Key changes: - Fix ConstraintKeysCollection.GetHashCode() returning constant 1, restoring O(1) dictionary lookups (was O(n) due to hash collisions) - Replace O(n²) topological sort in test discovery with Kahn's algorithm O(V+E) - Replace ConcurrentBag with ConcurrentQueue for better enumeration performance - Replace 6-pass LINQ categorization in GitHubReporter with single-pass loop - Fix thread safety in GitHubReporter/JUnitReporter (List → ConcurrentQueue) - Replace GroupBy + Count() with single-pass counter loops in orchestrators - Cache reflection results (ExplicitAttribute, ConstructorInfo, PropertyBag) - Guard trace log string interpolation behind IsTraceEnabled checks - Add TestContext.RemoveById() to prevent memory leak in long-running hosts - Remove redundant EnsureTestSessionHooksExecutedAsync call - Add fast-path in XML sanitization to skip StringBuilder when not needed - Make factory lambdas static to avoid delegate allocations * fix: address code review findings - Revert ConstraintKeysCollection.GetHashCode() to constant (0) — intersection-based Equals means two "equal" collections can have different key sets, making a content-based hash violate the hash/equals contract - Replace SingleOrDefault<TestNodeStateProperty>() with OfType<>().FirstOrDefault() in GitHubReporter to avoid throwing if multiple state properties exist - Add comments on _latestUpdates explaining the intentional non-atomic ordering trade-off - Improve cycle-break comment in Kahn's algorithm noting CircularDependencyDetector handles the error reporting * fix: address follow-up review findings - Remove dead _updates field from JUnitReporter (only _latestUpdates is needed) - Fix second SingleOrDefault in GitHubReporter flaky detection loop - Use _latestUpdates.IsEmpty for empty check in both reporters - Use typed constructor lookup in MetadataFilterMatcher instead of fragile [0] indexer
Configuration menu - View commit details
-
Copy full SHA for bfd4a20 - Browse repository at this point
Copy the full SHA bfd4a20View commit details -
feat: Add TUnitSettings static API for programmatic configuration (#5522
) * feat: add TUnitSettings static API classes (#5521) * refactor: deprecate Defaults class and switch internal reads to TUnitSettings (#5521) Mark the Defaults class and all its fields as [Obsolete], pointing users to TUnitSettings.Timeouts.* equivalents. Migrate all internal references from Defaults.* to Settings.TUnitSettings.Timeouts.* to eliminate CS0618 warnings under TreatWarningsAsErrors. Also fix a namespace collision in NuGetDownloader.cs caused by the new TUnit.Core.Settings namespace. * feat: wire TUnitSettings into engine for parallelism, fail-fast, and display (#5521) * test: update public API snapshots for TUnitSettings (#5521) * test: add TUnitSettings unit tests (#5521) * docs: add programmatic configuration documentation (#5521) * refactor: eliminate redundant defaults and simplify Settings access paths (#5521) - Defaults.cs fields now delegate to TUnitSettings.Timeouts (single source of truth) - Add `using TUnit.Core.Settings` to TUnit.Core files for consistent shorter access * fix: address review feedback — remove DisableLogo, lazy FailFast, validate parallelism (#5521) - Remove DisplaySettings.DisableLogo (banner fires before discovery hooks, making it useless as a code setting) - Make FailFast check lazy in TestRunner (reads TUnitSettings.Execution.FailFast at failure time, not at construction) - Add setter validation on MaximumParallelTests (reject negative values) - Document that MaximumParallelTests is read before discovery hooks - Update public API snapshots and docs * fix: address second review — clean up FailFast redundancy, revert Defaults alias, fix docs (#5521) - Remove TUnitSettings.Execution.FailFast from TUnitServiceProvider (eager capture). TestRunner already checks it lazily at failure time — no double-check needed. - Revert Defaults.cs to hardcoded values (static readonly fields can't track mutable TUnitSettings; stale alias is worse than honest deprecation). - Remove MaximumParallelTests from discovery hook example in docs (silently ignored due to scheduler timing) and add a note about the limitation. * docs: fix misleading parallelism example and document hook timeout ordering (#5521) - Replace discovery-hook example in parallelism.md with CLI/env-var approach (MaximumParallelTests is read before discovery hooks run) - Qualify "When to Set" section to note MaximumParallelTests exception - Document DefaultHookTimeout ordering constraint in XML doc * fix: address review round 5 — doc gap, test guard, dead code - Document DefaultHookTimeout timing exception in "When to Set" section - Add Before/After hooks to TUnitSettingsTests to snapshot and restore static state, making Defaults_Are_Correct resilient to prior mutations - Remove dead negative-value branch in GetMaxParallelism (setter already validates) * fix: add thread-safe volatile reads/writes to boolean settings Use Volatile.Read/Write for FailFast and DetailedStackTrace backing fields so cross-thread visibility is guaranteed when settings are configured in a discovery hook and read during parallel test execution. * fix: remove Volatile from boolean settings for consistency Revert to plain auto-properties on all settings classes. The framework's lifecycle guarantees that discovery hooks complete before test threads start, so no per-field synchronization is needed. Volatile cannot be applied uniformly to TimeSpan/int? types, so mixing patterns was worse than relying on the existing happens-before guarantee. Added threading contract doc comment on TUnitSettings. * fix: Defaults delegates to TUnitSettings, add TimeoutSettings validation - Change Defaults fields from static readonly to computed properties that delegate to TUnitSettings, so deprecated consumers see the correct value even after programmatic configuration. - Add setter validation to all TimeoutSettings properties — reject TimeSpan.Zero and negative values with ArgumentOutOfRangeException, consistent with ParallelismSettings. - Update public API snapshots for Defaults field→property change. * feat: make TUnitSettings non-static, expose via BeforeTestDiscoveryContext TUnitSettings is now a sealed class (not static) with an internal-only Default singleton. Users access settings exclusively through context.Settings in a [Before(HookType.TestDiscovery)] hook, which naturally enforces the correct lifecycle timing. Engine code uses TUnitSettings.Default.* via InternalsVisibleTo. Removed TUnitSettingsAccessor (no longer needed). Updated all docs to reference context.Settings instead of the static API. * fix: allow TimeSpan.Zero for ProcessExitHookDelay ProcessExitHookDelay is a delay, not a timeout — zero is a valid value meaning "no delay". Only reject negative values, unlike the three actual timeout properties which reject zero (a zero-duration timeout would cause immediate cancellation). * feat: defer MaximumParallelTests read with Lazy<T> Use Lazy<int> for _maxParallelism and Lazy<SemaphoreSlim?> for the semaphore so the value is computed on first access (during test execution) rather than at TestScheduler construction (before discovery hooks). Users can now set MaximumParallelTests in a [Before(HookType.TestDiscovery)] hook and have it take effect. Closes #5523 Removed timing caveats from ParallelismSettings XML doc and programmatic-configuration.md since the limitation no longer exists. * refactor: remove redundant comments and resolve Lazy<T> values once in hot paths * docs: remove DefaultHookTimeout from usage example It is captured at hook registration time before discovery hooks run, so setting it in a [Before(HookType.TestDiscovery)] hook has no effect. The "When to Set" section already documents this exception. * fix: defer DefaultHookTimeout resolution to execution time HookMethod.Timeout now defaults to null instead of eagerly capturing TUnitSettings.Default.Timeouts.DefaultHookTimeout at registration time. HookTimeoutHelper resolves the fallback lazily at execution time, so discovery-hook configuration is respected — matching the pattern already used for FailFast and MaximumParallelTests. * refactor: make sub-settings constructors internal Users should access settings via context.Settings, not by constructing new instances directly. Internal constructors prevent creating orphaned instances that have no connection to the engine.
Configuration menu - View commit details
-
Copy full SHA for 5c72b1b - Browse repository at this point
Copy the full SHA 5c72b1bView commit details -
perf: reduce allocations and improve hot-path performance (#5524)
* perf: reduce allocations and improve hot-path performance across engine Key optimizations: - Lazy-init Context output state (StringBuilder, RWLS, ConsoleLineBuffer) with thread-safe LazyInitializer — most test contexts never capture output - Volatile cached array for log sinks (eliminates lock + ToArray on every Console.Write) - Replace ConcurrentBag with List for sequential-write collections (Timings, Artifacts) - O(1) duplicate detection in ClassHookContext via generic ReferenceEqualityComparer<T> - Parallel test registration with Parallel.ForEachAsync for 8+ tests - HashSet-based UID filter lookup instead of O(N) list scan - CTS allocation fast-path in HookTimeoutHelper when no timeout configured - Targeted EventReceiverRegistry cache invalidation instead of blanket Clear() - Deferred StateBag/Events allocation in TestBuilderContext - Eliminate LINQ allocations in hot paths (TestBuilder, TestExtensions, TestFilterService) - Conditional List<Exception> allocation in test teardown * fix: address PR review feedback - Revert SingleOrDefault to OfType<T>().FirstOrDefault() in GitHubReporter (SingleOrDefault throws on >1 match, breaking defensive resilience) - Use LazyInitializer.EnsureInitialized for _uidFilterSet in TestFilterService (consistent thread-safe lazy init pattern across the PR) - Extract TimeSpan.FromDays(1) to static readonly field in HookTimeoutHelper (avoid recomputation on every call) * fix: address second review — thread-safe artifacts and documented parallelism - Revert _artifacts to thread-safe collection (Lock + List instead of ConcurrentBag) since AttachArtifact is user-facing and can be called from parallel Task.WhenAll branches within a single test - Document concurrency contract on RegisterTestsAsync: per-test state is isolated, shared services use ConcurrentDictionary, and ITestRegisteredEventReceiver implementations must be thread-safe * fix: simplification review — bug fix, efficiency, and cleanup - Fix ClassHookContext.RemoveTest not removing from _testSet (would silently drop re-added tests after removal) - Add null-check guard in GetOrCreateUidFilterSet to avoid closure allocation on every MatchesTest call after initialization - Capture testContext.Artifacts once in TestExtensions to eliminate double lock acquisition and double array copy - Trim narrating comment in Context.cs * docs: add thread-safety note to ITestRegisteredEventReceiver OnTestRegistered may be called concurrently when many tests are registered. Document this contract at the interface level so implementors are aware. * chore: accept public API snapshots for ReferenceEqualityComparer<T> New additive-only public API: ReferenceEqualityComparer<T> in TUnit.Core.Helpers. No breaking changes — existing APIs unchanged. * fix: make ReferenceEqualityComparer<T> internal Only used internally by ClassHookContext — no need to expose as public API. Reverts the snapshot changes since the type no longer appears in the public API surface.
Configuration menu - View commit details
-
Copy full SHA for 28a1d96 - Browse repository at this point
Copy the full SHA 28a1d96View commit details -
+semver:minor - fix: enforce ParallelLimiter semaphore in TestRunner …
…to prevent DependsOn bypass (#5526) * fix: enforce ParallelLimiter semaphore in TestRunner to prevent DependsOn bypass (#5525) The parallel limiter semaphore was acquired in TestScheduler and ConstraintKeyScheduler, but TestRunner.ExecuteTestInternalAsync executes dependency tests via a direct recursive call that bypassed the semaphore entirely. When a test without [ParallelLimiter] but with [DependsOn] triggered its dependencies, those dependencies could run without holding a semaphore slot — exceeding the declared concurrency limit (e.g. peak=3 with Limit=2). Move semaphore acquisition into TestRunner.ExecuteTestInternalAsync, after dependency resolution but before the test body, so it applies regardless of entry point (scheduler or dependency recursion). * fix: reset static concurrency counters in Before(Class) hook Prevents stale s_peak from accumulating across re-runs in the same process (e.g. retry logic or IDE re-runs). * test: add check test with same limiter on dependent Covers the two-phase acquire path where the depending test shares the same ParallelLimiter as its dependencies.
Configuration menu - View commit details
-
Copy full SHA for 410fd39 - Browse repository at this point
Copy the full SHA 410fd39View commit details
This comparison is taking too long to generate.
Unfortunately it looks like we can’t render this comparison for you right now. It might be too big, or there might be something weird with your repository.
You can try running this command locally to see the comparison on your machine:
git diff v1.32.0...v1.33.0