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

StepArgumentTransformation stack overflow exception #2561

Open
jrod567 opened this issue Feb 18, 2022 · 8 comments
Open

StepArgumentTransformation stack overflow exception #2561

jrod567 opened this issue Feb 18, 2022 · 8 comments
Labels
Difficulty: hard Feature-Request PR has to come from community if the community wants this feature, it has to make the PR. The SpecFlow team will not implement it Runtime Severity: medium

Comments

@jrod567
Copy link

jrod567 commented Feb 18, 2022

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

@jrod567 jrod567 added the Bug label Feb 18, 2022
@SabotageAndi
Copy link
Contributor

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();
}

@SabotageAndi
Copy link
Contributor

@gasparnagy Is this a bug?

@gasparnagy
Copy link
Contributor

@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}");
}

@SabotageAndi
Copy link
Contributor

Thanks @gasparnagy for clarifying.

@jrod567
Copy link
Author

jrod567 commented Mar 2, 2022

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!!

@gasparnagy
Copy link
Contributor

@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!).

@SabotageAndi SabotageAndi reopened this Mar 3, 2022
@SabotageAndi SabotageAndi added the PR has to come from community if the community wants this feature, it has to make the PR. The SpecFlow team will not implement it label Mar 3, 2022
@SabotageAndi
Copy link
Contributor

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.

@jrod567
Copy link
Author

jrod567 commented Mar 3, 2022

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Difficulty: hard Feature-Request PR has to come from community if the community wants this feature, it has to make the PR. The SpecFlow team will not implement it Runtime Severity: medium
Projects
None yet
Development

No branches or pull requests

3 participants