Skip to content

Commit 7d5e293

Browse files
authored
feat: add disposal execution support in hook executors and related tests (#3928)
1 parent 989adb7 commit 7d5e293

10 files changed

+73
-3
lines changed

TUnit.Core/Executors/GenericAbstractExecutor.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ public ValueTask ExecuteAfterTestHook(MethodMetadata hookMethodInfo, TestContext
5757
return ExecuteAsync(action);
5858
}
5959

60+
public ValueTask ExecuteDisposal(TestContext context, Func<ValueTask> action)
61+
{
62+
return ExecuteAsync(action);
63+
}
64+
6065
public ValueTask ExecuteTest(TestContext context, Func<ValueTask> action)
6166
{
6267
return ExecuteAsync(action);

TUnit.Core/Interfaces/IHookExecutor.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,11 @@ public interface IHookExecutor
1313
ValueTask ExecuteAfterAssemblyHook(MethodMetadata hookMethodInfo, AssemblyHookContext context, Func<ValueTask> action);
1414
ValueTask ExecuteAfterClassHook(MethodMetadata hookMethodInfo, ClassHookContext context, Func<ValueTask> action);
1515
ValueTask ExecuteAfterTestHook(MethodMetadata hookMethodInfo, TestContext context, Func<ValueTask> action);
16+
17+
#if NETSTANDARD2_0
18+
ValueTask ExecuteDisposal(TestContext context, Func<ValueTask> action);
19+
#else
20+
ValueTask ExecuteDisposal(TestContext context, Func<ValueTask> action)
21+
=> action();
22+
#endif
1623
}

TUnit.Engine/Discovery/ReflectionHookDiscoveryService.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,4 +1029,7 @@ public ValueTask ExecuteBeforeTestSessionHook(MethodMetadata testMethod, TestSes
10291029

10301030
public ValueTask ExecuteAfterTestSessionHook(MethodMetadata testMethod, TestSessionContext context, Func<ValueTask> action)
10311031
=> action();
1032+
1033+
public ValueTask ExecuteDisposal(TestContext context, Func<ValueTask> action)
1034+
=> action();
10321035
}

TUnit.Engine/Services/TestExecution/TestCoordinator.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Linq;
22
using TUnit.Core;
33
using TUnit.Core.Exceptions;
4+
using TUnit.Core.Interfaces;
45
using TUnit.Core.Logging;
56
using TUnit.Core.Tracking;
67
using TUnit.Engine.Helpers;
@@ -152,7 +153,8 @@ await TimeoutHelper.ExecuteWithTimeoutAsync(
152153

153154
try
154155
{
155-
await TestExecutor.DisposeTestInstance(test).ConfigureAwait(false);
156+
var hookExecutor = test.Context.CustomHookExecutor;
157+
await TestExecutor.DisposeTestInstance(test, hookExecutor).ConfigureAwait(false);
156158
}
157159
catch (Exception disposeEx)
158160
{

TUnit.Engine/TestExecutor.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,12 +247,12 @@ public IContextProvider GetContextProvider()
247247
return _contextProvider;
248248
}
249249

250-
internal static async Task DisposeTestInstance(AbstractExecutableTest test)
250+
internal static async Task DisposeTestInstance(AbstractExecutableTest test, IHookExecutor? hookExecutor = null)
251251
{
252252
// Dispose the test instance if it's disposable
253253
if (test.Context.Metadata.TestDetails.ClassInstance is not SkippedTestInstance)
254254
{
255-
try
255+
async ValueTask DisposeAsync()
256256
{
257257
var instance = test.Context.Metadata.TestDetails.ClassInstance;
258258

@@ -266,6 +266,18 @@ internal static async Task DisposeTestInstance(AbstractExecutableTest test)
266266
break;
267267
}
268268
}
269+
270+
try
271+
{
272+
if (hookExecutor != null)
273+
{
274+
await hookExecutor.ExecuteDisposal(test.Context, DisposeAsync).ConfigureAwait(false);
275+
}
276+
else
277+
{
278+
await DisposeAsync().ConfigureAwait(false);
279+
}
280+
}
269281
catch
270282
{
271283
// Swallow disposal errors - they shouldn't fail the test

TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet10_0.verified.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,7 @@ namespace
749749
public . ExecuteBeforeTestDiscoveryHook(.MethodMetadata hookMethodInfo, .BeforeTestDiscoveryContext context, <.> action) { }
750750
public . ExecuteBeforeTestHook(.MethodMetadata hookMethodInfo, .TestContext context, <.> action) { }
751751
public . ExecuteBeforeTestSessionHook(.MethodMetadata hookMethodInfo, .TestSessionContext context, <.> action) { }
752+
public . ExecuteDisposal(.TestContext context, <.> action) { }
752753
public . ExecuteTest(.TestContext context, <.> action) { }
753754
}
754755
public sealed class GenericMethodInfo
@@ -2269,6 +2270,7 @@ namespace .Interfaces
22692270
. ExecuteBeforeTestDiscoveryHook(.MethodMetadata hookMethodInfo, .BeforeTestDiscoveryContext context, <.> action);
22702271
. ExecuteBeforeTestHook(.MethodMetadata hookMethodInfo, .TestContext context, <.> action);
22712272
. ExecuteBeforeTestSessionHook(.MethodMetadata hookMethodInfo, .TestSessionContext context, <.> action);
2273+
. ExecuteDisposal(.TestContext context, <.> action);
22722274
}
22732275
public interface IHookRegisteredEventReceiver : .
22742276
{

TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,7 @@ namespace
749749
public . ExecuteBeforeTestDiscoveryHook(.MethodMetadata hookMethodInfo, .BeforeTestDiscoveryContext context, <.> action) { }
750750
public . ExecuteBeforeTestHook(.MethodMetadata hookMethodInfo, .TestContext context, <.> action) { }
751751
public . ExecuteBeforeTestSessionHook(.MethodMetadata hookMethodInfo, .TestSessionContext context, <.> action) { }
752+
public . ExecuteDisposal(.TestContext context, <.> action) { }
752753
public . ExecuteTest(.TestContext context, <.> action) { }
753754
}
754755
public sealed class GenericMethodInfo
@@ -2269,6 +2270,7 @@ namespace .Interfaces
22692270
. ExecuteBeforeTestDiscoveryHook(.MethodMetadata hookMethodInfo, .BeforeTestDiscoveryContext context, <.> action);
22702271
. ExecuteBeforeTestHook(.MethodMetadata hookMethodInfo, .TestContext context, <.> action);
22712272
. ExecuteBeforeTestSessionHook(.MethodMetadata hookMethodInfo, .TestSessionContext context, <.> action);
2273+
. ExecuteDisposal(.TestContext context, <.> action);
22722274
}
22732275
public interface IHookRegisteredEventReceiver : .
22742276
{

TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,7 @@ namespace
749749
public . ExecuteBeforeTestDiscoveryHook(.MethodMetadata hookMethodInfo, .BeforeTestDiscoveryContext context, <.> action) { }
750750
public . ExecuteBeforeTestHook(.MethodMetadata hookMethodInfo, .TestContext context, <.> action) { }
751751
public . ExecuteBeforeTestSessionHook(.MethodMetadata hookMethodInfo, .TestSessionContext context, <.> action) { }
752+
public . ExecuteDisposal(.TestContext context, <.> action) { }
752753
public . ExecuteTest(.TestContext context, <.> action) { }
753754
}
754755
public sealed class GenericMethodInfo
@@ -2269,6 +2270,7 @@ namespace .Interfaces
22692270
. ExecuteBeforeTestDiscoveryHook(.MethodMetadata hookMethodInfo, .BeforeTestDiscoveryContext context, <.> action);
22702271
. ExecuteBeforeTestHook(.MethodMetadata hookMethodInfo, .TestContext context, <.> action);
22712272
. ExecuteBeforeTestSessionHook(.MethodMetadata hookMethodInfo, .TestSessionContext context, <.> action);
2273+
. ExecuteDisposal(.TestContext context, <.> action);
22722274
}
22732275
public interface IHookRegisteredEventReceiver : .
22742276
{

TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,7 @@ namespace
726726
public . ExecuteBeforeTestDiscoveryHook(.MethodMetadata hookMethodInfo, .BeforeTestDiscoveryContext context, <.> action) { }
727727
public . ExecuteBeforeTestHook(.MethodMetadata hookMethodInfo, .TestContext context, <.> action) { }
728728
public . ExecuteBeforeTestSessionHook(.MethodMetadata hookMethodInfo, .TestSessionContext context, <.> action) { }
729+
public . ExecuteDisposal(.TestContext context, <.> action) { }
729730
public . ExecuteTest(.TestContext context, <.> action) { }
730731
}
731732
public sealed class GenericMethodInfo
@@ -2201,6 +2202,7 @@ namespace .Interfaces
22012202
. ExecuteBeforeTestDiscoveryHook(.MethodMetadata hookMethodInfo, .BeforeTestDiscoveryContext context, <.> action);
22022203
. ExecuteBeforeTestHook(.MethodMetadata hookMethodInfo, .TestContext context, <.> action);
22032204
. ExecuteBeforeTestSessionHook(.MethodMetadata hookMethodInfo, .TestSessionContext context, <.> action);
2205+
. ExecuteDisposal(.TestContext context, <.> action);
22042206
}
22052207
public interface IHookRegisteredEventReceiver : .
22062208
{

TUnit.TestProject/SetHookExecutorTests.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,36 @@ public async Task Test_StaticHooksExecuteInCustomExecutor()
117117
await Assert.That(Thread.CurrentThread.Name).IsEqualTo("CrossPlatformTestExecutor");
118118
}
119119
}
120+
121+
/// <summary>
122+
/// Tests demonstrating SetHookExecutor affects disposal execution - Issue #3918
123+
/// </summary>
124+
[EngineTest(ExpectedResult.Pass)]
125+
[SetBothExecutors] // This attribute sets both executors
126+
public class DisposalWithHookExecutorTests : IAsyncDisposable
127+
{
128+
private static bool _disposalExecutedInCustomExecutor;
129+
130+
[Test]
131+
public async Task Test_ExecutesInCustomExecutor()
132+
{
133+
// Test should execute in custom executor
134+
await Assert.That(Thread.CurrentThread.Name).IsEqualTo("CrossPlatformTestExecutor");
135+
await Assert.That(CrossPlatformTestExecutor.IsRunningInTestExecutor.Value).IsTrue();
136+
}
137+
138+
public ValueTask DisposeAsync()
139+
{
140+
// Verify disposal runs in the custom executor
141+
_disposalExecutedInCustomExecutor =
142+
Thread.CurrentThread.Name == "CrossPlatformTestExecutor" &&
143+
CrossPlatformTestExecutor.IsRunningInTestExecutor.Value;
144+
return default;
145+
}
146+
147+
[After(Class)]
148+
public static async Task VerifyDisposalRanInCustomExecutor(ClassHookContext context)
149+
{
150+
await Assert.That(_disposalExecutedInCustomExecutor).IsTrue();
151+
}
152+
}

0 commit comments

Comments
 (0)