diff --git a/TUnit.Core.SourceGenerator/Generators/PropertyInjectionSourceGenerator.cs b/TUnit.Core.SourceGenerator/Generators/PropertyInjectionSourceGenerator.cs index 9ab0d0eadf..4d29129591 100644 --- a/TUnit.Core.SourceGenerator/Generators/PropertyInjectionSourceGenerator.cs +++ b/TUnit.Core.SourceGenerator/Generators/PropertyInjectionSourceGenerator.cs @@ -273,7 +273,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 = () =>"); diff --git a/TUnit.TestProject/InheritanceSharedTypeRepro.cs b/TUnit.TestProject/InheritanceSharedTypeRepro.cs new file mode 100644 index 0000000000..576ada0705 --- /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 0000000000..fb87ce8aa9 --- /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