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

Added order parameter to stepArgumentTransformation to prioritize execution #2753

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions TechTalk.SpecFlow/Bindings/BindingFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ public IStepDefinitionBindingBuilder CreateStepDefinitionBindingBuilder(StepDefi
}

public IStepArgumentTransformationBinding CreateStepArgumentTransformation(string regexString,
IBindingMethod bindingMethod, string parameterTypeName = null)
IBindingMethod bindingMethod, string parameterTypeName = null,
int order = StepArgumentTransformationAttribute.DefaultOrder)
{
return new StepArgumentTransformationBinding(regexString, bindingMethod, parameterTypeName);
return new StepArgumentTransformationBinding(regexString, bindingMethod, parameterTypeName, order);
}
}
34 changes: 17 additions & 17 deletions TechTalk.SpecFlow/Bindings/BindingRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ public IEnumerable<IStepDefinitionBinding> GetConsideredStepDefinitions(StepDefi
{
//TODO: later optimize to return step definitions that has a chance to match to stepText
return _stepDefinitions.Where(sd => sd.StepDefinitionType == stepDefinitionType);
}
}

public virtual IEnumerable<IHookBinding> GetHooks()
{
return _hooks.Values.SelectMany(hookList => hookList);
Expand All @@ -34,19 +34,19 @@ public virtual IEnumerable<IHookBinding> GetHooks()
public virtual IEnumerable<IHookBinding> GetHooks(HookType bindingEvent)
{
return GetHookList(bindingEvent);
}
private IEnumerable<IHookBinding> GetHookList(HookType bindingEvent)
}

private IEnumerable<IHookBinding> GetHookList(HookType bindingEvent)
{
if (_hooks.TryGetValue(bindingEvent, out var list))
return list;
return Enumerable.Empty<IHookBinding>();
}
if (_hooks.TryGetValue(bindingEvent, out var list))
return list;

return Enumerable.Empty<IHookBinding>();
}

public virtual IEnumerable<IStepArgumentTransformationBinding> GetStepTransformations()
{
return _stepArgumentTransformations;
return _stepArgumentTransformations.OrderBy(s => s.Order);
}

public IEnumerable<BindingError> GetErrorMessages()
Expand All @@ -63,8 +63,8 @@ public IEnumerable<BindingError> GetErrorMessages()
public virtual void RegisterStepDefinitionBinding(IStepDefinitionBinding stepDefinitionBinding)
{
_stepDefinitions.Add(stepDefinitionBinding);
}
}

private List<IHookBinding> GetHookListForRegister(HookType bindingEvent)
{
if (!_hooks.TryGetValue(bindingEvent, out var list))
Expand All @@ -74,8 +74,8 @@ private List<IHookBinding> GetHookListForRegister(HookType bindingEvent)
}

return list;
}
}

public virtual void RegisterHookBinding(IHookBinding hookBinding)
{
List<IHookBinding> hookRegistry = GetHookListForRegister(hookBinding.HookType);
Expand All @@ -94,4 +94,4 @@ public void RegisterGenericBindingError(BindingError error)
_genericBindingErrors.Add(error);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ private void ProcessStepArgumentTransformationAttribute(BindingSourceMethod bind
{
string regex = stepArgumentTransformationAttribute.TryGetAttributeValue<string>(0) ?? stepArgumentTransformationAttribute.TryGetAttributeValue<string>(nameof(StepArgumentTransformationAttribute.Regex));
string name = stepArgumentTransformationAttribute.TryGetAttributeValue<string>(nameof(StepArgumentTransformationAttribute.Name));
int order = stepArgumentTransformationAttribute.TryGetAttributeValue(nameof(StepArgumentTransformationAttribute.Order), StepArgumentTransformationAttribute.DefaultOrder);

var validationResult = ValidateStepArgumentTransformation(bindingSourceMethod, stepArgumentTransformationAttribute);
if (!validationResult.IsValid)
Expand All @@ -207,7 +208,7 @@ private void ProcessStepArgumentTransformationAttribute(BindingSourceMethod bind
return;
}

var stepArgumentTransformationBinding = _bindingFactory.CreateStepArgumentTransformation(regex, bindingSourceMethod.BindingMethod, name);
var stepArgumentTransformationBinding = _bindingFactory.CreateStepArgumentTransformation(regex, bindingSourceMethod.BindingMethod, name, order);

ProcessStepArgumentTransformationBinding(stepArgumentTransformationBinding);
}
Expand Down Expand Up @@ -394,4 +395,4 @@ private void ApplyForScope(BindingScope[] scopes, Action<BindingScope> action)
}
}
}
}
}
5 changes: 3 additions & 2 deletions TechTalk.SpecFlow/Bindings/IBindingFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ IStepDefinitionBindingBuilder CreateStepDefinitionBindingBuilder(StepDefinitionT
BindingScope bindingScope, string expressionString);

IStepArgumentTransformationBinding CreateStepArgumentTransformation(string regexString,
IBindingMethod bindingMethod, string parameterTypeName = null);
IBindingMethod bindingMethod, string parameterTypeName = null,
int order = StepArgumentTransformationAttribute.DefaultOrder);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,10 @@ public interface IStepArgumentTransformationBinding : IBinding
/// The regular expression matches the step argument. Optional, if null, the transformation receives the entire argument.
/// </summary>
Regex Regex { get; }

/// <summary>
/// The deterministic order for step argument transformation
/// </summary>
int Order { get; }
}
}
}
13 changes: 9 additions & 4 deletions TechTalk.SpecFlow/Bindings/StepArgumentTransformationBinding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,21 @@ public class StepArgumentTransformationBinding : MethodBinding, IStepArgumentTra
public string Name { get; }

public Regex Regex { get; }

public int Order { get; }

public StepArgumentTransformationBinding(Regex regex, IBindingMethod bindingMethod, string name = null)
public StepArgumentTransformationBinding(Regex regex, IBindingMethod bindingMethod, string name = null,
int order = StepArgumentTransformationAttribute.DefaultOrder)
: base(bindingMethod)
{
Regex = regex;
Name = name;
Order = order;
}

public StepArgumentTransformationBinding(string regexString, IBindingMethod bindingMethod, string name = null)
: this(CreateRegexOrNull(regexString), bindingMethod, name)
public StepArgumentTransformationBinding(string regexString, IBindingMethod bindingMethod, string name = null,
int order = StepArgumentTransformationAttribute.DefaultOrder)
: this(CreateRegexOrNull(regexString), bindingMethod, name, order)
{
}

Expand All @@ -28,4 +33,4 @@ private static Regex CreateRegexOrNull(string regexString)
return RegexFactory.CreateWholeTextRegexForBindings(regexString);
}
}
}
}
7 changes: 6 additions & 1 deletion TechTalk.SpecFlow/Bindings/StepArgumentTypeConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,19 @@ public StepArgumentTypeConverter(ITestTracer testTracer, IBindingRegistry bindin
protected virtual IStepArgumentTransformationBinding GetMatchingStepTransformation(object value, IBindingType typeToConvertTo, bool traceWarning)
{
var stepTransformations = bindingRegistry.GetStepTransformations().Where(t => CanConvert(t, value, typeToConvertTo)).ToArray();
if (stepTransformations.Length > 1 && traceWarning)
if (traceWarning && HasMultipleTransformationsWithSameOrder(stepTransformations))
{
testTracer.TraceWarning($"Multiple step transformation matches to the input ({value}, target type: {typeToConvertTo}). We use the first.");
}

return stepTransformations.Length > 0 ? stepTransformations[0] : null;
}

private bool HasMultipleTransformationsWithSameOrder(IStepArgumentTransformationBinding[] transformations) =>
transformations
.GroupBy(t => t.Order)
.Any(group => group.Count() > 1);

public async Task<object> ConvertAsync(object value, IBindingType typeToConvertTo, CultureInfo cultureInfo)
{
if (value == null) throw new ArgumentNullException(nameof(value));
Expand Down
13 changes: 11 additions & 2 deletions TechTalk.SpecFlow/StepArgumentTransformationAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,23 @@ public class StepArgumentTransformationAttribute : Attribute
/// </summary>
public string Name { get; set; }

public StepArgumentTransformationAttribute(string regex)
/// <summary>
/// Specifies the deterministic order for step argument transformations. Lower numbers have higher priority.
/// Default value is <see cref="StepArgumentTransformationAttribute.DefaultOrder">10000</see>.
/// </summary>
public int Order { get; set; }

public const int DefaultOrder = 10000;

public StepArgumentTransformationAttribute(string regex, int order = DefaultOrder)
{
Regex = regex;
Order = order;
}

public StepArgumentTransformationAttribute()
{
Regex = null;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,25 @@ public void GetHooks_should_return_all_hooks()

result.Should().BeEquivalentTo(new List<HookBinding> { hook1, hook2 });
}

[Fact]
public void GetStepTransformations_should_return_all_step_transformers_in_correct_order()
{
var sut = new BindingRegistry();

var sat1 = new StepArgumentTransformationBinding(string.Empty, new Mock<IBindingMethod>().Object);
var sat2 = new StepArgumentTransformationBinding(string.Empty, new Mock<IBindingMethod>().Object);
var sat3 = new StepArgumentTransformationBinding(string.Empty, new Mock<IBindingMethod>().Object, order: 1);
var sat4 = new StepArgumentTransformationBinding(string.Empty, new Mock<IBindingMethod>().Object, null, 2);

sut.RegisterStepArgumentTransformationBinding(sat4);
sut.RegisterStepArgumentTransformationBinding(sat1);
sut.RegisterStepArgumentTransformationBinding(sat3);
sut.RegisterStepArgumentTransformationBinding(sat2);

var result = sut.GetStepTransformations();

result.Should().BeEquivalentTo(new List<StepArgumentTransformationBinding> { sat1, sat2, sat3, sat4 });
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ public int Transform(string val)
{
return 42;
}

[StepArgumentTransformation(Regex="BindingRegistryTests2", Order = 5)]
public int TransformWithRegexAndOrder(string val)
{
return 43;
}

[StepArgumentTransformation(Order = 10)]
public int TransformWithOrderAndWithoutRegex(string val)
{
return 44;
}
}

private BindingSourceProcessorStub bindingSourceProcessorStub;
Expand Down Expand Up @@ -288,6 +300,29 @@ public void ShouldFindBinding_WithSpecifiedPriorities()
s.HookType == HookType.AfterTestRun && s.Method.Name == "AfterOrderTenThousandAnd4" &&
s.HookOrder == 10004));
}

[Fact]
public void ShouldFindStepArgumentTransformations_WithSpecifiedOrder()
{
var builder = new RuntimeBindingRegistryBuilder(bindingSourceProcessorStub, new SpecFlowAttributesFilter());

BuildCompleteBindingFromType(builder, typeof (StepTransformationExample));

Assert.Single(
bindingSourceProcessorStub.StepArgumentTransformationBindings,
sat =>
sat.Method.Name == nameof(StepTransformationExample.Transform) && sat.Order == StepArgumentTransformationAttribute.DefaultOrder);

Assert.Single(
bindingSourceProcessorStub.StepArgumentTransformationBindings,
sat =>
sat.Method.Name == nameof(StepTransformationExample.TransformWithRegexAndOrder) && sat.Order == 5);

Assert.Single(
bindingSourceProcessorStub.StepArgumentTransformationBindings,
sat =>
sat.Method.Name == nameof(StepTransformationExample.TransformWithOrderAndWithoutRegex) && sat.Order == 10);
}

[Fact]
public void ShouldFindExampleConverter()
Expand Down Expand Up @@ -405,4 +440,4 @@ public void ShouldFindStepDefinitionsWithCustomAttribute()
Assert.Equal(0, bindingSourceProcessorStub.StepDefinitionBindings.Count(b => b.StepDefinitionType == StepDefinitionType.Then));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,14 @@ public Table TableToTableConvert(Table table)
}
}

public class StepTransformationTests
public class StepArgumentTransformationTests
{
private readonly Mock<IBindingRegistry> bindingRegistryStub = new Mock<IBindingRegistry>();
private readonly Mock<IContextManager> contextManagerStub = new Mock<IContextManager>();
private readonly Mock<IAsyncBindingInvoker> methodBindingInvokerStub = new Mock<IAsyncBindingInvoker>();
private readonly List<IStepArgumentTransformationBinding> stepTransformations = new List<IStepArgumentTransformationBinding>();

public StepTransformationTests()
public StepArgumentTransformationTests()
{
// ScenarioContext is needed, because the [Binding]-instances live there
var scenarioContext = new ScenarioContext(new ObjectContainer(), null, new TestObjectResolver());
Expand Down Expand Up @@ -250,4 +250,4 @@ public async Task ShouldUseStepArgumentTransformationToConvertTable()
}
}

}
}
Loading