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
2 changes: 1 addition & 1 deletion TUnit.Core/GenericTestMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public override Func<ExecutableTestCreationContext, TestMetadata, AbstractExecut
Func<TestContext, Task<object>> createInstance = async (testContext) =>
{
// Try to create instance with ClassConstructor attribute
var attributes = metadata.AttributeFactory();
var attributes = metadata.GetOrCreateAttributes();
var classInstance = await ClassConstructorHelper.TryCreateInstanceWithClassConstructor(
attributes,
TestClassType,
Expand Down
11 changes: 11 additions & 0 deletions TUnit.Core/TestMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@ public abstract class TestMetadata

public required Func<Attribute[]> AttributeFactory { get; init; }

private Attribute[]? _cachedAttributes;

/// <summary>
/// Returns the cached attributes array, creating it from <see cref="AttributeFactory"/> on first call.
/// Subsequent calls return the same array without re-invoking the factory.
/// </summary>
internal Attribute[] GetOrCreateAttributes()
{
return _cachedAttributes ??= AttributeFactory();
}

/// <summary>
/// Pre-extracted repeat count from RepeatAttribute.
/// Null if no repeat attribute is present (defaults to 0 at usage site).
Expand Down
2 changes: 1 addition & 1 deletion TUnit.Core/TestMetadata`1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public override Func<ExecutableTestCreationContext, TestMetadata, AbstractExecut
return await context.TestClassInstanceFactory();
}

var attributes = metadata.AttributeFactory();
var attributes = metadata.GetOrCreateAttributes();
var instance = await ClassConstructorHelper.TryCreateInstanceWithClassConstructor(
attributes,
TestClassType,
Expand Down
12 changes: 6 additions & 6 deletions TUnit.Engine/Building/TestBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ private async Task<object> CreateInstance(TestMetadata metadata, Type[] resolved

// First try to create instance with ClassConstructor attribute
// Use attributes from context if available
var attributes = builderContext.InitializedAttributes ?? metadata.AttributeFactory();
var attributes = builderContext.InitializedAttributes ?? metadata.GetOrCreateAttributes();

var instance = await ClassConstructorHelper.TryCreateInstanceWithClassConstructor(
attributes,
Expand Down Expand Up @@ -154,7 +154,7 @@ public async Task<IEnumerable<AbstractExecutableTest>> BuildTestsFromMetadataAsy
var repeatCount = metadata.RepeatCount ?? 0;

// Create and initialize attributes ONCE
var attributes = await InitializeAttributesAsync(metadata.AttributeFactory.Invoke());
var attributes = await InitializeAttributesAsync(metadata.GetOrCreateAttributes());

if (metadata.ClassDataSources.Any(ds => ds is IAccessesInstanceData))
{
Expand Down Expand Up @@ -1017,7 +1017,7 @@ private static void CollectAllDependencies(AbstractExecutableTest test, HashSet<
/// </summary>
private static string? GetBasicSkipReason(TestMetadata metadata, Attribute[]? cachedAttributes = null)
{
var attributes = cachedAttributes ?? metadata.AttributeFactory();
var attributes = cachedAttributes ?? metadata.GetOrCreateAttributes();

SkipAttribute? firstSkipAttribute = null;

Expand Down Expand Up @@ -1047,7 +1047,7 @@ private static void CollectAllDependencies(AbstractExecutableTest test, HashSet<
private async ValueTask<TestContext> CreateTestContextAsync(string testId, TestMetadata metadata, TestData testData, TestBuilderContext testBuilderContext)
{
// Use attributes from context if available, or create new ones
var attributes = testBuilderContext.InitializedAttributes ?? await InitializeAttributesAsync(metadata.AttributeFactory.Invoke());
var attributes = testBuilderContext.InitializedAttributes ?? await InitializeAttributesAsync(metadata.GetOrCreateAttributes());

if (testBuilderContext.DataSourceAttribute != null && testBuilderContext.DataSourceAttribute is not NoDataSource)
{
Expand Down Expand Up @@ -1138,7 +1138,7 @@ private async Task<AbstractExecutableTest> CreateFailedTestForDataGenerationErro

private async Task<TestDetails> CreateFailedTestDetails(TestMetadata metadata, string testId)
{
var attributes = (await InitializeAttributesAsync(metadata.AttributeFactory.Invoke()));
var attributes = (await InitializeAttributesAsync(metadata.GetOrCreateAttributes()));
return new TestDetails(attributes)
{
TestId = testId,
Expand Down Expand Up @@ -1524,7 +1524,7 @@ public async IAsyncEnumerable<AbstractExecutableTest> BuildTestsStreamingAsync(
var repeatCount = metadata.RepeatCount ?? 0;

// Initialize attributes
var attributes = await InitializeAttributesAsync(metadata.AttributeFactory.Invoke());
var attributes = await InitializeAttributesAsync(metadata.GetOrCreateAttributes());

// Create base context with ClassConstructor if present
// StateBag and Events are lazy-initialized for performance
Expand Down
6 changes: 3 additions & 3 deletions TUnit.Engine/Building/TestBuilderPipeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ private TestBuilderContext CreateTestBuilderContext(TestMetadata metadata)
};

// Check for ClassConstructor attribute and set it early if present
var attributes = metadata.AttributeFactory();
var attributes = metadata.GetOrCreateAttributes();

// Look for any attribute that inherits from ClassConstructorAttribute
// This handles both ClassConstructorAttribute and ClassConstructorAttribute<T>
Expand Down Expand Up @@ -246,7 +246,7 @@ private async Task<AbstractExecutableTest[]> GenerateDynamicTests(TestMetadata m
: baseDisplayName;

// Get attributes first
var attributes = metadata.AttributeFactory();
var attributes = metadata.GetOrCreateAttributes();

// Create TestDetails for dynamic tests
var testDetails = new TestDetails(attributes)
Expand Down Expand Up @@ -345,7 +345,7 @@ private async IAsyncEnumerable<AbstractExecutableTest> BuildTestsFromSingleMetad
var repeatCount = resolvedMetadata.RepeatCount ?? 0;

// Get attributes for test details
var attributes = resolvedMetadata.AttributeFactory?.Invoke() ?? [];
var attributes = resolvedMetadata.GetOrCreateAttributes();

// Dynamic tests need to honor attributes like RepeatCount, RetryCount, etc.
// We'll create multiple test instances based on RepeatCount
Expand Down
Loading