From 690500189d81011a2ac770df74bbcb90a942de85 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Sep 2025 23:44:15 +0000 Subject: [PATCH 1/3] Initial plan From 703b798e8c439954882c92dac4edf437869689dc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Sep 2025 23:57:56 +0000 Subject: [PATCH 2/3] Identify root cause of SharedType.PerTestSession inheritance issue Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com> --- .../InheritanceSharedTypeRepro.cs | 175 ++++++++++++++++++ TUnit.TestProject/SimpleInheritanceTest.cs | 63 +++++++ 2 files changed, 238 insertions(+) create mode 100644 TUnit.TestProject/InheritanceSharedTypeRepro.cs create mode 100644 TUnit.TestProject/SimpleInheritanceTest.cs diff --git a/TUnit.TestProject/InheritanceSharedTypeRepro.cs b/TUnit.TestProject/InheritanceSharedTypeRepro.cs new file mode 100644 index 00000000000..576ada07058 --- /dev/null +++ b/TUnit.TestProject/InheritanceSharedTypeRepro.cs @@ -0,0 +1,175 @@ +using TUnit.TestProject.Attributes; + +namespace TUnit.TestProject.InheritanceSharedTypeRepro; + +// Simulating the user's container wrappers +public class PostgreSqlContainerWrapper : IDisposable, IAsyncDisposable +{ + public bool IsDisposed { get; private set; } + public string InstanceId { get; } = Guid.NewGuid().ToString(); + + public void Dispose() + { + IsDisposed = true; + Console.WriteLine($"PostgreSQL Container {InstanceId} disposed (sync)"); + } + + public ValueTask DisposeAsync() + { + IsDisposed = true; + Console.WriteLine($"PostgreSQL Container {InstanceId} disposed (async)"); + return default; + } +} + +public class RabbitMqContainerWrapper : IDisposable, IAsyncDisposable +{ + public bool IsDisposed { get; private set; } + public string InstanceId { get; } = Guid.NewGuid().ToString(); + + public void Dispose() + { + IsDisposed = true; + Console.WriteLine($"RabbitMQ Container {InstanceId} disposed (sync)"); + } + + public ValueTask DisposeAsync() + { + IsDisposed = true; + Console.WriteLine($"RabbitMQ Container {InstanceId} disposed (async)"); + return default; + } +} + +// Base application testing server (like in the user's example) +public class BaseApplicationTestingServer +{ + // Base functionality +} + +// The working scenario - containers defined directly on TestingServer +public class TestingServerWorking : BaseApplicationTestingServer +{ + [ClassDataSource(Shared = SharedType.PerTestSession)] + public required PostgreSqlContainerWrapper PostgresContainerWrapper { get; set; } = new(); + + [ClassDataSource(Shared = SharedType.PerTestSession)] + public required RabbitMqContainerWrapper RabbitContainerWrapper { get; set; } = new(); +} + +// The broken scenario - containers defined on intermediate base class +public abstract class ModuleTestingServer : BaseApplicationTestingServer +{ + [ClassDataSource(Shared = SharedType.PerTestSession)] + public required PostgreSqlContainerWrapper PostgresContainerWrapper { get; set; } = new(); + + [ClassDataSource(Shared = SharedType.PerTestSession)] + public required RabbitMqContainerWrapper RabbitContainerWrapper { get; set; } = new(); +} + +public class TestingServerBroken : ModuleTestingServer +{ + // Inherits the ClassDataSource properties from ModuleTestingServer +} + +// Test classes for the working scenario +[EngineTest(ExpectedResult.Pass)] +public class WorkingTestClass1 +{ + [ClassDataSource(Shared = SharedType.PerClass)] + public required TestingServerWorking TestingServer { get; set; } = default!; + + [Test, NotInParallel(Order = 1)] + public async Task Test1() + { + Console.WriteLine($"WorkingTestClass1.Test1 - PostgreSQL: {TestingServer.PostgresContainerWrapper.InstanceId}, RabbitMQ: {TestingServer.RabbitContainerWrapper.InstanceId}"); + + await Assert.That(TestingServer.PostgresContainerWrapper.IsDisposed).IsFalse(); + await Assert.That(TestingServer.RabbitContainerWrapper.IsDisposed).IsFalse(); + } + + [Test, NotInParallel(Order = 2)] + public async Task Test2() + { + Console.WriteLine($"WorkingTestClass1.Test2 - PostgreSQL: {TestingServer.PostgresContainerWrapper.InstanceId}, RabbitMQ: {TestingServer.RabbitContainerWrapper.InstanceId}"); + + await Assert.That(TestingServer.PostgresContainerWrapper.IsDisposed).IsFalse(); + await Assert.That(TestingServer.RabbitContainerWrapper.IsDisposed).IsFalse(); + } +} + +[EngineTest(ExpectedResult.Pass)] +public class WorkingTestClass2 +{ + [ClassDataSource(Shared = SharedType.PerClass)] + public required TestingServerWorking TestingServer { get; set; } = default!; + + [Test, NotInParallel(Order = 3)] + public async Task Test3() + { + Console.WriteLine($"WorkingTestClass2.Test3 - PostgreSQL: {TestingServer.PostgresContainerWrapper.InstanceId}, RabbitMQ: {TestingServer.RabbitContainerWrapper.InstanceId}"); + + await Assert.That(TestingServer.PostgresContainerWrapper.IsDisposed).IsFalse(); + await Assert.That(TestingServer.RabbitContainerWrapper.IsDisposed).IsFalse(); + } + + [Test, NotInParallel(Order = 4)] + public async Task Test4() + { + Console.WriteLine($"WorkingTestClass2.Test4 - PostgreSQL: {TestingServer.PostgresContainerWrapper.InstanceId}, RabbitMQ: {TestingServer.RabbitContainerWrapper.InstanceId}"); + + await Assert.That(TestingServer.PostgresContainerWrapper.IsDisposed).IsFalse(); + await Assert.That(TestingServer.RabbitContainerWrapper.IsDisposed).IsFalse(); + } +} + +// Test classes for the broken scenario +[EngineTest(ExpectedResult.Pass)] +public class BrokenTestClass1 +{ + [ClassDataSource(Shared = SharedType.PerClass)] + public required TestingServerBroken TestingServer { get; set; } = default!; + + [Test, NotInParallel(Order = 5)] + public async Task Test5() + { + Console.WriteLine($"BrokenTestClass1.Test5 - PostgreSQL: {TestingServer.PostgresContainerWrapper.InstanceId}, RabbitMQ: {TestingServer.RabbitContainerWrapper.InstanceId}"); + + await Assert.That(TestingServer.PostgresContainerWrapper.IsDisposed).IsFalse(); + await Assert.That(TestingServer.RabbitContainerWrapper.IsDisposed).IsFalse(); + } + + [Test, NotInParallel(Order = 6)] + public async Task Test6() + { + Console.WriteLine($"BrokenTestClass1.Test6 - PostgreSQL: {TestingServer.PostgresContainerWrapper.InstanceId}, RabbitMQ: {TestingServer.RabbitContainerWrapper.InstanceId}"); + + await Assert.That(TestingServer.PostgresContainerWrapper.IsDisposed).IsFalse(); + await Assert.That(TestingServer.RabbitContainerWrapper.IsDisposed).IsFalse(); + } +} + +[EngineTest(ExpectedResult.Pass)] +public class BrokenTestClass2 +{ + [ClassDataSource(Shared = SharedType.PerClass)] + public required TestingServerBroken TestingServer { get; set; } = default!; + + [Test, NotInParallel(Order = 7)] + public async Task Test7() + { + Console.WriteLine($"BrokenTestClass2.Test7 - PostgreSQL: {TestingServer.PostgresContainerWrapper.InstanceId}, RabbitMQ: {TestingServer.RabbitContainerWrapper.InstanceId}"); + + await Assert.That(TestingServer.PostgresContainerWrapper.IsDisposed).IsFalse(); + await Assert.That(TestingServer.RabbitContainerWrapper.IsDisposed).IsFalse(); + } + + [Test, NotInParallel(Order = 8)] + public async Task Test8() + { + Console.WriteLine($"BrokenTestClass2.Test8 - PostgreSQL: {TestingServer.PostgresContainerWrapper.InstanceId}, RabbitMQ: {TestingServer.RabbitContainerWrapper.InstanceId}"); + + await Assert.That(TestingServer.PostgresContainerWrapper.IsDisposed).IsFalse(); + await Assert.That(TestingServer.RabbitContainerWrapper.IsDisposed).IsFalse(); + } +} \ No newline at end of file diff --git a/TUnit.TestProject/SimpleInheritanceTest.cs b/TUnit.TestProject/SimpleInheritanceTest.cs new file mode 100644 index 00000000000..fb87ce8aa9f --- /dev/null +++ b/TUnit.TestProject/SimpleInheritanceTest.cs @@ -0,0 +1,63 @@ +using TUnit.TestProject.Attributes; + +namespace TUnit.TestProject; + +// Simple reproduction of the inheritance issue +public class SimpleContainer : IDisposable +{ + public string Id { get; } = Guid.NewGuid().ToString()[..8]; + public bool IsDisposed { get; private set; } + + public void Dispose() + { + IsDisposed = true; + Console.WriteLine($"Container {Id} disposed"); + } +} + +// Working scenario - attribute directly on concrete class +public class DirectTestServer +{ + [ClassDataSource(Shared = SharedType.PerTestSession)] + public required SimpleContainer Container { get; set; } = new(); +} + +// Broken scenario - attribute on abstract base class +public abstract class BaseTestServer +{ + [ClassDataSource(Shared = SharedType.PerTestSession)] + public required SimpleContainer Container { get; set; } = new(); +} + +public class InheritedTestServer : BaseTestServer +{ + // Inherits the ClassDataSource property +} + +[EngineTest(ExpectedResult.Pass)] +public class DirectServerTests +{ + [ClassDataSource(Shared = SharedType.PerClass)] + public required DirectTestServer Server { get; set; } = default!; + + [Test] + public async Task DirectTest1() + { + Console.WriteLine($"DirectTest1 - Container ID: {Server.Container.Id}"); + await Assert.That(Server.Container.IsDisposed).IsFalse(); + } +} + +[EngineTest(ExpectedResult.Pass)] +public class InheritedServerTests +{ + [ClassDataSource(Shared = SharedType.PerClass)] + public required InheritedTestServer Server { get; set; } = default!; + + [Test] + public async Task InheritedTest1() + { + Console.WriteLine($"InheritedTest1 - Container ID: {Server.Container.Id}"); + await Assert.That(Server.Container.IsDisposed).IsFalse(); + } +} \ No newline at end of file From 809e25c309428a3abf2c81479f41bf73306924ce Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Sep 2025 00:03:46 +0000 Subject: [PATCH 3/3] Fix SharedType.PerTestSession inheritance issue in PropertyInjectionSourceGenerator Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com> --- .../Generators/PropertyInjectionSourceGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TUnit.Core.SourceGenerator/Generators/PropertyInjectionSourceGenerator.cs b/TUnit.Core.SourceGenerator/Generators/PropertyInjectionSourceGenerator.cs index df2547d0555..b05206b3bbe 100644 --- a/TUnit.Core.SourceGenerator/Generators/PropertyInjectionSourceGenerator.cs +++ b/TUnit.Core.SourceGenerator/Generators/PropertyInjectionSourceGenerator.cs @@ -259,7 +259,7 @@ private static void GeneratePropertyMetadata(StringBuilder sb, PropertyWithDataS sb.AppendLine(" {"); sb.AppendLine($" PropertyName = \"{propertyName}\","); sb.AppendLine($" PropertyType = typeof({propertyTypeForTypeof}),"); - sb.AppendLine($" ContainingType = typeof({classSymbol.ToDisplayString()}),"); + sb.AppendLine($" ContainingType = typeof({propInfo.Property.ContainingType.ToDisplayString()}),"); // Generate CreateDataSource delegate sb.AppendLine(" CreateDataSource = () =>");