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

Fix for 81 - Cucumber Expression using Enums errors when two enums exist with the same short name #100

Merged

Conversation

clrudolphi
Copy link
Contributor

This fix causes the CucumberExpressionParameterTypeRegistry to use the FQN of the enum Types used as Cucumber Expression parameters.

Types of changes

  • [X ] Bug fix (non-breaking change which fixes an issue).

  • New feature (non-breaking change which adds functionality).

  • Breaking change (fix or feature that would cause existing functionality to not work as expected).

  • Performance improvement

  • Refactoring (so no functional change)

  • Other (docs, build config, etc)

  • [X ] I've added tests for my code. (most of the time mandatory)

  • [X ] I have added an entry to the changelog. (mandatory)

  • My change requires a change to the documentation.

  • I have updated the documentation accordingly.

…th the same short name exist as parameters to cucumber expressions.

This fix causes the CucumberExpressionParameterTypeRegistry to use the FQN of the enum Types used as Cucumber Expression parameters.
@clrudolphi
Copy link
Contributor Author

@gasparnagy , this is regarding #81 . We discussed two possible sub-scenarios. This fix addresses the most immediate one (two enums that have the same short name but exist in different namespaces).
Let me know if the testing is adequate. I have a short unit test, but nothing added yet in Specs or elsewhere to demonstrate no adverse affect to using enums.

@clrudolphi
Copy link
Contributor Author

@gasparnagy Argh. NM. Already see where this breaks everything. I'll rethink this.

@gasparnagy
Copy link
Contributor

gasparnagy commented Apr 12, 2024

@clrudolphi Yes, unfortunately the problem is conceptual here. When I did the first version of CukeEx support, I had to solve somehow that enums work. Finally the workaround was to go through all possible parameter types and register them. But obviously this is not working if there is a name duplication...

Maybe what we could do is the following: when we realize that there is a duplication (we are about to add one with a name, but the dictionary already has sg with the same name), we could replace the existing value with a "proxy". This proxy would "remember" (have a list) of all ambiguous types and if (and only if) someone would try to use it actually (i.e. use a placeholder like "... {Color} ...") we would throw an error, suggesting that this is ambiguous (write out the type names from its list) and if you want to use this enum, you need to create two StepArgumentTransformations and give them unique name (names are case sensitive).

Note that @kipusoep was not actually using the registered name in #81, so in his case the "proxy" would never be actually used, so he would not see this error, but could just use the enums as he planned to.

What do you think?

@Code-Grump
Copy link
Contributor

Creating some kind of "aggregate" transformer is an interesting idea. How would we detect when a transform is actually ambiguous? It feels like a really clever result if Reqnroll can slip around the problem when it's just theoretical and only throw an exception when there is a practical problem. 👍

@gasparnagy
Copy link
Contributor

How would we detect when a transform is actually ambiguous?

There is a point where we would register the name to a dictionary. Is somebody is already in it is ambiguous.

@clrudolphi
Copy link
Contributor Author

Identifying colliding Enum shortnames at the time of discovery is difficult as the Dictionary is built Lazily.

Instead, please review the latest change(cba2383). At execution time, if there is no match in the Dictionary based upon the name given, then I attempt to match enums just on short name (assuming that the Dict key values are FullNames of enum types). Then, if 1 match is found, return that; if more than 1, then we have an ambiguous situation.

@gasparnagy
Copy link
Contributor

@clrudolphi good idea. I will check it.

Copy link
Contributor

@gasparnagy gasparnagy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes!!! This is a good idea.

I also like the test. You can add another one that checks if the exception is thrown in case of ambiguity.

And the good thing is, that if you still want to use the enum by short name (for one of the types), you can create a [StepArgumentTransformation] and that will register the trafo with the short name and therefore it will not run into the ambiguous lookup exception. So this will also fix your case as well, I think. We can maybe even add this hint to the exception message: [...] Use the enum's full name in the cucumber expression or define a [StepArgumentTransformation] with the chosen type and the short name.

Can you make a quick manual check in a sample project to verify if it really works with this the two cases?

if (matchingEnums.Length == 1) { return matchingEnums[0].Value; }
if (matchingEnums.Length > 1)
{
throw new ApplicationException($"Ambigous Enum Parameters share the same short name '{name}'. Use the Enum's FullName in the Cucumber Expression.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • throw ReqnrollException instead
  • I would make the error message more sounding like a normal text (there was also a typo in the Ambiguous word!): $"Ambiguous enum parameters share the same short name '{name}'. Use the enum's full name in the cucumber expression."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

public void Should_not_error_on_multiple_enums_of_the_same_name()
{
var sut = CreateSut();
Reqnroll.Bindings.Reflection.IBindingMethod enumUsingBindingMethod1 = new RuntimeBindingMethod(typeof(SampleEnumUsingClass).GetMethod("MethodUsingSampleColorEnum1"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of "MethodUsingSampleColorEnum1" you can use nameof(SampleEnumUsingClass.MethodUsingSampleColorEnum1)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

var sut = CreateSut();
Reqnroll.Bindings.Reflection.IBindingMethod enumUsingBindingMethod1 = new RuntimeBindingMethod(typeof(SampleEnumUsingClass).GetMethod("MethodUsingSampleColorEnum1"));
sut.OnBindingMethodProcessed(enumUsingBindingMethod1);
Reqnroll.Bindings.Reflection.IBindingMethod enumUsingBindingMethod2 = new RuntimeBindingMethod(typeof(CucumberAddtionalExpressions.EnumCucumberExpressions).GetMethod("MethodUsingSampleColorEnum2"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use nameof also here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@clrudolphi
Copy link
Contributor Author

Can you make a quick manual check in a sample project to verify if it really works with this the two cases?

Please take a look at this repo with a set of projects that demonstrate behavior.
I used two separate projects because the 'ambiguous' error will be thrown for EVERY test in the project as each Scenario execution is asking the registry for parameter bindings... Not great.

@gasparnagy
Copy link
Contributor

@clrudolphi could you paste the new stack trace of the errors?

@gasparnagy
Copy link
Contributor

error will be thrown for EVERY test in the project as each Scenario execution is asking the registry for parameter bindings

BTW, I think this is the usual behavior if there is any binding error. Like an invalid regex. So it is not that horrible.

@clrudolphi
Copy link
Contributor Author

@clrudolphi could you paste the new stack trace of the errors?

In the VisualStudio Output window, the VS RnR Extension will output:

10:04:38:688	Info: OnActivityStarted: Starting Visual Studio Extension...
10:04:38:695	Info: CreateProjectScope: Initializing project: RnR81Exp2
10:04:38:745	Info: OnSettingsInitialized: Project settings initialized: .NETCoreApp,Version=v6.0,Reqnroll:1.0.2-local
10:04:39:145	Info: ThenImportBindings: 2 step definitions and 0 hooks discovered for project RnR81Exp2
10:04:39:148	Warning: ReportInvalidStepDefinitions: Invalid step definitions found: 
10:04:39:148	  [Given(a shade of '{Shade}')]: ShadeEnumTestStepDefinitions.ThisWillResultInAnError(RnRGH81CucumberExpr_ItemWithSameKeyAlreadyAdded.StepDefinitions.Shade): Ambiguous Enum Parameters share the same short name 'Shade'. Use the Enum's FullName in the Cucumber Expression or define a [StepArgumentTransformation] with the chosen type and the short name. at C:\Users\clrud\source\repos\RnR81Exp\RnR81Exp2\StepDefinitions\ColorEnumTestStepDefinition2s.cs(15,58)
10:04:39:148	  [Given(a shade of '{Shade}')]: AnotherShadeEnumTestStepDefinitions.ThisWillResultInAnError(RnRGH81CucumberExpr_ItemWithSameKeyAlreadyAdded.AnotherNamespace.Shade): Ambiguous Enum Parameters share the same short name 'Shade'. Use the Enum's FullName in the Cucumber Expression or define a [StepArgumentTransformation] with the chosen type and the short name. at C:\Users\clrud\source\repos\RnR81Exp\RnR81Exp2\StepDefinitions\ColorEnumTestStepDefinition2s.cs(31,58)
10:05:00:421	Info: ThenImportBindings: 2 step definitions and 0 hooks discovered for project RnR81Exp2
10:05:00:422	Warning: ReportInvalidStepDefinitions: Invalid step definitions found: 
10:05:00:422	  [Given(a shade of '{Shade}')]: ShadeEnumTestStepDefinitions.ThisWillResultInAnError(RnRGH81CucumberExpr_ItemWithSameKeyAlreadyAdded.StepDefinitions.Shade): Ambiguous Enum Parameters share the same short name 'Shade'. Use the Enum's FullName in the Cucumber Expression or define a [StepArgumentTransformation] with the chosen type and the short name. at C:\Users\clrud\source\repos\RnR81Exp\RnR81Exp2\StepDefinitions\ColorEnumTestStepDefinition2s.cs(15,58)
10:05:00:422	  [Given(a shade of '{Shade}')]: AnotherShadeEnumTestStepDefinitions.ThisWillResultInAnError(RnRGH81CucumberExpr_ItemWithSameKeyAlreadyAdded.AnotherNamespace.Shade): Ambiguous Enum Parameters share the same short name 'Shade'. Use the Enum's FullName in the Cucumber Expression or define a [StepArgumentTransformation] with the chosen type and the short name. at C:\Users\clrud\source\repos\RnR81Exp\RnR81Exp2\StepDefinitions\ColorEnumTestStepDefinition2s.cs(31,58)

In the Test Explorer View of VS, the Test Detail Summary is:

Using a short name of an enum with conflicting enum names will result in an Ambiguous Error
   Source: ColorEnumTest2.feature line 4
   Duration: 14 ms

  Message: 
Reqnroll.BindingException : Binding error(s) found: 
Ambiguous Enum Parameters share the same short name 'Shade'. Use the Enum's FullName in the Cucumber Expression or define a [StepArgumentTransformation] with the chosen type and the short name.
Ambiguous Enum Parameters share the same short name 'Shade'. Use the Enum's FullName in the Cucumber Expression or define a [StepArgumentTransformation] with the chosen type and the short name.

  Stack Trace: 
ErrorProvider.GetInvalidBindingRegistryError(IEnumerable`1 errors)
TestExecutionEngine.GetStepMatch(StepInstance stepInstance)
TestExecutionEngine.ExecuteStepAsync(IContextManager contextManager, StepInstance stepInstance)
TestExecutionEngine.OnAfterLastStepAsync()
TestRunner.CollectScenarioErrorsAsync()
ColorEnumTestingFeature.ScenarioCleanupAsync()
ColorEnumTestingFeature.UsingAShortNameOfAnEnumWithConflictingEnumNamesWillResultInAnAmbiguousError() line 5
--- End of stack trace from previous location ---

  Standard Output: 
Given a shade of 'Grey'
-> binding error: Binding error(s) found: 
Ambiguous Enum Parameters share the same short name 'Shade'. Use the Enum's FullName in the Cucumber Expression or define a [StepArgumentTransformation] with the chosen type and the short name.
Ambiguous Enum Parameters share the same short name 'Shade'. Use the Enum's FullName in the Cucumber Expression or define a [StepArgumentTransformation] with the chosen type and the short name.

@gasparnagy
Copy link
Contributor

I think this is OK.

Could you please check if you get the same behavior if you add an attribute like [When("^invalid regex ( $")]

@clrudolphi
Copy link
Contributor Author

I think this is OK.

Could you please check if you get the same behavior if you add an attribute like [When("^invalid regex ( $")]

 Using a short name of an enum with conflicting enum names will result in an Ambiguous Error
   Source: ColorEnumTest2.feature line 4
   Duration: 14 ms

  Message: 
Reqnroll.BindingException : Binding error(s) found: 
Ambiguous Enum Parameters share the same short name 'Shade'. Use the Enum's FullName in the Cucumber Expression or define a [StepArgumentTransformation] with the chosen type and the short name.
Ambiguous Enum Parameters share the same short name 'Shade'. Use the Enum's FullName in the Cucumber Expression or define a [StepArgumentTransformation] with the chosen type and the short name.
Invalid pattern '^invalid regex ( $' at offset 18. Not enough )'s.

  Stack Trace: 
ErrorProvider.GetInvalidBindingRegistryError(IEnumerable`1 errors)
TestExecutionEngine.GetStepMatch(StepInstance stepInstance)
TestExecutionEngine.ExecuteStepAsync(IContextManager contextManager, StepInstance stepInstance)
TestExecutionEngine.OnAfterLastStepAsync()
TestRunner.CollectScenarioErrorsAsync()
ColorEnumTestingFeature.ScenarioCleanupAsync()
ColorEnumTestingFeature.UsingAShortNameOfAnEnumWithConflictingEnumNamesWillResultInAnAmbiguousError() line 5
--- End of stack trace from previous location ---

  Standard Output: 
Given a shade of 'Grey'
-> binding error: Binding error(s) found: 
Ambiguous Enum Parameters share the same short name 'Shade'. Use the Enum's FullName in the Cucumber Expression or define a [StepArgumentTransformation] with the chosen type and the short name.
Ambiguous Enum Parameters share the same short name 'Shade'. Use the Enum's FullName in the Cucumber Expression or define a [StepArgumentTransformation] with the chosen type and the short name.
Invalid pattern '^invalid regex ( $' at offset 18. Not enough )'s.


@gasparnagy
Copy link
Contributor

gasparnagy commented Apr 15, 2024

@clrudolphi ok. so that works the same way. i think this is good.

So I think what is missing is:

  • You can add another test that checks if the exception is thrown in case of ambiguity.
  • The error message still has capital case phrases, please also fix that

@gasparnagy gasparnagy marked this pull request as ready for review April 15, 2024 20:34
…nd BindingRegistry in InValid state when ambiguous enum cucumber parameter expressions are present.

Modified Error message for formatting.
@clrudolphi
Copy link
Contributor Author

@gasparnagy
Had to make a modification to the logic that identifies ambiguity - in addition to enums that are children of namespaces ("." + name) we also need to account for enums that are embedded in classes ("+" + name).
Please confirm that the wording of the error message is acceptable.

@gasparnagy
Copy link
Contributor

@clrudolphi yes, this is fine. Is it ready to merge once the CI is green then?

@clrudolphi
Copy link
Contributor Author

Yes. I'm not aware of any other remaining action items.

@gasparnagy gasparnagy merged commit 791b0c5 into reqnroll:main Apr 16, 2024
6 checks passed
@gasparnagy
Copy link
Contributor

Thx!

gasparnagy added a commit that referenced this pull request Apr 16, 2024
* origin/main:
  Fix for 81 - Cucumber Expression using Enums errors when two enums exist with the same short name (#100)
gasparnagy added a commit that referenced this pull request May 22, 2024
…ons-dependencyinjection-plugin

* origin/main: (21 commits)
  Fix #56 autofac ambiguous stepdef and hook required #127 issue (#139)
  Reduce target framework of Reqnroll to netstandard2.0 (#130)
  Fix StackOverflowException when using [StepArgumentTransformation] with same input and output type (#136)
  MsTest: Replace DelayedFixtureTearDown special case with ClassCleanupBehavior.EndOfClass (#128)
  Temporarily disabled tests until #132 is resolved
  Add NUnit & xUnit core tests to portability suite
  Capture ExecutionContext after every binding invoke (#126)
  small improvement in CodeDomHelper to be able to chain async calls
  fix method name sources in UnitTestFeatureGenerator
  External data plugin, support for JSON files  (#118)
  UnitTests: Check if SDK version is installed and if not ignore the test (#109)
  Fix 111 ignore attr not inherited from rule (#113)
  Update README.md (#110)
  Fix 81 - modified CucumberExpressionParameterTypeRegistry to handle multiple custom types used as cucumber expressions when those types share the same short name. (#104)
  Update index.md
  Simplify test project targets (#105)
  Make SystemTests temp folder configurable and use NUGET_PACKAGES env var to override global NuGet folder
  Fleshing out Generation System Tests (2) (#99)
  Fix for 81 - Cucumber Expression using Enums errors when two enums exist with the same short name (#100)
  Include BoDi to Reqnroll package (#91) (#95)
  ...

# Conflicts:
#	Reqnroll.sln
#	Tests/Reqnroll.PluginTests/Reqnroll.PluginTests.csproj
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants