Skip to content

Commit 30f4ba4

Browse files
Copilotthomhurst
andauthored
Fix SharedType.PerTestSession not being respected with inheritance (#3082)
* Initial plan * Identify root cause of SharedType.PerTestSession inheritance issue Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com> * Fix SharedType.PerTestSession inheritance issue in PropertyInjectionSourceGenerator Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com>
1 parent 2deb77d commit 30f4ba4

File tree

3 files changed

+239
-1
lines changed

3 files changed

+239
-1
lines changed

TUnit.Core.SourceGenerator/Generators/PropertyInjectionSourceGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ private static void GeneratePropertyMetadata(StringBuilder sb, PropertyWithDataS
267267
sb.AppendLine(" {");
268268
sb.AppendLine($" PropertyName = \"{propertyName}\",");
269269
sb.AppendLine($" PropertyType = typeof({propertyTypeForTypeof}),");
270-
sb.AppendLine($" ContainingType = typeof({classSymbol.ToDisplayString()}),");
270+
sb.AppendLine($" ContainingType = typeof({propInfo.Property.ContainingType.ToDisplayString()}),");
271271

272272
// Generate CreateDataSource delegate
273273
sb.AppendLine(" CreateDataSource = () =>");
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
using TUnit.TestProject.Attributes;
2+
3+
namespace TUnit.TestProject.InheritanceSharedTypeRepro;
4+
5+
// Simulating the user's container wrappers
6+
public class PostgreSqlContainerWrapper : IDisposable, IAsyncDisposable
7+
{
8+
public bool IsDisposed { get; private set; }
9+
public string InstanceId { get; } = Guid.NewGuid().ToString();
10+
11+
public void Dispose()
12+
{
13+
IsDisposed = true;
14+
Console.WriteLine($"PostgreSQL Container {InstanceId} disposed (sync)");
15+
}
16+
17+
public ValueTask DisposeAsync()
18+
{
19+
IsDisposed = true;
20+
Console.WriteLine($"PostgreSQL Container {InstanceId} disposed (async)");
21+
return default;
22+
}
23+
}
24+
25+
public class RabbitMqContainerWrapper : IDisposable, IAsyncDisposable
26+
{
27+
public bool IsDisposed { get; private set; }
28+
public string InstanceId { get; } = Guid.NewGuid().ToString();
29+
30+
public void Dispose()
31+
{
32+
IsDisposed = true;
33+
Console.WriteLine($"RabbitMQ Container {InstanceId} disposed (sync)");
34+
}
35+
36+
public ValueTask DisposeAsync()
37+
{
38+
IsDisposed = true;
39+
Console.WriteLine($"RabbitMQ Container {InstanceId} disposed (async)");
40+
return default;
41+
}
42+
}
43+
44+
// Base application testing server (like in the user's example)
45+
public class BaseApplicationTestingServer<T>
46+
{
47+
// Base functionality
48+
}
49+
50+
// The working scenario - containers defined directly on TestingServer
51+
public class TestingServerWorking : BaseApplicationTestingServer<object>
52+
{
53+
[ClassDataSource<PostgreSqlContainerWrapper>(Shared = SharedType.PerTestSession)]
54+
public required PostgreSqlContainerWrapper PostgresContainerWrapper { get; set; } = new();
55+
56+
[ClassDataSource<RabbitMqContainerWrapper>(Shared = SharedType.PerTestSession)]
57+
public required RabbitMqContainerWrapper RabbitContainerWrapper { get; set; } = new();
58+
}
59+
60+
// The broken scenario - containers defined on intermediate base class
61+
public abstract class ModuleTestingServer : BaseApplicationTestingServer<object>
62+
{
63+
[ClassDataSource<PostgreSqlContainerWrapper>(Shared = SharedType.PerTestSession)]
64+
public required PostgreSqlContainerWrapper PostgresContainerWrapper { get; set; } = new();
65+
66+
[ClassDataSource<RabbitMqContainerWrapper>(Shared = SharedType.PerTestSession)]
67+
public required RabbitMqContainerWrapper RabbitContainerWrapper { get; set; } = new();
68+
}
69+
70+
public class TestingServerBroken : ModuleTestingServer
71+
{
72+
// Inherits the ClassDataSource properties from ModuleTestingServer
73+
}
74+
75+
// Test classes for the working scenario
76+
[EngineTest(ExpectedResult.Pass)]
77+
public class WorkingTestClass1
78+
{
79+
[ClassDataSource<TestingServerWorking>(Shared = SharedType.PerClass)]
80+
public required TestingServerWorking TestingServer { get; set; } = default!;
81+
82+
[Test, NotInParallel(Order = 1)]
83+
public async Task Test1()
84+
{
85+
Console.WriteLine($"WorkingTestClass1.Test1 - PostgreSQL: {TestingServer.PostgresContainerWrapper.InstanceId}, RabbitMQ: {TestingServer.RabbitContainerWrapper.InstanceId}");
86+
87+
await Assert.That(TestingServer.PostgresContainerWrapper.IsDisposed).IsFalse();
88+
await Assert.That(TestingServer.RabbitContainerWrapper.IsDisposed).IsFalse();
89+
}
90+
91+
[Test, NotInParallel(Order = 2)]
92+
public async Task Test2()
93+
{
94+
Console.WriteLine($"WorkingTestClass1.Test2 - PostgreSQL: {TestingServer.PostgresContainerWrapper.InstanceId}, RabbitMQ: {TestingServer.RabbitContainerWrapper.InstanceId}");
95+
96+
await Assert.That(TestingServer.PostgresContainerWrapper.IsDisposed).IsFalse();
97+
await Assert.That(TestingServer.RabbitContainerWrapper.IsDisposed).IsFalse();
98+
}
99+
}
100+
101+
[EngineTest(ExpectedResult.Pass)]
102+
public class WorkingTestClass2
103+
{
104+
[ClassDataSource<TestingServerWorking>(Shared = SharedType.PerClass)]
105+
public required TestingServerWorking TestingServer { get; set; } = default!;
106+
107+
[Test, NotInParallel(Order = 3)]
108+
public async Task Test3()
109+
{
110+
Console.WriteLine($"WorkingTestClass2.Test3 - PostgreSQL: {TestingServer.PostgresContainerWrapper.InstanceId}, RabbitMQ: {TestingServer.RabbitContainerWrapper.InstanceId}");
111+
112+
await Assert.That(TestingServer.PostgresContainerWrapper.IsDisposed).IsFalse();
113+
await Assert.That(TestingServer.RabbitContainerWrapper.IsDisposed).IsFalse();
114+
}
115+
116+
[Test, NotInParallel(Order = 4)]
117+
public async Task Test4()
118+
{
119+
Console.WriteLine($"WorkingTestClass2.Test4 - PostgreSQL: {TestingServer.PostgresContainerWrapper.InstanceId}, RabbitMQ: {TestingServer.RabbitContainerWrapper.InstanceId}");
120+
121+
await Assert.That(TestingServer.PostgresContainerWrapper.IsDisposed).IsFalse();
122+
await Assert.That(TestingServer.RabbitContainerWrapper.IsDisposed).IsFalse();
123+
}
124+
}
125+
126+
// Test classes for the broken scenario
127+
[EngineTest(ExpectedResult.Pass)]
128+
public class BrokenTestClass1
129+
{
130+
[ClassDataSource<TestingServerBroken>(Shared = SharedType.PerClass)]
131+
public required TestingServerBroken TestingServer { get; set; } = default!;
132+
133+
[Test, NotInParallel(Order = 5)]
134+
public async Task Test5()
135+
{
136+
Console.WriteLine($"BrokenTestClass1.Test5 - PostgreSQL: {TestingServer.PostgresContainerWrapper.InstanceId}, RabbitMQ: {TestingServer.RabbitContainerWrapper.InstanceId}");
137+
138+
await Assert.That(TestingServer.PostgresContainerWrapper.IsDisposed).IsFalse();
139+
await Assert.That(TestingServer.RabbitContainerWrapper.IsDisposed).IsFalse();
140+
}
141+
142+
[Test, NotInParallel(Order = 6)]
143+
public async Task Test6()
144+
{
145+
Console.WriteLine($"BrokenTestClass1.Test6 - PostgreSQL: {TestingServer.PostgresContainerWrapper.InstanceId}, RabbitMQ: {TestingServer.RabbitContainerWrapper.InstanceId}");
146+
147+
await Assert.That(TestingServer.PostgresContainerWrapper.IsDisposed).IsFalse();
148+
await Assert.That(TestingServer.RabbitContainerWrapper.IsDisposed).IsFalse();
149+
}
150+
}
151+
152+
[EngineTest(ExpectedResult.Pass)]
153+
public class BrokenTestClass2
154+
{
155+
[ClassDataSource<TestingServerBroken>(Shared = SharedType.PerClass)]
156+
public required TestingServerBroken TestingServer { get; set; } = default!;
157+
158+
[Test, NotInParallel(Order = 7)]
159+
public async Task Test7()
160+
{
161+
Console.WriteLine($"BrokenTestClass2.Test7 - PostgreSQL: {TestingServer.PostgresContainerWrapper.InstanceId}, RabbitMQ: {TestingServer.RabbitContainerWrapper.InstanceId}");
162+
163+
await Assert.That(TestingServer.PostgresContainerWrapper.IsDisposed).IsFalse();
164+
await Assert.That(TestingServer.RabbitContainerWrapper.IsDisposed).IsFalse();
165+
}
166+
167+
[Test, NotInParallel(Order = 8)]
168+
public async Task Test8()
169+
{
170+
Console.WriteLine($"BrokenTestClass2.Test8 - PostgreSQL: {TestingServer.PostgresContainerWrapper.InstanceId}, RabbitMQ: {TestingServer.RabbitContainerWrapper.InstanceId}");
171+
172+
await Assert.That(TestingServer.PostgresContainerWrapper.IsDisposed).IsFalse();
173+
await Assert.That(TestingServer.RabbitContainerWrapper.IsDisposed).IsFalse();
174+
}
175+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using TUnit.TestProject.Attributes;
2+
3+
namespace TUnit.TestProject;
4+
5+
// Simple reproduction of the inheritance issue
6+
public class SimpleContainer : IDisposable
7+
{
8+
public string Id { get; } = Guid.NewGuid().ToString()[..8];
9+
public bool IsDisposed { get; private set; }
10+
11+
public void Dispose()
12+
{
13+
IsDisposed = true;
14+
Console.WriteLine($"Container {Id} disposed");
15+
}
16+
}
17+
18+
// Working scenario - attribute directly on concrete class
19+
public class DirectTestServer
20+
{
21+
[ClassDataSource<SimpleContainer>(Shared = SharedType.PerTestSession)]
22+
public required SimpleContainer Container { get; set; } = new();
23+
}
24+
25+
// Broken scenario - attribute on abstract base class
26+
public abstract class BaseTestServer
27+
{
28+
[ClassDataSource<SimpleContainer>(Shared = SharedType.PerTestSession)]
29+
public required SimpleContainer Container { get; set; } = new();
30+
}
31+
32+
public class InheritedTestServer : BaseTestServer
33+
{
34+
// Inherits the ClassDataSource property
35+
}
36+
37+
[EngineTest(ExpectedResult.Pass)]
38+
public class DirectServerTests
39+
{
40+
[ClassDataSource<DirectTestServer>(Shared = SharedType.PerClass)]
41+
public required DirectTestServer Server { get; set; } = default!;
42+
43+
[Test]
44+
public async Task DirectTest1()
45+
{
46+
Console.WriteLine($"DirectTest1 - Container ID: {Server.Container.Id}");
47+
await Assert.That(Server.Container.IsDisposed).IsFalse();
48+
}
49+
}
50+
51+
[EngineTest(ExpectedResult.Pass)]
52+
public class InheritedServerTests
53+
{
54+
[ClassDataSource<InheritedTestServer>(Shared = SharedType.PerClass)]
55+
public required InheritedTestServer Server { get; set; } = default!;
56+
57+
[Test]
58+
public async Task InheritedTest1()
59+
{
60+
Console.WriteLine($"InheritedTest1 - Container ID: {Server.Container.Id}");
61+
await Assert.That(Server.Container.IsDisposed).IsFalse();
62+
}
63+
}

0 commit comments

Comments
 (0)