Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions TUnit.Core/ObjectInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,10 @@ private static async ValueTask InitializeCoreAsync(
}
catch
{
// Remove failed initialization from cache to allow retry
// This is important for transient failures that may succeed on retry
InitializationTasks.TryRemove(obj, out _);
// Do NOT remove from cache - the faulted Lazy<Task> stays so subsequent
// callers get the same error immediately via .WaitAsync() on the faulted task.
// Removing and retrying can cause hangs when InitializeAsync partially initialized
// resources (e.g. started ports/processes) that block re-initialization (#4715).
throw;
}
}
Expand Down
21 changes: 21 additions & 0 deletions TUnit.Engine.Tests/AttributePropertyInjectionFailureTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Shouldly;
using TUnit.Engine.Tests.Enums;

namespace TUnit.Engine.Tests;

public class AttributePropertyInjectionFailureTests(TestMode testMode) : InvokableTestBase(testMode)
{
[Test]
public async Task Test()
{
await RunTestsWithFilter(
"/*/*/AttributePropertyInjectionFailureTests/*",
[
result => result.ResultSummary.Outcome.ShouldBe("Failed"),
result => result.ResultSummary.Counters.Total.ShouldBe(1),
result => result.ResultSummary.Counters.Passed.ShouldBe(0),
result => result.ResultSummary.Counters.Failed.ShouldBe(1),
result => result.ResultSummary.Counters.NotExecuted.ShouldBe(0)
]);
}
}
13 changes: 7 additions & 6 deletions TUnit.Engine/Services/ObjectLifecycleService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ public async ValueTask<T> EnsureInitializedAsync<T>(
// Fast path: already processed by this service
if (_initializationTasks.TryGetValue(obj, out var existingTcs) && existingTcs.Task.IsCompleted)
{
if (existingTcs.Task.IsFaulted)
if (existingTcs.Task.IsFaulted || existingTcs.Task.IsCanceled)
{
await existingTcs.Task.ConfigureAwait(false);
}
Expand All @@ -353,16 +353,17 @@ public async ValueTask<T> EnsureInitializedAsync<T>(
}
catch (OperationCanceledException)
{
// Propagate cancellation without caching failure - allows retry after cancel
_initializationTasks.TryRemove(obj, out _);
// Do NOT remove from cache - the cancelled TCS stays so subsequent
// callers get the cancellation immediately. Retrying can cause hangs
// when InitializeAsync partially initialized resources (#4715).
tcs.SetCanceled();
throw;
}
catch (Exception ex)
{
// Remove failed initialization from cache to allow retry
// This is important for transient failures that may succeed on retry
_initializationTasks.TryRemove(obj, out _);
// Do NOT remove from cache - the faulted TCS stays so subsequent
// callers get the same error immediately. Retrying can cause hangs
// when InitializeAsync partially initialized resources (#4715).
tcs.SetException(ex);
throw;
}
Expand Down
6 changes: 5 additions & 1 deletion TUnit.Engine/TestDiscoveryService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,15 @@ public async Task<TestDiscoveryResult> DiscoverTests(string testSessionId, ITest
var metadataToInclude = _dependencyExpander.ExpandToIncludeDependencies(allMetadataList, filter);

// Build tests directly from the pre-collected metadata (avoid re-collecting)
// Apply 5-minute discovery timeout matching the streaming path (#4715)
using var filterCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
filterCts.CancelAfter(TimeSpan.FromMinutes(5));

var buildingContext = new Building.TestBuildingContext(isForExecution, Filter: null);
var tests = await _testBuilderPipeline.BuildTestsFromMetadataAsync(
metadataToInclude,
buildingContext,
cancellationToken).ConfigureAwait(false);
filterCts.Token).ConfigureAwait(false);

var testsList = tests.ToList();

Expand Down
Loading