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

append loader exceptions to error output #1296

Merged
21 changes: 19 additions & 2 deletions TechTalk.SpecFlow/TestRunnerManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class TestRunnerManager : ITestRunnerManager
protected readonly Configuration.SpecFlowConfiguration specFlowConfiguration;
protected readonly IRuntimeBindingRegistryBuilder bindingRegistryBuilder;

private readonly ITestTracer testTracer;
private readonly Dictionary<int, ITestRunner> testRunnerRegistry = new Dictionary<int, ITestRunner>();
private readonly object syncRoot = new object();
private bool isTestRunInitialized;
Expand All @@ -36,12 +37,14 @@ public class TestRunnerManager : ITestRunnerManager

public bool IsMultiThreaded { get { return testRunnerRegistry.Count > 1; } }

public TestRunnerManager(IObjectContainer globalContainer, IContainerBuilder containerBuilder, Configuration.SpecFlowConfiguration specFlowConfiguration, IRuntimeBindingRegistryBuilder bindingRegistryBuilder)
public TestRunnerManager(IObjectContainer globalContainer, IContainerBuilder containerBuilder, Configuration.SpecFlowConfiguration specFlowConfiguration, IRuntimeBindingRegistryBuilder bindingRegistryBuilder,
ITestTracer testTracer)
{
this.globalContainer = globalContainer;
this.containerBuilder = containerBuilder;
this.specFlowConfiguration = specFlowConfiguration;
this.bindingRegistryBuilder = bindingRegistryBuilder;
this.testTracer = testTracer;
}

public virtual ITestRunner CreateTestRunner(int threadId)
Expand Down Expand Up @@ -116,7 +119,21 @@ public void Initialize(Assembly assignedTestAssembly)
TestAssembly = assignedTestAssembly;
}

public virtual ITestRunner GetTestRunner(int threadId)
public virtual ITestRunner GetTestRunner(int threadId)
{
try
{
return GetTestRunnerWithoutExceptionHandling(threadId);

}
catch (Exception ex)
{
testTracer.TraceError(ex);
throw;
}
}

private ITestRunner GetTestRunnerWithoutExceptionHandling(int threadId)
{
ITestRunner testRunner;
if (!testRunnerRegistry.TryGetValue(threadId, out testRunner))
Expand Down
41 changes: 36 additions & 5 deletions TechTalk.SpecFlow/Tracing/TestTracer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using TechTalk.SpecFlow.BindingSkeletons;
using TechTalk.SpecFlow.Bindings;
using TechTalk.SpecFlow.Bindings.Reflection;
using TechTalk.SpecFlow.Configuration;

namespace TechTalk.SpecFlow.Tracing
{
Expand Down Expand Up @@ -53,7 +52,7 @@ public void TraceWarning(string text)

public void TraceStepDone(BindingMatch match, object[] arguments, TimeSpan duration)
{
traceListener.WriteToolOutput("done: {0} ({1:F1}s)",
traceListener.WriteToolOutput("done: {0} ({1:F1}s)",
stepFormatter.GetMatchText(match, arguments), duration.TotalSeconds);
}

Expand All @@ -75,7 +74,39 @@ public void TraceBindingError(BindingException ex)

public void TraceError(Exception ex)
{
traceListener.WriteToolOutput("error: {0}", ex.Message);
WriteErrorMessage(ex.Message);
WriteLoaderExceptionsIfAny(ex);
}

private void WriteLoaderExceptionsIfAny(Exception ex)
{
switch (ex)
{
case TypeInitializationException typeInitializationException:
WriteLoaderExceptionsIfAny(typeInitializationException.InnerException);
break;
case ReflectionTypeLoadException reflectionTypeLoadException:
WriteErrorMessage("Type Loader exceptions:");
FormatLoaderExceptions(reflectionTypeLoadException);
break;
}
}

private void FormatLoaderExceptions(ReflectionTypeLoadException reflectionTypeLoadException)
{
var exceptions = reflectionTypeLoadException.LoaderExceptions
.Select(x => x.ToString())
.Distinct()
.Select(x => $"LoaderException: {x}");
foreach (var ex in exceptions)
{
WriteErrorMessage(ex);
}
}

private void WriteErrorMessage(string ex)
{
traceListener.WriteToolOutput("error: {0}", ex);
}

public void TraceNoMatchingStepDefinition(StepInstance stepInstance, ProgrammingLanguage targetLanguage, CultureInfo bindingCulture, List<BindingMatch> matchesWithoutScopeCheck)
Expand All @@ -85,7 +116,7 @@ public void TraceNoMatchingStepDefinition(StepInstance stepInstance, Programming
message.AppendLine("No matching step definition found for the step. Use the following code to create one:");
else
{
string preMessage = string.Format("No matching step definition found for the step. There are matching step definitions, but none of them have matching scope for this step: {0}.",
string preMessage = string.Format("No matching step definition found for the step. There are matching step definitions, but none of them have matching scope for this step: {0}.",
string.Join(", ", matchesWithoutScopeCheck.Select(m => stepFormatter.GetMatchText(m, null)).ToArray()));
traceListener.WriteToolOutput(preMessage);
message.AppendLine("Change the scope or use the following code to create a new step definition:");
Expand All @@ -99,7 +130,7 @@ public void TraceNoMatchingStepDefinition(StepInstance stepInstance, Programming

public void TraceDuration(TimeSpan elapsed, IBindingMethod method, object[] arguments)
{
traceListener.WriteToolOutput("duration: {0}: {1:F1}s",
traceListener.WriteToolOutput("duration: {0}: {1:F1}s",
stepFormatter.GetMatchText(method, arguments), elapsed.TotalSeconds);
}

Expand Down
66 changes: 66 additions & 0 deletions Tests/TechTalk.SpecFlow.IntegrationTests/Features/Tracing.feature
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,69 @@ Scenario: Preserves step keywords in trace
When I execute the tests
Then the execution log should contain text 'Angenommen ich Knopf 1 drücke'
And the execution log should contain text 'Gegeben sei ich Knopf 2 drücke'

Scenario: ReflectionTypeLoaderException in a step binding
Given there is a feature file in the project as
"""
Feature: f
Scenario: s
When ...
"""
And the following step definition
"""
[When(@".*")]
public void When___()
{
throw new System.Reflection.ReflectionTypeLoadException(
new System.Type[3],
new[]
{
new System.Exception("crash"),
new System.Exception("boom"),
new System.Exception("bang"),
});
}
"""
When I execute the tests
Then the execution log should contain text '-> error: Type Loader exceptions:'
And the execution log should contain text '-> error: LoaderException: System.Exception: crash'
And the execution log should contain text '-> error: LoaderException: System.Exception: boom'
And the execution log should contain text '-> error: LoaderException: System.Exception: bang'

Scenario: ReflectionTypeLoaderException in a static constructor
Given the following class
"""
public class Class1
{
static Class1()
{
throw new System.Reflection.ReflectionTypeLoadException(
new System.Type[3],
new[]
{
new System.Exception("crash"),
new System.Exception("boom"),
new System.Exception("bang"),
});
}
}
"""
And there is a feature file in the project as
"""
Feature: f
Scenario: s
When ...
"""
And the following step definition
"""
[When(@".*")]
public void When___()
{
System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(Class1).TypeHandle);
}
"""
When I execute the tests
Then the execution log should contain text '-> error: Type Loader exceptions:'
And the execution log should contain text '-> error: LoaderException: System.Exception: crash'
And the execution log should contain text '-> error: LoaderException: System.Exception: boom'
And the execution log should contain text '-> error: LoaderException: System.Exception: bang'
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ private TestRunnerManager CreateTestRunnerFactory()

var runtimeBindingRegistryBuilderMock = new Mock<IRuntimeBindingRegistryBuilder>();

var testRunnerManager = new TestRunnerManager(globalObjectContainerStub.Object, testRunContainerBuilderStub.Object, _specFlowConfigurationStub, runtimeBindingRegistryBuilderMock.Object);
var testRunnerManager = new TestRunnerManager(globalObjectContainerStub.Object, testRunContainerBuilderStub.Object, _specFlowConfigurationStub, runtimeBindingRegistryBuilderMock.Object,
Mock.Of<ITestTracer>());
testRunnerManager.Initialize(anAssembly);
return testRunnerManager;
}
Expand Down
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ New Features:
+ Check for non-default constructors using case-insensitive comparison https://github.com/techtalk/SpecFlow/pull/1265
+ Syntax and structural changes in value retriever/comparer registration https://github.com/techtalk/SpecFlow/pull/1260
+ Add utility to get enum field value from TableRow.
+ Loader exceptions are appended to the exception message https://github.com/techtalk/SpecFlow/issues/1293

2.4 - 2018-08-20
New Features:
Expand Down