Skip to content

Commit 46898d3

Browse files
authored
Override Results from an AfterTestContext object (#2401)
* Override Results from an `AfterTestContext` object * Update snaps
1 parent 89a6bf9 commit 46898d3

21 files changed

+192
-43
lines changed

TUnit.Core.SourceGenerator.Tests/TUnit.Core.SourceGenerator.Tests.csproj

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
<ItemGroup>
66
<ProjectReference Include="..\TUnit.Core.SourceGenerator\TUnit.Core.SourceGenerator.csproj" />
7-
<ProjectReference Include="..\TUnit.Core\TUnit.Core.csproj" ReferenceOutputAssembly="false" />
7+
<ProjectReference Include="..\TUnit.Core\TUnit.Core.csproj" />
8+
<ProjectReference Include="..\TUnit.TestProject.Library\TUnit.TestProject.Library.csproj" />
89
</ItemGroup>
910
<ItemGroup>
1011
<PackageReference Include="Microsoft.CodeAnalysis.SourceGenerators.Testing" />
@@ -18,9 +19,6 @@
1819
<None Include="..\TUnit.Core\bin\$(Configuration)\netstandard2.0\TUnit.Core.dll">
1920
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
2021
</None>
21-
<None Include="..\TUnit.TestProject.Library\bin\$(Configuration)\$(LibraryTargetFramework)\TUnit.TestProject.Library.dll">
22-
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
23-
</None>
2422
</ItemGroup>
2523

2624
<Import Project="..\TestProject.targets" />

TUnit.Core/AfterTestContext.cs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using TUnit.Core.Enums;
2+
3+
namespace TUnit.Core;
4+
5+
public class AfterTestContext
6+
{
7+
internal readonly DiscoveredTest DiscoveredTest;
8+
9+
internal AfterTestContext(DiscoveredTest discoveredTest)
10+
{
11+
DiscoveredTest = discoveredTest;
12+
}
13+
14+
public TestContext TestContext => DiscoveredTest.TestContext;
15+
public TestDetails TestDetails => TestContext.TestDetails;
16+
17+
public void OverrideResult(Status status, string reason)
18+
{
19+
var testResult = TestContext.Result;
20+
21+
if (testResult is null)
22+
{
23+
throw new InvalidOperationException("There is no test result to override.");
24+
}
25+
26+
OverrideResult(testResult with
27+
{
28+
Status = status,
29+
IsOverridden = true,
30+
OverrideReason = reason
31+
});
32+
33+
if(status == Status.Skipped)
34+
{
35+
TestContext.SkipReason = reason;
36+
}
37+
}
38+
39+
public void OverrideResult(Exception exception, string reason)
40+
{
41+
var testResult = TestContext.Result;
42+
43+
if (testResult is null)
44+
{
45+
throw new InvalidOperationException("There is no test result to override.");
46+
}
47+
48+
OverrideResult(testResult with
49+
{
50+
Status = Status.Failed,
51+
Exception = exception,
52+
IsOverridden = true,
53+
OverrideReason = reason,
54+
});
55+
}
56+
57+
private void OverrideResult(TestResult result)
58+
{
59+
TestContext.Result = result;
60+
}
61+
62+
public static implicit operator TestContext(AfterTestContext afterTestContext) => afterTestContext.TestContext;
63+
}

TUnit.Core/Attributes/TestData/ClassDataSources.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ private static object Create([DynamicallyAccessedMembers(DynamicallyAccessedMemb
261261
}
262262
}
263263

264-
public async Task OnTestEnd<T>(TestContext context, T item) where T : new()
264+
public async Task OnTestEnd<T>(AfterTestContext context, T item) where T : new()
265265
{
266266
if (item is ITestEndEventReceiver testEndEventReceiver)
267267
{

TUnit.Core/BeforeTestContext.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,6 @@ public void AddLinkedCancellationToken(CancellationToken cancellationToken)
3030
}
3131

3232
public void AddAsyncLocalValues() => TestContext.AddAsyncLocalValues();
33+
34+
public static implicit operator TestContext(BeforeTestContext beforeTestContext) => beforeTestContext.TestContext;
3335
}

TUnit.Core/Interfaces/ITestEndEventReceiver.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22

33
public interface ITestEndEventReceiver : IEventReceiver
44
{
5-
ValueTask OnTestEnd(TestContext testContext);
5+
ValueTask OnTestEnd(AfterTestContext afterTestContext);
66
}

TUnit.Core/TestContext.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
namespace TUnit.Core;
1+
using System.Diagnostics;
2+
3+
namespace TUnit.Core;
24

35
/// <summary>
46
/// Represents the context for a test.
57
/// </summary>
8+
[DebuggerDisplay("{TestDetails.TestClass.Name}.{TestDetails.TestName}")]
69
public partial class TestContext : Context
710
{
811
private readonly IServiceProvider _serviceProvider;

TUnit.Core/TestContextEvents.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public record TestContextEvents :
2323
public AsyncEvent<TestRegisteredContext>? OnTestRegistered { get; set; }
2424
public AsyncEvent<TestContext>? OnInitialize { get; set; }
2525
public AsyncEvent<BeforeTestContext>? OnTestStart { get; set; }
26-
public AsyncEvent<TestContext>? OnTestEnd { get; set; }
26+
public AsyncEvent<AfterTestContext>? OnTestEnd { get; set; }
2727
public AsyncEvent<TestContext>? OnTestSkipped { get; set; }
2828
public AsyncEvent<(ClassHookContext, TestContext)>? OnLastTestInClass { get; set; }
2929
public AsyncEvent<(AssemblyHookContext, TestContext)>? OnLastTestInAssembly { get; set; }
@@ -40,7 +40,7 @@ ValueTask ITestStartEventReceiver.OnTestStart(BeforeTestContext beforeTestContex
4040
return OnTestStart?.InvokeAsync(this, beforeTestContext) ?? default;
4141
}
4242

43-
ValueTask ITestEndEventReceiver.OnTestEnd(TestContext testContext)
43+
ValueTask ITestEndEventReceiver.OnTestEnd(AfterTestContext testContext)
4444
{
4545
return OnTestEnd?.InvokeAsync(this, testContext) ?? default;
4646
}

TUnit.Core/TestResult.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,7 @@ public record TestResult
4848
/// </summary>
4949
[JsonIgnore]
5050
internal TestContext? TestContext { get; init; }
51+
52+
public string? OverrideReason { get; set; }
53+
public bool IsOverridden { get; set; }
5154
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using Shouldly;
2+
using TUnit.Engine.Tests.Enums;
3+
4+
namespace TUnit.Engine.Tests;
5+
6+
public class OverrideResultsTests(TestMode testMode) : InvokableTestBase(testMode)
7+
{
8+
[Test]
9+
public async Task Test()
10+
{
11+
await RunTestsWithFilter(
12+
"/*/*/OverrideResultsTests/*",
13+
[
14+
result => result.ResultSummary.Outcome.ShouldBe("Completed"),
15+
result => result.ResultSummary.Counters.Total.ShouldBe(1),
16+
result => result.ResultSummary.Counters.Passed.ShouldBe(1),
17+
result => result.ResultSummary.Counters.Failed.ShouldBe(0),
18+
result => result.ResultSummary.Counters.NotExecuted.ShouldBe(0)
19+
]);
20+
}
21+
}

TUnit.Engine/Services/SingleTestExecutor.cs

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,17 @@ private async ValueTask ExecuteTestInternalAsync(DiscoveredTest test, ITestExecu
109109
await logger.LogInformationAsync($"Skipping {testContext.GetClassTypeName()}.{testContext.GetTestDisplayName()}...");
110110

111111
testContext.SetResult(skipTestException);
112-
113-
await messageBus.Skipped(testContext, skipTestException.Reason);
114112
}
115113
catch (Exception e)
116114
{
117-
await logger.LogDebugAsync($"Error in test {testContext.TestDetails.TestClass.Type.FullName}.{testContext.GetTestDisplayName()}: {e}");
118-
testContext.SetResult(e);
119-
throw;
115+
if (testContext.Result is null
116+
|| testContext.Result?.IsOverridden is false
117+
|| testContext.Result?.Status is Status.Failed or Status.Cancelled)
118+
{
119+
await logger.LogDebugAsync($"Error in test {testContext.TestDetails.TestClass.Type.FullName}.{testContext.GetTestDisplayName()}: {e}");
120+
testContext.SetResult(e);
121+
throw;
122+
}
120123
}
121124
finally
122125
{
@@ -134,29 +137,14 @@ private async ValueTask ExecuteTestInternalAsync(DiscoveredTest test, ITestExecu
134137
Status.Passed => messageBus.Passed(test.TestContext, start.GetValueOrDefault()),
135138
Status.Failed => messageBus.Failed(test.TestContext, result.Exception!, start.GetValueOrDefault()),
136139
Status.Cancelled => messageBus.Cancelled(test.TestContext, start.GetValueOrDefault()),
140+
Status.Skipped => messageBus.Skipped(test.TestContext, test.TestContext.SkipReason!),
137141
_ => default,
138142
};
139143

140144
await task;
141145
}
142146
}
143147

144-
private bool IsCancelled(Exception ex)
145-
{
146-
if (ex is TestRunCanceledException)
147-
{
148-
return true;
149-
}
150-
151-
if (ex is TaskCanceledException or OperationCanceledException
152-
&& engineCancellationToken.Token.IsCancellationRequested)
153-
{
154-
return true;
155-
}
156-
157-
return false;
158-
}
159-
160148
private async Task RunFirstTestEventReceivers(TestContext testContext)
161149
{
162150
ExecutionContextHelper.RestoreContext(await RunFirstTestInSessionEventReceivers(testContext));

0 commit comments

Comments
 (0)