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
BindingException incorrectly wrapping ArgumentException when using an async step #2100
Comments
Also, a quick note: we recently went from 3.1.74 to 3.3.74 and I just saw 3.4.3, but I tried all 3 and this issue is present on each one. |
Yeah, the async/await support is not the best in SpecFlow. You will find some issues here on GitHub about it. |
The exception handling code that throws the BindingException is in BindingInvoker, lines 62 to 67. So far I was able to identify the difference between synchronous and asynchronous code using the tests provided by @btvanhooser:
|
I opened #2328 with a fix proposal: Change AsyncHelpers.RunSync exception handling to throw a TargetInvocationException when an exception is thrown while executing the async task. This way exceptions thrown by synchronous and asynchronous executions are the same and can be handled by the same code. If this fix is reasonable I will proceed with other adjustments in this PR. |
I think the AsyncHelpers.RunSync is a general purpose helper to queue async calls and the additional wrapping with TargetInvocationException does not fit well into the responsibility of this helper method. But to be honest I don't fully understand the root cause of this problem yet. What happens to the "original" TargetInvocationException thrown by bindingDelegate.DynamicInvoke(invokeArgs) if this runs within AsyncHelpers.RunSync? |
@tzongithub comparing both stack traces I think the root cause is that in the asynchronous code the exception is not thrown when DynamicInvoke is called. The DynamicInvoke simply returns a Task and then the exception is thrown when GetResult is called and the inner workings of both methods is different. Asynchronous code stack trace:
Synchronous code stack trace:
About your suggestion I can either:
What's your opinion? |
@mdalepiane I debugged the execution and came to the same conclusion: for some reason only the synchronous As you pointed out, the exception handling happens in SpecFlow/Runtime/TestRunner.cs Line 333 in abf885d
Back then it was possible to distinguish the case when
Now the problem is that we cannot distinguish these two cases anymore in case of async. We simply don't have this information based on the type of the exception. Wrapping the exception into a TargetInvocationException has a few drawbacks:
I'm considering an alternative solution: get rid of the catch case of ArgumentException in https://github.com/SpecFlowOSS/SpecFlow/blob/master/TechTalk.SpecFlow/Bindings/BindingInvoker.cs#L62
What do you think? |
Do we have tests for the wanted behavior? If not, we should write them. When they are green, I am happy with any implementation. |
@tzongithub I tested with a synchronous method throwing an AggregateException there is no 2-level-unpacking because the AggregateException is wrapped in a TargetInvocationException. If this 2-level-unpacking was expected for synchronous calls I think we have another issue at hand. I believe the following code in InvokeBindingDelegateAsync would still allow us to distinguish an ArgumentException thrown by a binding failure in Delegate.DynamicInvoke. I believe any binding issue would throw an exception in line 5 that would not be wrapped. 1 protected virtual object InvokeBindingDelegateAsync(Delegate bindingDelegate, object[] invokeArgs)
2 {
3 return AsyncHelpers.RunSync(async () =>
4 {
5 var r = bindingDelegate.DynamicInvoke(invokeArgs);
6 if (r is Task t)
7 {
8 try
9 {
10 await t;
11 }
12 catch (Exception ex)
13 {
14 throw new TargetInvocationException(ex);
15 }
16 }
17 return r;
18 });
19 } Any idea on how I can force an ArgumentException when calling Delegate.DynamicInvoke to check this hypothesis? |
@mdalepiane The AggregateException handling has been added exactly due to async support here, so most probably it was never really relevant for sync cases. Hence my concern about the 2-level-unpacking might be still valid. I think sync cases might be wrapped with TargetInvocationException while async cases might be wrapped with AggregateException and hence the unpacking works right now. |
Due to time constraints we will try to look into this later in the OSS Iteration 2 |
SpecFlow Version:
Used Test Runner
Version number: SpecFlow.NUnit: 3.3.74, NUnit: 3.12.0
Project Format of the SpecFlow project
packages.config
<PackageReference>
tags.feature.cs files are generated using
SpecFlow.Tools.MsBuild.Generation
NuGet packageSpecFlowSingleFileGenerator
custom toolVisual Studio Version
Enable SpecFlowSingleFileGenerator Custom Tool
option in Visual Studio extension settingsAre the latest Visual Studio updates installed?
<Major>.<Minor>.<Patch>
.NET Framework:
Test Execution Method:
<SpecFlow> Section in app.config or content of specflow.json
Issue Description
While using LINQ in a test project of ours, I found out that I accidentally allowed an ArgumentNullException to be thrown due to an list never being instantiated. The problem was that this was in a step that was using the async/await pattern and return a Task and the exception that made it to the TestExplorer was "BindingException", but with the correct message within it. This was confusing, so while trying to dig in and find the specific reason for that I create a simple synchronous step that just had that same issue and it appropriately recorded an ArgumentNullException on the TestExplorer this time. I've completely isolated this issue with this test project.
Steps to Reproduce
Repro Project
Here ya go
The text was updated successfully, but these errors were encountered: