SpecFlow
SpecFlow copied to clipboard
StepArgumentTransformation stack overflow exception
SpecFlow Version
VS2022
Which test runner are you using?
NUnit
Test Runner Version Number
3.13.2
.NET Implementation
.NET 5.0
Project Format of the SpecFlow project
Classic project format using <PackageReference>
tags
.feature.cs files are generated using
SpecFlow.Tools.MsBuild.Generation NuGet package
Test Execution Method
Visual Studio Test Explorer
SpecFlow Section in app.config or content of specflow.json
No response
Issue Description
StepArgumentTransformation throws stack overflow exception whenever one tries to filter parameter data. Further details provided on SpecFlow forum here:
https://support.specflow.org/hc/en-us/community/posts/4423434386577-StepArgumentTransformation-stack-overflow-exception?page=1#community_comment_4436919840401
Exception:
The active test run was aborted. Reason: Test host process crashed : Stack overflow.
at System.Text.RegularExpressions.Match.get_Groups()
at TechTalk.SpecFlow.Bindings.StepArgumentTypeConverter.GetStepTransformationArgumentsFromRegex(TechTalk.SpecFlow.Bindings.IStepArgumentTransformationBinding, System.String, System.Globalization.CultureInfo)
at TechTalk.SpecFlow.Bindings.StepArgumentTypeConverter.DoTransform(TechTalk.SpecFlow.Bindings.IStepArgumentTransformationBinding, System.Object, System.Globalization.CultureInfo)
at TechTalk.SpecFlow.Bindings.StepArgumentTypeConverter.Convert(System.Object, TechTalk.SpecFlow.Bindings.Reflection.IBindingType, System.Globalization.CultureInfo)
at TechTalk.SpecFlow.Bindings.StepArgumentTypeConverter+<>c__DisplayClass8_0.<GetStepTransformationArgumentsFromRegex>b__1(System.String, Int32)
at System.Linq.Enumerable+<SelectIterator>d__174`2[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext()
at System.Collections.Generic.LargeArrayBuilder`1[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].AddRange(System.Collections.Generic.IEnumerable`1<System.__Canon>)
at System.Collections.Generic.EnumerableHelpers.ToArray[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](System.Collections.Generic.IEnumerable`1<System.__Canon>)
at System.Linq.Enumerable.ToArray[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](System.Collections.Generic.IEnumerable`1<System.__Canon>)
at TechTalk.SpecFlow.Bindings.StepArgumentTypeConverter.GetStepTransformationArgumentsFromRegex(TechTalk.SpecFlow.Bindings.IStepArgumentTransformationBinding, System.String, System.Globalization.CultureInfo)
at TechTalk.SpecFlow.Bindings.StepArgumentTypeConverter.DoTransform(TechTalk.SpecFlow.Bindings.IStepArgumentTransformationBinding, System.Object, System.Globalization.CultureInfo)
at TechTalk.SpecFlow.Bindings.StepArgumentTypeConverter.Convert(System.Object, TechTalk.SpecFlow.Bindings.Reflection.IBindingType, System.Globalization.CultureInfo)
at TechTalk.SpecFlow.Bindings.StepArgumentTypeConverter+<>c__DisplayClass8_0.<GetStepTransformationArgumentsFromRegex>b__1(System.String, Int32)
at System.Linq.Enumerable+<SelectIterator>d__174`2[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext()
at System.Collections.Generic.LargeArrayBuilder`1[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].AddRange(System.Collections.Generic.IEnumerable`1<System.__Canon>)
at System.Collections.Generic.EnumerableHelpers.ToArray[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](System.Collections.Generic.IEnumerable`1<System.__Canon>)
at System.Linq.Enumerable.ToArray[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](System.Collections.Generic.IEnumerable`1<System.__Canon>)
at TechTalk.SpecFlow.Bindings.StepArgumentTypeConverter.GetStepTransformationArgumentsFromRegex(TechTalk.SpecFlow.Bindings.IStepArgumentTransformationBinding, System.String, System.Globalization.CultureInfo)
at TechTalk.SpecFlow.Bindings.StepArgumentTypeConverter.DoTransform(TechTalk.SpecFlow.Bindings.IStepArgumentTransformationBinding, System.Object, System.Globalization.CultureInfo)
at TechTalk.SpecFlow.Bindings.StepArgumentTypeConverter.Convert(System.Object, TechTalk.SpecFlow.Bindings.Reflection.IBindingType, System.Globalization.CultureInfo)
at TechTalk.SpecFlow.Bindings.StepArgumentTypeConverter+<>c__DisplayClass8_0.<GetStepTransformationArgumentsFromRegex>b__1(System.String, Int32)
at System.Linq.Enumerable+<SelectIterator>d__174`2[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext()
at System.Collections.Generic.LargeArrayBuilder`1[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].AddRange(System.Collections.Generic.IEnumerable`1<System.__Canon>)
at System.Collections.Generic.EnumerableHelpers.ToArray[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](System.Collections.Generic.IEnumerable`1<System.__Canon>)
at System.Linq.Enumerable.ToArray[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](System.Collections.Generic.IEnumerable`1<System.__Canon>)
at TechTalk.SpecFlow.Bindings.StepArgumentTypeConverter.GetStepTransformationArgumentsFromRegex(TechTalk.SpecFlow.Bindings.IStepArgumentTransformationBinding, System.String, System.Globalization.CultureInfo)
at TechTalk.SpecFlow.Bindings.StepArgumentTypeConverter.DoTransform(TechTalk.SpecFlow.Bindings.IStepArgumentTransformationBinding, System.Object, System.Globalization.CultureInfo)
at TechTalk.SpecFlow.Bindings.StepArgumentTypeConverter.Convert(System.Object, TechTalk.SpecFlow.Bindings.Reflection.IBindingType, System.Globalization.CultureInfo)
at TechTalk.SpecFlow.Bindings.StepArgumentTypeConverter+<>c__DisplayClass8_0.<GetStepTransformationArgumentsFromRegex>b__1(System.String, Int32)
at System.Linq.Enumerable+<SelectIterator>d__174`2[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext()
at System.Collections.Generic.LargeArrayBuilder`1[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].AddRange(System.Collections.Generic.IEnumerable`1<System.__Canon>)
at System.Collections.Generic.EnumerableHelpers.ToArray[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](System.Collections.Generic.IEnumerable`1<System.__Canon>)
at System.Linq.Enumerable.ToArray[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](System.Collections.Generic.IEnumerable`1<System.__Canon>)
at TechTalk.SpecFlow.Bindings.StepArgumentTypeConverter.GetStepTransformationArgumentsFromRegex(TechTalk.SpecFlow.Bindings.IStepArgumentTransformationBinding, System.String, System.Globalization.CultureInfo)
at TechTalk.SpecFlow.Bindings.StepArgumentTypeConverter.DoTransform(TechTalk.SpecFlow.Bindings.IStepArgumentTransformationBinding, System.Object, System.Globalization.CultureInfo)
at TechTalk.SpecFlow.Bindings.StepArgumentTypeConverter.Convert(System.Object, TechTalk.SpecFlow.Bindings.Reflection.IBindingType, System.Globalization.CultureInfo)
at TechTalk.SpecFlow.Bindings.StepArgumentTypeConverter+<>c__DisplayClass8_0.<GetStepTransformationArgumentsFromRegex>b__1(System.String, Int32)
at System.Linq.Enumerable+<SelectIterator>d__174`2[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext()
at System.Collections.Generic.LargeArrayBuilder`1[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].AddRange(System.Collections.Generic.IEnumerable`1<System.__Canon>)
at System.Collections.Generic.EnumerableHelpers.ToArray[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](System.Collections.Generic.IEnumerable`1<System.__Canon>)
at System.Linq.Enumerable
Steps to Reproduce
Apply StepArgumentTransformation binding and apply a regular expression such as:
**[StepArgumentTransformation(@"(\d+)")]
public string Transform(string val)
{
Console.WriteLine($"Transform: {val}");
return val;
}**
[Given(@"I have a value '([^']*)'")]
public void GivenIHaveAValue(string val)
{
Console.WriteLine($"Given: {val}");
}
[When(@"I have another value '([^']*)'")]
public void WhenIHaveAnotherValue(string val)
{
Console.WriteLine($"When: {val}");
}
[Then(@"I have something else '([^']*)'")]
public void ThenIHaveSomethingElse(string val)
{
Console.WriteLine($"Then: {val}");
}
When this goes to run a stack overflow will be thrown; remove the filter regular expression and it works fine and iterates through all parameter data such as:
Standard Output: Given I have a value 'abc' Transform: abc Given: abc -> done: StepArgExStepDefinitions.GivenIHaveAValue("abc") (0.0s) When I have another value 'xyz' Transform: xyz When: xyz -> done: StepArgExStepDefinitions.WhenIHaveAnotherValue("xyz") (0.0s) Then I have something else '123' Transform: 123 Then: 123 -> done: StepArgExStepDefinitions.ThenIHaveSomethingElse("123") (0.0s)
Link to Repro Project
No response
As the step argument transformation is limited to numbers, I changed the parameter type to int and it is working:
[StepArgumentTransformation(@"(\d+)")]
public string Transform(int val)
{
Console.WriteLine($"Transform: {val}");
return val.ToString();
}
@gasparnagy Is this a bug?
@SabotageAndi @jrod567 The string
-> string
transformations are not supported with the step argument transformations. (It is not limited to numbers, but this special case is not supported.)
For such cases it is recommended to create a custom type (that wraps a string) and use that instead:
public class ValueString
{
public ValueString(string value) { ... }
}
[StepArgumentTransformation(@"(\d+)")]
public ValueString Transform(string val)
{
Console.WriteLine($"Transform: {val}");
return new ValueString(val);
}
[Given(@"I have a value '([^']*)'")]
public void GivenIHaveAValue(ValueString val)
{
Console.WriteLine($"Given: {val}");
}
Thanks @gasparnagy for clarifying.
Hi Andreas and Gaspar - Thank you for the feedback! For our needs, we were looking at using this to convert string based dynamic info (i.e. dates, unique placeholders, etc.) back into another string so string->string transformation would be a nice feature for our team. Maybe something to put in the backlog?
That said, string to string transformation appears to work without using the StepArgumentTransformation filter. Do you see issues using it in this capacity? Wasn't sure if there were any gotchas in doing so that aren't immediately evident.
Thanks!!
@jrod567 I did a bit more investigation and found, that indeed, we have improved the conversion logic for string
-> string
conversions, but only for cases where there is no regex provided for the [StepArgumentTransformation]
attribute. This means that you could also implement the transformation in a way, that you just take the entire string, do the Regex matching yourself and return the parameter value if they don't match.
[StepArgumentTransformation]
public string Transform(string val)
{
if (!Regex,IsMatch(val, @"(\d+)")
return val; // no change
// do transformation
}
I checked the code. The conversion is done by the DoTransform
method of the StepArgumentTypeConverter
class here.
The step argument transformations are handled reclusively by design. I.e. if you have a conversion from string
to int
(e.g. roman numbers) and another conversion from int
to DateTime
(gets the date in X days) then you will be able use a step that provides a date using the "in X days" conversion by specifying a roman number for X.
Because of this reclusive nature, the x
-> x
conversions are problematic, because when converting the parameter of the conversion, the conversion will be invoked again. This is why you see the stack overflow.
As you can see the code, the string
-> string
conversion was fixed by cutting out the recursion. But we could only do it in cases where there is no regex here. Generalizing this special handling is not trivial, at he moment I don't have an idea to solve it in a simple way.
If you have any suggestion, feel free to provide it in a pull request. You can also submit it as a feature request, but knowing the capacity of the team and the specific nature of the issue, I don't think that it will get much attention, unless you can work on it (it is open-source!).
Yeah, I also don't see a possibility to put this on the backlog of the team in the future. Especially as there is a workaround.
Thanks again for looking into this! I see what you mean now. That said, the workaround above was similar to what I was considering as well so it sounds like we'd be fine going that route.