Skip to content

Commit 00c2ef9

Browse files
authored
feat: add DynamicTestIndex property for unique test ID generation in dynamic tests (#4056)
* feat: add DynamicTestIndex property for unique test ID generation in dynamic tests * feat: implement DynamicTestIndexTests to validate unique test ID generation for dynamic tests
1 parent 4b6f5a3 commit 00c2ef9

13 files changed

Lines changed: 121 additions & 8 deletions

TUnit.Core/AbstractDynamicTest.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,23 @@ public class DynamicDiscoveryResult : DiscoveryResult
4747
public Dictionary<string, object?>? Properties { get; set; }
4848

4949
public string? DisplayName { get; set; }
50+
51+
/// <summary>
52+
/// Unique index for this dynamic test within its builder context.
53+
/// Used to generate unique test IDs when multiple dynamic tests target the same method.
54+
/// </summary>
55+
public int DynamicTestIndex { get; set; }
5056
}
5157

5258
public abstract class AbstractDynamicTest
5359
{
5460
public abstract IEnumerable<DiscoveryResult> GetTests();
61+
62+
/// <summary>
63+
/// Unique index for this dynamic test within its builder context.
64+
/// Used to generate unique test IDs when multiple dynamic tests target the same method.
65+
/// </summary>
66+
public int DynamicTestIndex { get; set; }
5567
}
5668

5769
public abstract class AbstractDynamicTest<[DynamicallyAccessedMembers(
@@ -99,7 +111,8 @@ public override IEnumerable<DiscoveryResult> GetTests()
99111
Attributes = Attributes,
100112
TestClassType = typeof(T),
101113
CreatorFilePath = CreatorFilePath,
102-
CreatorLineNumber = CreatorLineNumber
114+
CreatorLineNumber = CreatorLineNumber,
115+
DynamicTestIndex = DynamicTestIndex
103116
};
104117

105118
yield return result;

TUnit.Core/DynamicTestBuilderContext.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ public class DynamicTestBuilderContext
1111
[
1212
];
1313

14+
private int _nextIndex = 1; // Start at 1 to match loop index convention used by regular data sources
15+
1416
public DynamicTestBuilderContext(string filePath, int lineNumber)
1517
{
1618
FilePath = filePath;
@@ -34,6 +36,9 @@ public void AddTest(AbstractDynamicTest test)
3436
testWithLocation.CreatorLineNumber = LineNumber;
3537
}
3638

39+
// Assign unique index for test ID generation
40+
test.DynamicTestIndex = _nextIndex++;
41+
3742
_tests.Add(test);
3843
}
3944
}

TUnit.Core/IDynamicTestMetadata.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
namespace TUnit.Core;
22

33
/// <summary>
4-
/// Marker interface for dynamic test metadata that should bypass normal data source processing
4+
/// Interface for dynamic test metadata that should bypass normal data source processing
55
/// </summary>
66
public interface IDynamicTestMetadata
77
{
8+
/// <summary>
9+
/// Unique index for this dynamic test within its builder context.
10+
/// Used to generate unique test IDs when multiple dynamic tests target the same method.
11+
/// </summary>
12+
int DynamicTestIndex { get; }
813
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using Shouldly;
2+
using TUnit.Engine.Tests.Enums;
3+
4+
namespace TUnit.Engine.Tests;
5+
6+
/// <summary>
7+
/// Tests that validate DynamicTestIndex generates unique test IDs
8+
/// when multiple dynamic tests target the same method.
9+
/// </summary>
10+
public class DynamicTestIndexTests(TestMode testMode) : InvokableTestBase(testMode)
11+
{
12+
[Test]
13+
public async Task Test()
14+
{
15+
await RunTestsWithFilter(
16+
"/*/*DynamicTests/DynamicTestIndexTests/*",
17+
[
18+
result => result.ResultSummary.Outcome.ShouldBe("Completed"),
19+
result => result.ResultSummary.Counters.Total.ShouldBe(5),
20+
result => result.ResultSummary.Counters.Passed.ShouldBe(5),
21+
result => result.ResultSummary.Counters.Failed.ShouldBe(0)
22+
]);
23+
}
24+
}

TUnit.Engine/Building/Collectors/AotTestDataCollector.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,8 @@ private static MethodMetadata CreateDummyMethodMetadata(Type type, string method
298298

299299
private sealed class AotDynamicTestMetadata(DynamicDiscoveryResult dynamicResult) : TestMetadata, IDynamicTestMetadata
300300
{
301+
public int DynamicTestIndex => dynamicResult.DynamicTestIndex;
302+
301303
public override Func<ExecutableTestCreationContext, TestMetadata, AbstractExecutableTest> CreateExecutableTestFactory
302304
{
303305
get => (context, metadata) =>

TUnit.Engine/Building/TestBuilderPipeline.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,14 +152,16 @@ private async Task<AbstractExecutableTest[]> GenerateDynamicTests(TestMetadata m
152152
.SelectAsync(async repeatIndex =>
153153
{
154154
// Create a simple TestData for ID generation
155+
// Use DynamicTestIndex from the metadata to ensure unique test IDs for multiple dynamic tests
156+
var dynamicTestIndex = metadata is IDynamicTestMetadata dynMeta ? dynMeta.DynamicTestIndex : 0;
155157
var testData = new TestBuilder.TestData
156158
{
157159
TestClassInstanceFactory = () => Task.FromResult(metadata.InstanceFactory(Type.EmptyTypes, [])),
158160
ClassDataSourceAttributeIndex = 0,
159161
ClassDataLoopIndex = 0,
160162
ClassData = [],
161163
MethodDataSourceAttributeIndex = 0,
162-
MethodDataLoopIndex = 0,
164+
MethodDataLoopIndex = dynamicTestIndex,
163165
MethodData = [],
164166
RepeatIndex = repeatIndex,
165167
InheritanceDepth = metadata.InheritanceDepth,
@@ -271,6 +273,8 @@ private async IAsyncEnumerable<AbstractExecutableTest> BuildTestsFromSingleMetad
271273

272274
// Dynamic tests need to honor attributes like RepeatCount, RetryCount, etc.
273275
// We'll create multiple test instances based on RepeatCount
276+
// Use DynamicTestIndex from the metadata to ensure unique test IDs for multiple dynamic tests
277+
var dynamicTestIndex = ((IDynamicTestMetadata)resolvedMetadata).DynamicTestIndex;
274278
for (var repeatIndex = 0; repeatIndex < repeatCount + 1; repeatIndex++)
275279
{
276280
// Create a simple TestData for ID generation
@@ -281,7 +285,7 @@ private async IAsyncEnumerable<AbstractExecutableTest> BuildTestsFromSingleMetad
281285
ClassDataLoopIndex = 0,
282286
ClassData = [],
283287
MethodDataSourceAttributeIndex = 0,
284-
MethodDataLoopIndex = 0,
288+
MethodDataLoopIndex = dynamicTestIndex,
285289
MethodData = [],
286290
RepeatIndex = repeatIndex,
287291
InheritanceDepth = resolvedMetadata.InheritanceDepth,

TUnit.Engine/Discovery/ReflectionTestDataCollector.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2108,6 +2108,8 @@ public DynamicReflectionTestMetadata(
21082108
_dynamicResult = dynamicResult;
21092109
}
21102110

2111+
public int DynamicTestIndex => _dynamicResult.DynamicTestIndex;
2112+
21112113
public override Func<ExecutableTestCreationContext, TestMetadata, AbstractExecutableTest> CreateExecutableTestFactory
21122114
{
21132115
get => (context, metadata) =>

TUnit.Engine/Services/TestRegistry.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,8 @@ public RuntimeDynamicTestMetadata(Type testClass, MethodInfo testMethod, Dynamic
353353
_dynamicResult = dynamicResult;
354354
}
355355

356+
public int DynamicTestIndex => _dynamicResult.DynamicTestIndex;
357+
356358
public override Func<ExecutableTestCreationContext, TestMetadata, AbstractExecutableTest> CreateExecutableTestFactory
357359
{
358360
get => (context, metadata) =>

TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet10_0.verified.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace
66
public abstract class AbstractDynamicTest
77
{
88
protected AbstractDynamicTest() { }
9+
public int DynamicTestIndex { get; set; }
910
public abstract .<.DiscoveryResult> GetTests();
1011
}
1112
public abstract class AbstractDynamicTest<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicFields | ..NonPublicFields | ..PublicProperties)] T> : .AbstractDynamicTest
@@ -617,6 +618,7 @@ namespace
617618
public string? CreatorFilePath { get; set; }
618619
public int? CreatorLineNumber { get; set; }
619620
public string? DisplayName { get; set; }
621+
public int DynamicTestIndex { get; set; }
620622
public string? ParentTestId { get; set; }
621623
public .<string, object?>? Properties { get; set; }
622624
public .? Relationship { get; set; }
@@ -853,7 +855,10 @@ namespace
853855
string? CreatorFilePath { get; set; }
854856
int? CreatorLineNumber { get; set; }
855857
}
856-
public interface IDynamicTestMetadata { }
858+
public interface IDynamicTestMetadata
859+
{
860+
int DynamicTestIndex { get; }
861+
}
857862
public interface IDynamicTestSource
858863
{
859864
.<.AbstractDynamicTest> CollectDynamicTests(string sessionId);

TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace
66
public abstract class AbstractDynamicTest
77
{
88
protected AbstractDynamicTest() { }
9+
public int DynamicTestIndex { get; set; }
910
public abstract .<.DiscoveryResult> GetTests();
1011
}
1112
public abstract class AbstractDynamicTest<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicFields | ..NonPublicFields | ..PublicProperties)] T> : .AbstractDynamicTest
@@ -617,6 +618,7 @@ namespace
617618
public string? CreatorFilePath { get; set; }
618619
public int? CreatorLineNumber { get; set; }
619620
public string? DisplayName { get; set; }
621+
public int DynamicTestIndex { get; set; }
620622
public string? ParentTestId { get; set; }
621623
public .<string, object?>? Properties { get; set; }
622624
public .? Relationship { get; set; }
@@ -853,7 +855,10 @@ namespace
853855
string? CreatorFilePath { get; set; }
854856
int? CreatorLineNumber { get; set; }
855857
}
856-
public interface IDynamicTestMetadata { }
858+
public interface IDynamicTestMetadata
859+
{
860+
int DynamicTestIndex { get; }
861+
}
857862
public interface IDynamicTestSource
858863
{
859864
.<.AbstractDynamicTest> CollectDynamicTests(string sessionId);

0 commit comments

Comments
 (0)