Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
21b7fb8
Initial plan
Copilot Aug 11, 2025
1860e84
Fix disposal issue by triggering OnDispose events in test execution
Copilot Aug 11, 2025
1710baf
Remove problematic test file and finalize disposal fix
Copilot Aug 11, 2025
75fc090
Remove dead code: ExtractTimeout, ExtractRetryCount, ExtractRepeatCount
thomhurst Aug 11, 2025
3004b59
Add timeout protection to OnDispose event invocation
thomhurst Aug 11, 2025
6e1ac4c
Fix hanging issue by preventing duplicate disposal handlers registration
Copilot Aug 12, 2025
35c5f2d
Merge branch 'main' into copilot/fix-2867
thomhurst Aug 12, 2025
644b9ca
Fix disposal hanging by removing disposal calls from OnDispose handlers
claude[bot] Aug 12, 2025
9fc1edd
Implement proper reference counting with disposal in ObjectTracker
claude[bot] Aug 12, 2025
7092303
Fix premature disposal of shared objects
claude[bot] Aug 12, 2025
5d6683b
Implement reference counting based disposal system
claude[bot] Aug 12, 2025
2e714d7
Fix disposal order to prevent ObjectDisposedException
claude[bot] Aug 12, 2025
7828979
Fix shared object disposal tracking to ensure all test contexts track…
claude[bot] Aug 12, 2025
e5da8c2
Fix ObjectDisposedException by preventing disposal of shared objects
claude[bot] Aug 12, 2025
7b139e3
Remove shared object concept and implement pure reference counting
claude[bot] Aug 12, 2025
6c2825f
Fix ObjectDisposedException by preventing disposal of shared objects
claude[bot] Aug 12, 2025
d6e8f76
Implement pure reference counting by removing all shared object detec…
claude[bot] Aug 12, 2025
b8ebc60
Implement pure reference counting disposal system as requested by user
Copilot Aug 12, 2025
8c01e6f
Merge branch 'main' into copilot/fix-2867
thomhurst Aug 12, 2025
02465d3
Fix race condition in object disposal by removing background task dis…
Copilot Aug 12, 2025
b21c07e
Restore global.json to original .NET 9.0 version
Copilot Aug 12, 2025
c4374ac
Merge branch 'main' into copilot/fix-2867
thomhurst Aug 12, 2025
78d31da
Fix hanging/deadlock issues in object disposal system
thomhurst Aug 12, 2025
11de084
Fix remaining build errors in disposal system
thomhurst Aug 12, 2025
6e5999a
Fix hanging/deadlock issues in object disposal system with recursive …
thomhurst Aug 12, 2025
f0a93f6
Fix object disposal issues and hanging tests
thomhurst Aug 12, 2025
c9f15a1
Fix object disposal tracking for shared objects in repeated tests
thomhurst Aug 12, 2025
a4fdd77
Fix object disposal tracking for shared objects in repeated tests
thomhurst Aug 13, 2025
947ff2c
Fix reference counting logic in object disposal tracking
thomhurst Aug 13, 2025
80b8cb8
Add debug logging and instance removal methods in object tracking and…
thomhurst Aug 13, 2025
ce1f16f
Refactor object retrieval methods to improve type handling and simpli…
thomhurst Aug 13, 2025
4991073
Enhance object disposal tracking and cleanup in ScopedDictionary
thomhurst Aug 13, 2025
0aaafcd
Change visibility of ObjectTracker methods to internal for better enc…
thomhurst Aug 13, 2025
b14a50c
Refactor property injection logic to improve task handling and object…
thomhurst Aug 13, 2025
44c2a20
Fix object disposal tracking for shared objects in repeated tests
thomhurst Aug 13, 2025
5c41935
Refactor TestBuilderContextAccessor visibility and enhance ScopedDict…
thomhurst Aug 13, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -314,11 +314,13 @@ private static void GenerateAsyncDataSourceGeneratorWithProperty(CodeWriter writ
{
var generatorCode = CodeGenerationHelpers.GenerateAttributeInstantiation(attr);
writer.AppendLine($"var generator = {generatorCode};");
writer.AppendLine("// Use the global static property context for disposal tracking");
writer.AppendLine("var globalContext = global::TUnit.Core.TestSessionContext.GlobalStaticPropertyContext;");
writer.AppendLine("var metadata = new global::TUnit.Core.DataGeneratorMetadata");
writer.AppendLine("{");
writer.Indent();
writer.AppendLine("Type = global::TUnit.Core.Enums.DataGeneratorType.Property,");
writer.AppendLine("TestBuilderContext = null,");
writer.AppendLine("TestBuilderContext = new global::TUnit.Core.TestBuilderContextAccessor(globalContext),");
writer.AppendLine("MembersToGenerate = new global::TUnit.Core.MemberMetadata[] { propertyMetadata },");
writer.AppendLine("TestInformation = null,");
writer.AppendLine("TestSessionId = global::TUnit.Core.TestSessionContext.Current?.Id ?? \"static-property-init\",");
Expand Down
119 changes: 36 additions & 83 deletions TUnit.Core/Attributes/TestData/ClassDataSources.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,24 @@ private ClassDataSources()

public static readonly GetOnlyDictionary<string, ClassDataSources> SourcesPerSession = new();

public static ClassDataSources Get(string sessionId) => SourcesPerSession.GetOrAdd(sessionId, _ => new());
public static ClassDataSources Get(string sessionId)
{
var isNew = false;
var result = SourcesPerSession.GetOrAdd(sessionId, _ =>
{
isNew = true;
return new ClassDataSources();
});

if (isNew)
{
Console.WriteLine($"[ClassDataSources] Created new ClassDataSources for session {sessionId}");
}

return result;
}

public (T, SharedType, string) GetItemForIndexAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>(int index, Type? testClassType, SharedType[] sharedTypes, string[] keys, DataGeneratorMetadata dataGeneratorMetadata) where T : new()
public (T, SharedType, string) GetItemForIndexAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>(int index, Type testClassType, SharedType[] sharedTypes, string[] keys, DataGeneratorMetadata dataGeneratorMetadata) where T : new()
{
var shared = sharedTypes.ElementAtOrDefault(index);

Expand All @@ -37,82 +52,30 @@ private string GetKey(int index, SharedType[] sharedTypes, string[] keys)
return keys.ElementAtOrDefault(keyedIndex) ?? throw new ArgumentException($"Key at index {keyedIndex} not found");
}

public T Get<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>(SharedType sharedType, Type? testClassType, string key, DataGeneratorMetadata dataGeneratorMetadata)
public T Get<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>(SharedType sharedType, Type testClassType, string key, DataGeneratorMetadata dataGeneratorMetadata)
{
#pragma warning disable CS8603 // Possible null reference return.
if (sharedType == SharedType.None)
{
return Create<T>(dataGeneratorMetadata);
}

if (sharedType == SharedType.PerTestSession)
{
return (T) TestDataContainer.GetGlobalInstance(typeof(T), () => Create(typeof(T), dataGeneratorMetadata));
}

if (sharedType == SharedType.PerClass)
{
if (testClassType == null)
{
throw new InvalidOperationException($"Cannot use SharedType.PerClass without a test class type. This may occur during static property initialization.");
}
return (T) TestDataContainer.GetInstanceForClass(testClassType, typeof(T), () => Create(typeof(T), dataGeneratorMetadata));
}

if (sharedType == SharedType.Keyed)
return sharedType switch
{
return (T) TestDataContainer.GetInstanceForKey(key, typeof(T), () => Create(typeof(T), dataGeneratorMetadata));
}

if (sharedType == SharedType.PerAssembly)
{
if (testClassType == null)
{
throw new InvalidOperationException($"Cannot use SharedType.PerAssembly without a test class type. This may occur during static property initialization.");
}
return (T) TestDataContainer.GetInstanceForAssembly(testClassType.Assembly, typeof(T), () => Create(typeof(T), dataGeneratorMetadata));
}
#pragma warning restore CS8603 // Possible null reference return.

throw new ArgumentOutOfRangeException();
SharedType.None => Create<T>(dataGeneratorMetadata),
SharedType.PerTestSession => (T) TestDataContainer.GetGlobalInstance(typeof(T), _ => Create(typeof(T), dataGeneratorMetadata))!,
SharedType.PerClass => (T) TestDataContainer.GetInstanceForClass(testClassType, typeof(T), _ => Create(typeof(T), dataGeneratorMetadata))!,
SharedType.Keyed => (T) TestDataContainer.GetInstanceForKey(key, typeof(T), _ => Create(typeof(T), dataGeneratorMetadata))!,
SharedType.PerAssembly => (T) TestDataContainer.GetInstanceForAssembly(testClassType.Assembly, typeof(T), _ => Create(typeof(T), dataGeneratorMetadata))!,
_ => throw new ArgumentOutOfRangeException()
};
}

public object Get(SharedType sharedType, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] Type type, Type? testClassType, string? key, DataGeneratorMetadata dataGeneratorMetadata)
public object? Get(SharedType sharedType, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] Type type, Type testClassType, string? key, DataGeneratorMetadata dataGeneratorMetadata)
{
if (sharedType == SharedType.None)
{
return Create(type, dataGeneratorMetadata);
}

if (sharedType == SharedType.PerTestSession)
{
return TestDataContainer.GetGlobalInstance(type, () => Create(type, dataGeneratorMetadata));
}

if (sharedType == SharedType.PerClass)
{
if (testClassType == null)
{
throw new InvalidOperationException($"Cannot use SharedType.PerClass without a test class type. This may occur during static property initialization.");
}
return TestDataContainer.GetInstanceForClass(testClassType, type, () => Create(type, dataGeneratorMetadata));
}

if (sharedType == SharedType.Keyed)
return sharedType switch
{
return TestDataContainer.GetInstanceForKey(key!, type, () => Create(type, dataGeneratorMetadata));
}

if (sharedType == SharedType.PerAssembly)
{
if (testClassType == null)
{
throw new InvalidOperationException($"Cannot use SharedType.PerAssembly without a test class type. This may occur during static property initialization.");
}
return TestDataContainer.GetInstanceForAssembly(testClassType.Assembly, type, () => Create(type, dataGeneratorMetadata));
}

throw new ArgumentOutOfRangeException();
SharedType.None => Create(type, dataGeneratorMetadata),
SharedType.PerTestSession => TestDataContainer.GetGlobalInstance(type, _ => Create(type, dataGeneratorMetadata)),
SharedType.PerClass => TestDataContainer.GetInstanceForClass(testClassType, type, _ => Create(type, dataGeneratorMetadata)),
SharedType.Keyed => TestDataContainer.GetInstanceForKey(key!, type, _ => Create(type, dataGeneratorMetadata)),
SharedType.PerAssembly => TestDataContainer.GetInstanceForAssembly(testClassType.Assembly, type, _ => Create(type, dataGeneratorMetadata)),
_ => throw new ArgumentOutOfRangeException()
};
}

[return: NotNull]
Expand All @@ -125,17 +88,7 @@ private static object Create([DynamicallyAccessedMembers(DynamicallyAccessedMemb
{
try
{
var instance = Activator.CreateInstance(type)!;

// Track the created object
var trackerEvents2 = dataGeneratorMetadata.TestBuilderContext?.Current.Events;

if (trackerEvents2 != null)
{
ObjectTracker.TrackObject(trackerEvents2, instance);
}

return instance;
return Activator.CreateInstance(type)!;
}
catch (TargetInvocationException targetInvocationException)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public abstract class DependencyInjectionDataSourceAttribute<TScope> : UntypedDa
{
if (scope is IAsyncDisposable asyncDisposable)
{
await asyncDisposable.DisposeAsync();
await asyncDisposable.DisposeAsync().ConfigureAwait(false);
}
else if (scope is IDisposable disposable)
{
Expand Down
87 changes: 0 additions & 87 deletions TUnit.Core/Data/DependencyTracker.cs

This file was deleted.

43 changes: 0 additions & 43 deletions TUnit.Core/Data/IDisposableReference.cs

This file was deleted.

29 changes: 0 additions & 29 deletions TUnit.Core/Data/IScopeManager.cs

This file was deleted.

37 changes: 0 additions & 37 deletions TUnit.Core/Data/ScopedContainer.cs

This file was deleted.

Loading
Loading