Skip to content

Commit

Permalink
GD-104: Fix exception failure message propagation (#108)
Browse files Browse the repository at this point in the history
# Why
see #106

# What
fixing the extraction of inner exception to catch the right exception
message
  • Loading branch information
MikeSchulze authored Jun 3, 2024
1 parent edb26bd commit aee3ae6
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 12 deletions.
24 changes: 13 additions & 11 deletions api/src/core/execution/ExecutionStage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public virtual async Task Execute(ExecutionContext context)
{
ReportAsFailure(context, e);
}
catch (TargetInvocationException e)
catch (Exception e)
{
if (e.GetBaseException() is TestFailedException ex)
{
Expand All @@ -76,11 +76,6 @@ public virtual async Task Execute(ExecutionContext context)
ReportUnexpectedException(context, e);
}
}
// unexpected exceptions
catch (Exception e)
{
ReportUnexpectedException(context, e);
}
}

private static void ReportAsFailure(ExecutionContext context, TestFailedException e)
Expand All @@ -91,10 +86,17 @@ private static void ReportAsFailure(ExecutionContext context, TestFailedExceptio

private static void ReportUnexpectedException(ExecutionContext context, Exception exception)
{
var ei = ExceptionDispatchInfo.Capture(exception.InnerException ?? exception);
var stack = new StackTrace(ei.SourceException, true);
var lineNumber = ScanFailureLineNumber(stack);
context.ReportCollector.Consume(new TestReport(ReportType.FAILURE, lineNumber, ei.SourceException.Message, TrimStackTrace(stack.ToString())));
if (exception is TargetInvocationException)
{
var ei = ExceptionDispatchInfo.Capture(exception.InnerException ?? exception);
ReportUnexpectedException(context, ei.SourceException);
}
else
{
var stack = new StackTrace(exception, true);
var lineNumber = ScanFailureLineNumber(stack);
context.ReportCollector.Consume(new TestReport(ReportType.FAILURE, lineNumber, exception.Message, TrimStackTrace(stack.ToString())));
}
}

private static int ScanFailureLineNumber(StackTrace stack)
Expand All @@ -106,7 +108,7 @@ private static int ScanFailureLineNumber(StackTrace stack)
if (frame.GetMethod()?.IsDefined(typeof(TestCaseAttribute)) ?? false)
return frame.GetFileLineNumber();
}
return stack.FrameCount > 1 ? stack.GetFrame(1)!.GetFileLineNumber() : -1;
return stack.FrameCount > 1 ? stack.GetFrame(0)!.GetFileLineNumber() : -1;
}

internal static string TrimStackTrace(string stackTrace)
Expand Down
1 change: 0 additions & 1 deletion api/src/core/execution/TestSuiteExecutionStage.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
namespace GdUnit4.Executions;

using System.Threading.Tasks;
using System.Linq;

internal sealed class TestSuiteExecutionStage : IExecutionStage
{
Expand Down
3 changes: 3 additions & 0 deletions test/.runsettings
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
<TargetFrameworks>net7.0;net8.0</TargetFrameworks>
<TestSessionTimeout>300000</TestSessionTimeout>
<TreatNoTestsAsError>true</TreatNoTestsAsError>
<EnvironmentVariables>
<GODOT_BIN>d:\development\Godot_v4.2.1-stable_mono_win64\Godot_v4.2.1-stable_mono_win64.exe</GODOT_BIN>
</EnvironmentVariables>
</RunConfiguration>

<LoggerRunSettings>
Expand Down
49 changes: 49 additions & 0 deletions test/src/core/ExecutorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -728,4 +728,53 @@ public async Task ExecuteParameterizedTest()
Tuple(TESTSUITE_AFTER, "After", new List<TestReport>())
);
}

[TestCase(Description = "Verifies the exceptions are catches the right message as failure.")]
public async Task ExecuteTestWithExceptions()
{
var testSuite = LoadTestSuite("src/core/resources/testsuites/mono/TestSuiteAllTestsFailWithExceptions.cs");
AssertArray(testSuite.TestCases).Extract("Name").ContainsExactly(new string[] { "ExceptionIsThrownOnSceneInvoke", "ExceptionAtAsyncMethod", "ExceptionAtSyncMethod" });

var events = await ExecuteTestSuite(testSuite);

AssertTestCaseNames(events)
.ContainsExactly(ExpectedEvents("TestSuiteAllTestsFailWithExceptions", "ExceptionIsThrownOnSceneInvoke", "ExceptionAtAsyncMethod", "ExceptionAtSyncMethod"));

// we expect all tests are failing and commits failures
AssertEventCounters(events).ContainsExactly(
Tuple(TESTSUITE_BEFORE, "Before", 0, 0, 0),
Tuple(TESTCASE_BEFORE, "ExceptionIsThrownOnSceneInvoke", 0, 0, 0),
Tuple(TESTCASE_AFTER, "ExceptionIsThrownOnSceneInvoke", 0, 1, 0),
Tuple(TESTCASE_BEFORE, "ExceptionAtAsyncMethod", 0, 0, 0),
Tuple(TESTCASE_AFTER, "ExceptionAtAsyncMethod", 0, 1, 0),
Tuple(TESTCASE_BEFORE, "ExceptionAtSyncMethod", 0, 0, 0),
Tuple(TESTCASE_AFTER, "ExceptionAtSyncMethod", 0, 1, 0),
Tuple(TESTSUITE_AFTER, "After", 0, 0, 0)
);
AssertEventStates(events).ContainsExactly(
Tuple(TESTSUITE_BEFORE, "Before", true, false, false, false),
Tuple(TESTCASE_BEFORE, "ExceptionIsThrownOnSceneInvoke", true, false, false, false),
Tuple(TESTCASE_AFTER, "ExceptionIsThrownOnSceneInvoke", false, false, true, false),
Tuple(TESTCASE_BEFORE, "ExceptionAtAsyncMethod", true, false, false, false),
Tuple(TESTCASE_AFTER, "ExceptionAtAsyncMethod", false, false, true, false),
Tuple(TESTCASE_BEFORE, "ExceptionAtSyncMethod", true, false, false, false),
Tuple(TESTCASE_AFTER, "ExceptionAtSyncMethod", false, false, true, false),
// report suite is not success, is failed
Tuple(TESTSUITE_AFTER, "After", false, false, true, false)
);
// check for failure reports
AssertReports(events).Contains(
Tuple(TESTSUITE_BEFORE, "Before", new List<TestReport>()),
Tuple(TESTCASE_AFTER, "ExceptionIsThrownOnSceneInvoke", new List<TestReport>() { new(FAILURE, 12, """
Test Exception
""") }),
Tuple(TESTCASE_AFTER, "ExceptionAtAsyncMethod", new List<TestReport>() { new(FAILURE, 24, """
outer exception
""") }),
Tuple(TESTCASE_AFTER, "ExceptionAtSyncMethod", new List<TestReport>() { new(FAILURE, 28, """
outer exception
""") }),
Tuple(TESTSUITE_AFTER, "After", new List<TestReport>()));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace GdUnit4.Tests.Core;

using System;
using System.Threading.Tasks;


// will be ignored because of missing `[TestSuite]` annotation
// used by executor integration test
public class TestSuiteAllTestsFailWithExceptions
{

[TestCase]
public void ExceptionIsThrownOnSceneInvoke()
{
var runner = ISceneRunner.Load("res://src/core/resources/scenes/TestSceneWithExceptionTest.tscn");

runner.Invoke("SomeMethodThatThrowsException");
}

[TestCase]
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
public async Task ExceptionAtAsyncMethod()
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
=> throw new ArgumentException("outer exception", new ArgumentNullException("inner exception"));

[TestCase]
public void ExceptionAtSyncMethod()
=> throw new ArgumentException("outer exception", new ArgumentNullException("inner exception"));

}

0 comments on commit aee3ae6

Please sign in to comment.