Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GD-104: Fix exception failure message propagation #108

Merged
merged 1 commit into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"));

}