diff --git a/TUnit.Core/TestDetails.Metadata.cs b/TUnit.Core/TestDetails.Metadata.cs index fac756338c..d5f2e15437 100644 --- a/TUnit.Core/TestDetails.Metadata.cs +++ b/TUnit.Core/TestDetails.Metadata.cs @@ -14,4 +14,5 @@ public partial class TestDetails bool ITestDetailsMetadata.HasAttribute() => HasAttribute(); IEnumerable ITestDetailsMetadata.GetAttributes() => GetAttributes(); IReadOnlyList ITestDetailsMetadata.GetAllAttributes() => GetAllAttributes(); + } diff --git a/TUnit.Core/TestDetails.cs b/TUnit.Core/TestDetails.cs index b10daeb267..4227e5b81c 100644 --- a/TUnit.Core/TestDetails.cs +++ b/TUnit.Core/TestDetails.cs @@ -9,6 +9,8 @@ namespace TUnit.Core; /// public partial class TestDetails : ITestIdentity, ITestClass, ITestMethod, ITestConfiguration, ITestLocation, ITestDetailsMetadata { + private readonly IReadOnlyList _allAttributes; + // Zero-allocation interface properties for organized API access public ITestIdentity Identity => this; public ITestClass Class => this; @@ -40,19 +42,9 @@ public partial class TestDetails : ITestIdentity, ITestClass, ITestMethod, ITest public required IReadOnlyDictionary> AttributesByType { get; init; } - private readonly Lazy> _cachedAllAttributes; - - public TestDetails() + public TestDetails(IReadOnlyList allAttributes) { - _cachedAllAttributes = new Lazy>(() => - { - var allAttrs = new List(); - foreach (var attrList in AttributesByType?.Values ?? []) - { - allAttrs.AddRange(attrList); - } - return allAttrs; - }); + _allAttributes = allAttributes; } /// @@ -75,10 +67,9 @@ public IEnumerable GetAttributes() where T : Attribute /// /// Gets all attributes as a flattened collection. - /// Cached after first access for performance. /// /// All attributes associated with this test. - public IReadOnlyList GetAllAttributes() => _cachedAllAttributes.Value; + public IReadOnlyList GetAllAttributes() => _allAttributes; /// /// Resolved generic type arguments for the test method. @@ -96,4 +87,4 @@ public IEnumerable GetAttributes() where T : Attribute /// /// Generic version of TestDetails for compatibility with tests /// -public class TestDetails : TestDetails where T : class; +public class TestDetails(IReadOnlyList allAttributes) : TestDetails(allAttributes) where T : class; diff --git a/TUnit.Engine/Building/TestBuilder.cs b/TUnit.Engine/Building/TestBuilder.cs index ffb901e5dc..06cec57a2a 100644 --- a/TUnit.Engine/Building/TestBuilder.cs +++ b/TUnit.Engine/Building/TestBuilder.cs @@ -978,7 +978,7 @@ private async ValueTask CreateTestContextAsync(string testId, TestM attributes = [..attributes, (Attribute)testBuilderContext.DataSourceAttribute]; } - var testDetails = new TestDetails + var testDetails = new TestDetails(attributes) { TestId = testId, TestName = metadata.TestName, @@ -1074,7 +1074,8 @@ private async Task CreateFailedTestForDataGenerationErro private async Task CreateFailedTestDetails(TestMetadata metadata, string testId) { - return new TestDetails + var attributes = (await InitializeAttributesAsync(metadata.AttributeFactory.Invoke())); + return new TestDetails(attributes) { TestId = testId, TestName = metadata.TestName, @@ -1087,7 +1088,7 @@ private async Task CreateFailedTestDetails(TestMetadata metadata, s TestLineNumber = metadata.LineNumber, ReturnType = typeof(Task), MethodMetadata = metadata.MethodMetadata, - AttributesByType = (await InitializeAttributesAsync(metadata.AttributeFactory.Invoke())).ToAttributeDictionary(), + AttributesByType = attributes.ToAttributeDictionary(), Timeout = TimeSpan.FromMinutes(30) // Default 30-minute timeout (can be overridden by TimeoutAttribute) }; } diff --git a/TUnit.Engine/Building/TestBuilderPipeline.cs b/TUnit.Engine/Building/TestBuilderPipeline.cs index 23884ba399..54b3dae40f 100644 --- a/TUnit.Engine/Building/TestBuilderPipeline.cs +++ b/TUnit.Engine/Building/TestBuilderPipeline.cs @@ -249,7 +249,7 @@ private async Task GenerateDynamicTests(TestMetadata m var attributes = metadata.AttributeFactory(); // Create TestDetails for dynamic tests - var testDetails = new TestDetails + var testDetails = new TestDetails(attributes) { TestId = testId, TestName = metadata.TestName, @@ -377,7 +377,7 @@ private async IAsyncEnumerable BuildTestsFromSingleMetad : baseDisplayName; // Create TestDetails for dynamic tests - var testDetails = new TestDetails + var testDetails = new TestDetails(attributes) { TestId = testId, TestName = resolvedMetadata.TestName, @@ -457,7 +457,7 @@ private AbstractExecutableTest CreateFailedTestForDataGenerationError(TestMetada var testId = TestIdentifierService.GenerateFailedTestId(metadata); var displayName = $"{metadata.TestClassType.Name}.{metadata.TestName}"; - var testDetails = new TestDetails + var testDetails = new TestDetails([]) { TestId = testId, TestName = metadata.TestName, @@ -509,7 +509,7 @@ private AbstractExecutableTest CreateFailedTestForGenericResolutionError(TestMet var testId = TestIdentifierService.GenerateFailedTestId(metadata); var displayName = $"{metadata.TestName} [GENERIC RESOLUTION ERROR]"; - var testDetails = new TestDetails + var testDetails = new TestDetails([]) { TestId = testId, TestName = metadata.TestName, diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet10_0.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet10_0.verified.txt index e9f46064a1..a37b740d48 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet10_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet10_0.verified.txt @@ -1420,7 +1420,7 @@ namespace } public class TestDetails : ., ., ., ., ., . { - public TestDetails() { } + public TestDetails(.<> allAttributes) { } public . Attributes { get; } public required .<, .<>> AttributesByType { get; init; } public . Categories { get; } @@ -1457,7 +1457,7 @@ namespace public class TestDetails : .TestDetails where T : class { - public TestDetails() { } + public TestDetails(.<> allAttributes) { } } public class TestDiscoveryContext : .Context { diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt index 8cc53af1a3..f6dc6eb0b9 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt @@ -1420,7 +1420,7 @@ namespace } public class TestDetails : ., ., ., ., ., . { - public TestDetails() { } + public TestDetails(.<> allAttributes) { } public . Attributes { get; } public required .<, .<>> AttributesByType { get; init; } public . Categories { get; } @@ -1457,7 +1457,7 @@ namespace public class TestDetails : .TestDetails where T : class { - public TestDetails() { } + public TestDetails(.<> allAttributes) { } } public class TestDiscoveryContext : .Context { diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt index ab0af14524..33bedbec01 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt @@ -1420,7 +1420,7 @@ namespace } public class TestDetails : ., ., ., ., ., . { - public TestDetails() { } + public TestDetails(.<> allAttributes) { } public . Attributes { get; } public required .<, .<>> AttributesByType { get; init; } public . Categories { get; } @@ -1457,7 +1457,7 @@ namespace public class TestDetails : .TestDetails where T : class { - public TestDetails() { } + public TestDetails(.<> allAttributes) { } } public class TestDiscoveryContext : .Context { diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt index 0b1701f10a..220dd7716f 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt @@ -1375,7 +1375,7 @@ namespace } public class TestDetails : ., ., ., ., ., . { - public TestDetails() { } + public TestDetails(.<> allAttributes) { } public . Attributes { get; } public required .<, .<>> AttributesByType { get; init; } public . Categories { get; } @@ -1411,7 +1411,7 @@ namespace public class TestDetails : .TestDetails where T : class { - public TestDetails() { } + public TestDetails(.<> allAttributes) { } } public class TestDiscoveryContext : .Context {