Skip to content

Commit

Permalink
Fix #1433: Only use async waiting for async step definitions (#1449)
Browse files Browse the repository at this point in the history
* move AsyncHelpers to Bindings folder

* Only call bindings with AsyncHelpers when they are actually async (return Task)

* extract synchronous binding invocation to a service, to be able to customize it more easily
  • Loading branch information
gasparnagy authored and SabotageAndi committed Mar 27, 2019
1 parent 99557a6 commit f3ee016
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 45 deletions.
Expand Up @@ -4,7 +4,7 @@
using System.Threading;
using System.Threading.Tasks;

namespace TechTalk.SpecFlow
namespace TechTalk.SpecFlow.Bindings
{
/// <summary>
/// https://stackoverflow.com/questions/5095183/how-would-i-run-an-async-taskt-method-synchronously#answer-5097066
Expand Down
16 changes: 6 additions & 10 deletions TechTalk.SpecFlow/Bindings/BindingInvoker.cs
Expand Up @@ -5,6 +5,7 @@
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
using TechTalk.SpecFlow.Bindings.Reflection;
using TechTalk.SpecFlow.Compatibility;
using TechTalk.SpecFlow.Configuration;
Expand All @@ -14,17 +15,17 @@

namespace TechTalk.SpecFlow.Bindings
{
using System.Threading.Tasks;

public class BindingInvoker : IBindingInvoker
{
protected readonly Configuration.SpecFlowConfiguration specFlowConfiguration;
protected readonly IErrorProvider errorProvider;
protected readonly ISynchronousBindingDelegateInvoker synchronousBindingDelegateInvoker;

public BindingInvoker(Configuration.SpecFlowConfiguration specFlowConfiguration, IErrorProvider errorProvider)
public BindingInvoker(Configuration.SpecFlowConfiguration specFlowConfiguration, IErrorProvider errorProvider, ISynchronousBindingDelegateInvoker synchronousBindingDelegateInvoker)
{
this.specFlowConfiguration = specFlowConfiguration;
this.errorProvider = errorProvider;
this.synchronousBindingDelegateInvoker = synchronousBindingDelegateInvoker;
}

public virtual object InvokeBinding(IBinding binding, IContextManager contextManager, object[] arguments, ITestTracer testTracer, out TimeSpan duration)
Expand All @@ -45,13 +46,8 @@ public virtual object InvokeBinding(IBinding binding, IContextManager contextMan
Array.Copy(arguments, 0, invokeArgs, 1, arguments.Length);
invokeArgs[0] = contextManager;

result = AsyncHelpers.RunSync(async () =>
{
var r = bindingAction.DynamicInvoke(invokeArgs);
if (r is Task t)
await t;
return r;
});
result = synchronousBindingDelegateInvoker
.InvokeDelegateSynchronously(bindingAction, invokeArgs);

stopwatch.Stop();
}
Expand Down
37 changes: 37 additions & 0 deletions TechTalk.SpecFlow/Bindings/SynchronousBindingDelegateInvoker.cs
@@ -0,0 +1,37 @@
using System;
using System.Threading.Tasks;

namespace TechTalk.SpecFlow.Bindings
{
public interface ISynchronousBindingDelegateInvoker
{
object InvokeDelegateSynchronously(Delegate bindingDelegate, object[] invokeArgs);
}

public class SynchronousBindingDelegateInvoker : ISynchronousBindingDelegateInvoker
{
public virtual object InvokeDelegateSynchronously(Delegate bindingDelegate, object[] invokeArgs)
{
if (typeof(Task).IsAssignableFrom(bindingDelegate.Method.ReturnType))
return InvokeBindingDelegateAsync(bindingDelegate, invokeArgs);
return InvokeBindingDelegateSync(bindingDelegate, invokeArgs);
}

protected virtual object InvokeBindingDelegateSync(Delegate bindingDelegate, object[] invokeArgs)
{
return bindingDelegate.DynamicInvoke(invokeArgs);
}

protected virtual object InvokeBindingDelegateAsync(Delegate bindingDelegate, object[] invokeArgs)
{
return AsyncHelpers.RunSync(async () =>
{
var r = bindingDelegate.DynamicInvoke(invokeArgs);
if (r is Task t)
await t;
return r;
});
}

}
}
Expand Up @@ -34,6 +34,7 @@ public virtual void RegisterGlobalContainerDefaults(ObjectContainer container)
container.RegisterTypeAs<BindingFactory, IBindingFactory>();
container.RegisterTypeAs<StepDefinitionRegexCalculator, IStepDefinitionRegexCalculator>();
container.RegisterTypeAs<BindingInvoker, IBindingInvoker>();
container.RegisterTypeAs<SynchronousBindingDelegateInvoker, ISynchronousBindingDelegateInvoker>();
container.RegisterTypeAs<TestObjectResolver, ITestObjectResolver>();

container.RegisterTypeAs<StepDefinitionSkeletonProvider, IStepDefinitionSkeletonProvider>();
Expand Down
28 changes: 0 additions & 28 deletions Tests/TechTalk.SpecFlow.RuntimeTests/AsyncHelpersTests.cs

This file was deleted.

23 changes: 21 additions & 2 deletions Tests/TechTalk.SpecFlow.RuntimeTests/Bindings/AsyncHelperTests.cs
@@ -1,21 +1,27 @@
using System;
using System.Threading.Tasks;
using FluentAssertions;
using TechTalk.SpecFlow.Bindings;
using Xunit;

namespace TechTalk.SpecFlow.RuntimeTests.Bindings
{
public class AsyncHelperTests
{
public void NormalMethod()
private void NormalMethod()
{

}

public void ThrowException()
private void ThrowException()
{
throw new Exception("Hi from async");
}

private Task AsyncMethodThrowsException()
{
throw new Exception();
}

[Fact]
public void RunSync_NormalMethod()
Expand All @@ -31,5 +37,18 @@ public void RunSync_ExceptionIsThrown()

action.Should().Throw<Exception>().WithMessage("Hi from async");
}

[Fact]
public void RunSync_WhenExceptionIsThrown_ShouldRestoreOriginalStackTrace()
{
try
{
AsyncHelpers.RunSync(AsyncMethodThrowsException);
}
catch (Exception ex)
{
ex.StackTrace.Should().StartWith($" at {GetType().FullName}.{nameof(AsyncMethodThrowsException)}()");
}
}
}
}
Expand Up @@ -99,7 +99,7 @@ public void UserConverterShouldConvertStringToUser()

stepTransformationBinding.Regex.IsMatch("user xyz").Should().BeTrue();

var invoker = new BindingInvoker(ConfigurationLoader.GetDefault(), new Mock<IErrorProvider>().Object);
var invoker = new BindingInvoker(ConfigurationLoader.GetDefault(), new Mock<IErrorProvider>().Object, new SynchronousBindingDelegateInvoker());
TimeSpan duration;
var result = invoker.InvokeBinding(stepTransformationBinding, contextManagerStub.Object, new object[] { "xyz" }, new Mock<ITestTracer>().Object, out duration);
Assert.NotNull(result);
Expand All @@ -116,7 +116,7 @@ public void TypeToTypeConverterShouldConvertStringToStringUsingRegex()

Assert.True(stepTransformationBinding.Regex.IsMatch("string xyz"));

var invoker = new BindingInvoker(ConfigurationLoader.GetDefault(), new Mock<IErrorProvider>().Object);
var invoker = new BindingInvoker(ConfigurationLoader.GetDefault(), new Mock<IErrorProvider>().Object, new SynchronousBindingDelegateInvoker());
TimeSpan duration;
var result = invoker.InvokeBinding(stepTransformationBinding, contextManagerStub.Object, new object[] { "xyz" }, new Mock<ITestTracer>().Object, out duration);
Assert.NotNull(result);
Expand All @@ -131,7 +131,7 @@ public void TypeToTypeConverterShouldConvertStringToString()
var transformMethod = stepTransformationInstance.GetType().GetMethod("StringToStringConvert");
var stepTransformationBinding = CreateStepTransformationBinding(@"", transformMethod);

var invoker = new BindingInvoker(ConfigurationLoader.GetDefault(), new Mock<IErrorProvider>().Object);
var invoker = new BindingInvoker(ConfigurationLoader.GetDefault(), new Mock<IErrorProvider>().Object, new SynchronousBindingDelegateInvoker());
TimeSpan duration;
var result = invoker.InvokeBinding(stepTransformationBinding, contextManagerStub.Object, new object[] { "xyz" }, new Mock<ITestTracer>().Object, out duration);
Assert.NotNull(result);
Expand All @@ -146,7 +146,7 @@ public void TypeToTypeConverterShouldConvertTableToTable()
var transformMethod = stepTransformationInstance.GetType().GetMethod("TableToTableConvert");
var stepTransformationBinding = CreateStepTransformationBinding(@"", transformMethod);

var invoker = new BindingInvoker(ConfigurationLoader.GetDefault(), new Mock<IErrorProvider>().Object);
var invoker = new BindingInvoker(ConfigurationLoader.GetDefault(), new Mock<IErrorProvider>().Object, new SynchronousBindingDelegateInvoker());
TimeSpan duration;
var result = invoker.InvokeBinding(stepTransformationBinding, contextManagerStub.Object, new object[] { new Table("h1") }, new Mock<ITestTracer>().Object, out duration);
Assert.NotNull(result);
Expand Down

0 comments on commit f3ee016

Please sign in to comment.