Tags: thomhurst/TUnit
Tags
Detect async void lambdas in AsyncVoidAnalyzer (TUnit0031) (#4758) * Initial plan * Enhance AsyncVoidAnalyzer to detect async void lambdas and anonymous delegates Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com>
feat: support assembly-level [assembly: Repeat(N)] attribute (#4753) RepeatAttribute declares AttributeTargets.Assembly but neither the source generator nor the reflection engine checked assembly-level attributes. This adds assembly as the lowest-precedence fallback (method > class > assembly) in both ExtractRepeatCount overloads and the reflection discovery path. Also tighten all RepeatAttribute checks in the source generator to verify the attribute's containing namespace is TUnit.Core, preventing false matches against user-defined attributes with the same name. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
fix: disable IDE streaming sink by default to prevent test runner cra… …shes (#4751) The IdeStreamingSink causes crashes in Rider and VS Code due to the Microsoft Testing Platform's TestApplicationResult.ConsumeAsync throwing on duplicate TestNodeUid keys. Gate the feature behind the TUNIT_ENABLE_IDE_STREAMING=1 environment variable so users can opt in. Closes #4749 Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
fix: prevent test hangs when IAsyncDiscoveryInitializer partially ini… …tializes then throws (#4715) (#4746) When InitializeAsync() starts resources (e.g. ports, background processes) then throws, TUnit was removing the failed entry from its initialization caches to allow retry. The retry calls InitializeAsync() again on the same partially-initialized shared object, which hangs because the first resources still hold ports/processes. - Stop removing failed/cancelled entries from ObjectInitializer and ObjectLifecycleService caches so the faulted task is returned immediately to subsequent callers - Fix fast-path in ObjectLifecycleService to also check IsCanceled so cancelled initializations don't silently return uninitialized objects - Add 5-minute discovery timeout to the filter execution path matching the existing streaming path timeout - Add engine test for attribute-based property injection failure scenario Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
fix: evaluate SkipAttribute before data source initialization (#4737) (… …#4743) Derived SkipAttribute subclasses (e.g., skip-in-CI attributes) set SkipReason during OnTestRegistered, but the execution path called CreateInstanceAsync() — triggering expensive IAsyncInitializer calls — before checking SkipReason. If initialization threw (e.g., no database in CI), the test was marked as failed instead of skipped. Three layered fixes: 1. TestFilterService.RegisterTest: fire OnTestRegistered event receivers BEFORE RegisterTestArgumentsAsync, and skip argument registration entirely when SkipReason is set. This prevents ClassDataSource initialization for skipped tests during the registration phase. 2. TestCoordinator.ExecuteTestInternalAsync: check SkipReason before entering retry/timeout logic and instance creation. 3. TestCoordinator.ExecuteTestLifecycleAsync: check SkipReason before CreateInstanceAsync() to prevent data source initialization that would fail or waste resources for skipped tests. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
fix: consistent test time tracking - exclude Before/After hooks from … …headline duration (#4723) Before hooks were already excluded from test duration (TestStart set after Before hooks), but After hooks were included (TestEnd only set after After hooks completed). This made reported test times inconsistent and inflated. Now TestEnd is set immediately after the test body finishes (before After hooks run), and both Before/After test-level hooks are recorded as step timings via Timings.Record() for granular visibility. EndTime assignments in TestRunner, TestStateManager use ??= to respect the body-level TestEnd. Timings are cleared on retry and re-execution. Closes #4711 Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
fix: prevent After(TestSession) hook from firing prematurely on test … …timeout (#4720) * fix: prevent After(TestSession) hook from firing prematurely on test timeout When a test timed out, the cancellation callback registered by AfterHookPairTracker would fire the session-level After hooks immediately — even while other tests were still running. Additionally, TestScheduler called HookExecutor directly, bypassing the tracker's deduplication, causing hooks to run a second time. Fix by: (1) adding a first-call-wins guard so only the first RegisterAfterTestSessionHook call registers a cancellation callback (the one from TestCoordinator with the session-level token, not per-test timeout tokens), and (2) routing TestScheduler through AfterHookPairTracker.GetOrCreateAfterTestSessionTask instead of calling HookExecutor directly. Closes #4712 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * perf: avoid redundant ValueTask-Task-ValueTask conversion in TestScheduler Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
fix: prevent tests hanging when InitializeAsync throws in chained Cla… …ssDataSource dependencies (#4715) (#4717) Three bugs combined to cause indefinite hangs when IAsyncDiscoveryInitializer.InitializeAsync() throws: 1. CreateFailedTestDetails called InitializeAttributesAsync — the exact method that caused the original exception — re-triggering the failure inside the catch block. Made it synchronous with empty attributes matching the safe TestBuilderPipeline pattern. 2. FailedExecutableTest in TestBuilder was missing State=Failed and Result, so failed tests didn't get the early-exit in TestCoordinator.ExecuteTestInternalAsync. 3. The 5-minute discovery timeout CTS was created but the original cancellationToken was passed instead of cts.Token, so the timeout was never applied. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
PreviousNext