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
21 changes: 18 additions & 3 deletions TUnit.Core/TestBuilderContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace TUnit.Core;
public record TestBuilderContext
{
private static readonly AsyncLocal<TestBuilderContext?> BuilderContexts = new();
private string? _definitionId;

/// <summary>
/// Gets the current test builder context.
Expand All @@ -19,17 +20,31 @@ public static TestBuilderContext? Current
internal set => BuilderContexts.Value = value;
}

public string DefinitionId { get; } = Guid.NewGuid().ToString();
/// <summary>
/// Gets the unique definition ID for this context. Generated lazily on first access.
/// </summary>
public string DefinitionId => _definitionId ??= Guid.NewGuid().ToString();

[Obsolete("Use StateBag property instead.")]
public ConcurrentDictionary<string, object?> ObjectBag => StateBag;

private ConcurrentDictionary<string, object?>? _stateBag;
private TestContextEvents? _events;

/// <summary>
/// Gets the state bag for storing arbitrary data during test building.
/// </summary>
public ConcurrentDictionary<string, object?> StateBag { get; set; } = new();
public ConcurrentDictionary<string, object?> StateBag
{
get => _stateBag ??= new ConcurrentDictionary<string, object?>();
set => _stateBag = value;
}

public TestContextEvents Events { get; set; } = new();
public TestContextEvents Events
{
get => _events ??= new TestContextEvents();
set => _events = value;
}

public IDataSourceAttribute? DataSourceAttribute { get; set; }

Expand Down
19 changes: 7 additions & 12 deletions TUnit.Engine/Building/TestBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,11 +162,10 @@ public async Task<IEnumerable<AbstractExecutableTest>> BuildTestsFromMetadataAsy
}

// Create a single context accessor that we'll reuse, updating its Current property for each test
// StateBag and Events are lazy-initialized, so we don't need to pre-allocate them
var testBuilderContext = new TestBuilderContext
{
TestMetadata = metadata.MethodMetadata,
Events = new TestContextEvents(),
StateBag = new ConcurrentDictionary<string, object?>(),
InitializedAttributes = attributes // Store the initialized attributes
};

Expand Down Expand Up @@ -309,11 +308,10 @@ await _objectLifecycleService.RegisterObjectAsync(
for (var i = 0; i < repeatCount + 1; i++)
{
// Update context BEFORE calling data factories so they track objects in the right context
// StateBag and Events are lazy-initialized for performance
contextAccessor.Current = new TestBuilderContext
{
TestMetadata = metadata.MethodMetadata,
Events = new TestContextEvents(),
StateBag = new ConcurrentDictionary<string, object?>(),
DataSourceAttribute = methodDataSource,
InitializedAttributes = testBuilderContext.InitializedAttributes, // Preserve attributes from parent context
ClassConstructor = testBuilderContext.ClassConstructor // Preserve ClassConstructor for instance creation
Expand Down Expand Up @@ -443,10 +441,10 @@ await _objectLifecycleService.RegisterObjectAsync(
Metadata = finalMetadata
};

// Events is lazy-initialized; explicitly share StateBag from per-iteration context
var testSpecificContext = new TestBuilderContext
{
TestMetadata = metadata.MethodMetadata,
Events = new TestContextEvents(),
StateBag = contextAccessor.Current.StateBag,
ClassConstructor = testBuilderContext.ClassConstructor,
DataSourceAttribute = contextAccessor.Current.DataSourceAttribute,
Expand Down Expand Up @@ -508,11 +506,10 @@ await _objectLifecycleService.RegisterObjectAsync(
ResolvedMethodGenericArguments = Type.EmptyTypes
};

// StateBag and Events are lazy-initialized
var testSpecificContext = new TestBuilderContext
{
TestMetadata = metadata.MethodMetadata,
Events = new TestContextEvents(),
StateBag = new ConcurrentDictionary<string, object?>(),
ClassConstructor = testBuilderContext.ClassConstructor,
DataSourceAttribute = methodDataSource,
InitializedAttributes = attributes
Expand Down Expand Up @@ -568,11 +565,10 @@ await _objectLifecycleService.RegisterObjectAsync(
ResolvedMethodGenericArguments = Type.EmptyTypes
};

// StateBag and Events are lazy-initialized
var testSpecificContext = new TestBuilderContext
{
TestMetadata = metadata.MethodMetadata,
Events = new TestContextEvents(),
StateBag = new ConcurrentDictionary<string, object?>(),
ClassConstructor = testBuilderContext.ClassConstructor,
DataSourceAttribute = classDataSource,
InitializedAttributes = attributes
Expand Down Expand Up @@ -1428,11 +1424,10 @@ public async IAsyncEnumerable<AbstractExecutableTest> BuildTestsStreamingAsync(
var attributes = await InitializeAttributesAsync(metadata.AttributeFactory.Invoke());

// Create base context with ClassConstructor if present
// StateBag and Events are lazy-initialized for performance
var baseContext = new TestBuilderContext
{
TestMetadata = metadata.MethodMetadata,
Events = new TestContextEvents(),
StateBag = new ConcurrentDictionary<string, object?>(),
InitializedAttributes = attributes // Store the initialized attributes
};

Expand Down Expand Up @@ -1732,10 +1727,10 @@ private Task<InstanceCreationResult> CreateInstanceForMethodDataSources(
Metadata = finalMetadata
};

// Events is lazy-initialized; explicitly share StateBag from per-iteration context
var testSpecificContext = new TestBuilderContext
{
TestMetadata = metadata.MethodMetadata,
Events = new TestContextEvents(),
StateBag = contextAccessor.Current.StateBag,
ClassConstructor = contextAccessor.Current.ClassConstructor,
DataSourceAttribute = contextAccessor.Current.DataSourceAttribute,
Expand Down
Loading