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
6 changes: 0 additions & 6 deletions TUnit.Core/TestContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -341,12 +341,6 @@ internal override void SetAsyncLocalContext()

internal bool RunOnTestDiscovery { get; set; }

/// <summary>
/// Indicates whether this test is reusing the discovery-time instance instead of creating a new instance.
/// When true, property resolution and initialization should be skipped since the instance is already prepared.
/// </summary>
internal bool IsDiscoveryInstanceReused { get; set; }

/// <summary>
/// Gets a synchronization object that can be used for thread-safe operations within this test context.
/// </summary>
Expand Down
20 changes: 20 additions & 0 deletions TUnit.Engine.Tests/Issue6361Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Shouldly;
using TUnit.Engine.Tests.Enums;

namespace TUnit.Engine.Tests;

public class Issue6361Tests(TestMode testMode) : InvokableTestBase(testMode)
{
[Test]
public async Task Deferred_Instance_Method_Data_Source_Does_Not_Reuse_Enumeration_Instance()
{
await RunTestsWithFilter(
"/*/TUnit.TestProject.Bugs._6361/Issue6361InstanceMethodDataSourceIsolationTests/*",
[
result => result.ResultSummary.Outcome.ShouldBe("Completed"),
result => result.ResultSummary.Counters.Total.ShouldBe(4),
result => result.ResultSummary.Counters.Passed.ShouldBe(4),
result => result.ResultSummary.Counters.Failed.ShouldBe(0)
]);
}
}
3 changes: 1 addition & 2 deletions TUnit.Engine/Building/Interfaces/ITestBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@ internal interface ITestBuilder
/// <param name="metadata">The test metadata</param>
/// <param name="testData">The test data</param>
/// <param name="testBuilderContext"></param>
/// <param name="isReusingDiscoveryInstance">Whether this test is reusing the discovery instance</param>
/// <returns>An executable test ready for execution</returns>
Task<AbstractExecutableTest> BuildTestAsync(TestMetadata metadata, TestBuilder.TestData testData, TestBuilderContext testBuilderContext, bool isReusingDiscoveryInstance = false, CancellationToken cancellationToken = default);
Task<AbstractExecutableTest> BuildTestAsync(TestMetadata metadata, TestBuilder.TestData testData, TestBuilderContext testBuilderContext, CancellationToken cancellationToken = default);

/// <summary>
/// Builds all executable tests from a single TestMetadata using its DataCombinationGenerator delegate.
Expand Down
634 changes: 341 additions & 293 deletions TUnit.Engine/Building/TestBuilder.cs

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions TUnit.Engine/Services/PropertyInjector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@ public Task ResolveAndCachePropertiesAsync(
TestContext testContext,
CancellationToken cancellationToken = default)
{
// Even when the first data row reuses a discovery instance, this test still
// needs its own cached property values so shared fixtures get ref-counted.
// Resolve cached property values here so shared fixtures get ref-counted before execution.
var plan = PropertyInjectionCache.GetOrCreatePlan(testClassType);

if (!plan.HasProperties)
Expand Down
54 changes: 54 additions & 0 deletions TUnit.TestProject/Bugs/6361/Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System.Collections.Concurrent;
using TUnit.TestProject.Attributes;

namespace TUnit.TestProject.Bugs._6361;

[EngineTest(ExpectedResult.Pass)]
public sealed class Issue6361InstanceMethodDataSourceIsolationTests
{
private static readonly ConcurrentBag<int> TestInstanceIds = [];

private static int _nextInstanceId;
private static int _dataSourceInstanceId;

private readonly int _instanceId = Interlocked.Increment(ref _nextInstanceId);

[Test]
[MethodDataSource(nameof(GetCases), DeferEnumeration = true)]
public async Task InstanceMethodDataSource_DoesNotReuseEnumerationInstance(string value)
{
TestInstanceIds.Add(_instanceId);

await Assert.That(value).IsNotNullOrEmpty();
}

public IEnumerable<string> GetCases()
{
Interlocked.Exchange(ref _dataSourceInstanceId, _instanceId);

yield return "Case1";
yield return "Case2";
yield return "Case3";
}

[After(Class)]
public static async Task AssertEnumerationInstanceWasIsolated()
{
try
{
var testInstanceIds = TestInstanceIds.ToArray();
var dataSourceInstanceId = Volatile.Read(ref _dataSourceInstanceId);

await Assert.That(dataSourceInstanceId).IsNotEqualTo(0);
await Assert.That(testInstanceIds).Count().IsEqualTo(3);
await Assert.That(testInstanceIds.Distinct()).Count().IsEqualTo(3);
await Assert.That(testInstanceIds).DoesNotContain(dataSourceInstanceId);
}
finally
{
TestInstanceIds.Clear();
_nextInstanceId = 0;
_dataSourceInstanceId = 0;
}
}
}
3 changes: 1 addition & 2 deletions TUnit.UnitTests/PropertyInjectorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ namespace TUnit.UnitTests;
public class PropertyInjectorTests
{
[Test]
public async Task ReusedDiscoveryInstanceStillCachesInjectedProperties()
public async Task ResolveAndCacheProperties_CachesInjectedProperties()
{
var context = CreateContext<ReusedDiscoveryInstanceTestClass>();
context.IsDiscoveryInstanceReused = true;

var injector = new PropertyInjector(new Lazy<IInitializationCallback>(() => new PassthroughInitializationCallback()), "session");

Expand Down
Loading