Skip to content
Draft
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
44 changes: 40 additions & 4 deletions Reqnroll/Bindings/Discovery/BindingSourceProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,12 @@ private bool IsStepDefinitionAttribute(BindingSourceAttribute attribute)

private bool IsHookAttribute(BindingSourceAttribute attribute)
{
// ReSharper disable AssignNullToNotNullAttribute
// ReSharper disable AssignNullToNotNullAttribute
return (attribute.AttributeType.FullName.StartsWith(typeof(BeforeScenarioAttribute).Namespace) ||
attribute.AttributeType.Name.StartsWith("Before", StringComparison.InvariantCulture) ||
attribute.AttributeType.Name.StartsWith("After", StringComparison.InvariantCulture)) &&
TryGetHookType(attribute) != null;
// ReSharper restore AssignNullToNotNullAttribute
// ReSharper restore AssignNullToNotNullAttribute
}

private bool IsStepArgumentTransformationAttribute(BindingSourceAttribute attribute)
Expand Down Expand Up @@ -245,7 +245,7 @@ private HookType GetHookType(BindingSourceAttribute hookAttribute)
private void ProcessStepDefinitionAttribute(BindingSourceMethod bindingSourceMethod, BindingSourceAttribute stepDefinitionAttribute, BindingScope scope)
{
var stepDefinitionTypes = GetStepDefinitionTypes(stepDefinitionAttribute);

var expressionString = GetExpressionString(stepDefinitionAttribute);
var expressionType = stepDefinitionAttribute.TryGetAttributeValue<ExpressionType>(nameof(StepDefinitionBaseAttribute.ExpressionType));

Expand Down Expand Up @@ -280,7 +280,7 @@ private static string GetExpressionString(BindingSourceAttribute stepDefinitionA
protected readonly struct BindingValidationResult
{
public static BindingValidationResult Valid = new();
public static BindingValidationResult Error(string errorMessage) => new (errorMessage);
public static BindingValidationResult Error(string errorMessage) => new(errorMessage);

public List<string> ErrorMessages { get; }

Expand Down Expand Up @@ -315,11 +315,33 @@ protected virtual void OnValidationError(BindingValidationResult validationResul

protected virtual BindingValidationResult ValidateType(BindingSourceType bindingSourceType)
{
bool BindingClassInheritsFromBindingClass(BindingSourceType bindingSourceType)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A safer approach for this would be the following (background: IBindingType instances that correspond to a .NET type must be RuntimeBindingType).

bool BindingClassInheritsFromBindingClass(BindingSourceType bindingSourceType)
{
    if (bindingSourceType.BindingType is not RuntimeBindingType runtimeBindingType)
        return false;
    var type = runtimeBindingType.Type;
    int count = 0;
    while (type != null && type != typeof(object))
    {
        if (type.IsDefined(typeof(BindingAttribute), false))
        {
            count++;
            if (count > 1)
                return true;
        }
        type = type.BaseType;
    }
    return false;
}

However even with this approach the problem is that the decision about what makes a type "binding type" is duplicated between here and RuntimeBindingRegistryBuilder. Altogether maybe a better approach would be to collect all "recognized" binding types in ProcessType (in a field, like we do for _stepDefinitionBindingBuilders) and scan through all of them in the BuildingCompleted and check if they are among each-others parents.

{
if (bindingSourceType.BindingType == null)
return false;
if (string.IsNullOrEmpty(bindingSourceType.BindingType.FullName) || string.IsNullOrEmpty(bindingSourceType.BindingType.AssemblyName))
return false;
var type = GetTypeFromFullNameAndAssembly(bindingSourceType.BindingType?.FullName, bindingSourceType.BindingType?.AssemblyName);
int count = 0;
while (type != null && type != typeof(object))
{
if (type.IsDefined(typeof(BindingAttribute), false))
{
count++;
if (count > 1)
return true;
}
type = type.BaseType;
}
return false;
}
var result = BindingValidationResult.Valid;
if (!bindingSourceType.IsClass)
result += BindingValidationResult.Error($"Binding types must be classes: {bindingSourceType}");
if (bindingSourceType.IsGenericTypeDefinition)
result += BindingValidationResult.Error($"Binding types cannot be generic: {bindingSourceType}");
if (BindingClassInheritsFromBindingClass(bindingSourceType))
result += BindingValidationResult.Error($"Binding types cannot inherit from other binding types: {bindingSourceType}");
return result;
}

Expand Down Expand Up @@ -405,5 +427,19 @@ private void ApplyForScope(BindingScope[] scopes, Action<BindingScope> action)
action(null);
}
}

private Type GetTypeFromFullNameAndAssembly(string fullName, string assemblyName)
{
// Construct the assembly-qualified name
string assemblyQualifiedName = $"{fullName}, {assemblyName}";
// Try to get the Type
var type = Type.GetType(assemblyQualifiedName);
if (type == null)
{
// Optionally, handle the error or throw
throw new InvalidOperationException($"Type '{assemblyQualifiedName}' could not be found.");
}
return type;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using FluentAssertions;
using Reqnroll.Bindings.Discovery;
using Reqnroll.RuntimeTests.Bindings.Discovery.TestSubjects;
using System;
using System.Collections.Generic;
using Xunit;

namespace Reqnroll.RuntimeTests.Bindings.Discovery;

public class BindingSourceProcessorTestsWithInheritedBindingClasses
{
private BindingSourceProcessorStub CreateBindingSourceProcessor()
{
//NOTE: BindingSourceProcessor is abstract, to test its base functionality we need to instantiate a subclass
return new BindingSourceProcessorStub();
}

[Theory]
[InlineData(typeof(TestSubjects.DerivedBoundClassBoundMethods_InheritsFromBaseClassNoBindingNoBoundMethods), true, 1, new string[] { })]
[InlineData(typeof(TestSubjects.DerivedBoundClassBoundMethods_InheritsFromBaseClassNoBindingWithBoundMethods), true, 2, new string[] { })]
[InlineData(typeof(TestSubjects.DerivedBoundClassBoundMethods_InheritsFromBaseClassWithBindingNoBoundMethods), false, 0, new string[] { "Binding types cannot inherit from other binding types" })]
[InlineData(typeof(TestSubjects.DerivedBoundClassBoundMethods_InheritsFromBaseClassWithBindingWithBoundMethods), false, 0, new string[] { "Binding types cannot inherit from other binding types" })]
[InlineData(typeof(TestSubjects.DerivedBoundClassBoundMethodswithBindingaAttributes_InheritsFromBaseClassNoBindingNoBoundMethod), true, 1, new string[] { })]
[InlineData(typeof(TestSubjects.DerivedBoundClassBoundMethodswithBindingaAttributes_InheritsFromBaseClassNoBindingBoundMethod), true, 1, new string[] { })]
[InlineData(typeof(TestSubjects.DerivedBoundClassBoundMethodswithBindingaAttributes_InheritsFromBaseClassWithBindingNoBoundMethod), false, 0, new string[] { "Binding types cannot inherit from other binding types" })]
[InlineData(typeof(TestSubjects.DerivedBoundClassBoundMethodswithBindingaAttributes_InheritsFromBaseClassWithBindingBoundMethod), false, 0, new string[] { "Binding types cannot inherit from other binding types" })]
[InlineData(typeof(TestSubjects.DerivedBoundClassOverriddenMethodsWithoutBindingAttributes_InheritsFromBaseClassNoBindingBoundMethods), true, 1, new string[] { })]
[InlineData(typeof(TestSubjects.DerivedBoundClassOverriddenMethodsWithoutBindingAttributes_InheritsFromBaseClassWithBindingBoundMethods), false, 0, new string[] { "Binding types cannot inherit from other binding types" })]
[InlineData(typeof(TestSubjects.DerivedUnBoundClassNoBoundMethods_InheritsFromBaseClassNoBindingNoBoundMethods), false, 0, new string[] { })]
[InlineData(typeof(TestSubjects.DerivedUnBoundClassNoBoundMethods_InheritsFromBaseClassNoBindingWithBoundMethods), false, 0, new string[] { })]
[InlineData(typeof(TestSubjects.DerivedUnBoundClassNoBoundMethods_InheritsFromBaseClassWithBindingNoBoundMethods), true, 0, new string[] { })]
[InlineData(typeof(TestSubjects.DerivedUnBoundClassNoBoundMethods_InheritsFromBaseClassWithBindingWithBoundMethods), true, 1, new string[] { })]
[InlineData(typeof(TestSubjects.DerivedUnboundClassWithBoundMethods_InheritsFromBaseClassNoBindingNoBoundMethods), false, 0, new string[] { })]
[InlineData(typeof(TestSubjects.DerivedUnboundClassWithBoundMethods_InheritsFromBaseClassNoBindingWithBoundMethods), false, 0, new string[] { })]
[InlineData(typeof(TestSubjects.DerivedUnboundClassWithBoundMethods_InheritsFromBaseClassWithBindingNoBoundMethods), true, 1, new string[] { })]
[InlineData(typeof(TestSubjects.DerivedUnboundClassWithBoundMethods_InheritsFromBaseClassWithBindingWithBoundMethods), true, 2, new string[] { })]
[InlineData(typeof(TestSubjects.DerivedUnBoundClassOveriddenMethodsWithBindingAttributes_InheritsFromBaseClassNoBindingNoBoundMethods), false, 0, new string[] { })]
[InlineData(typeof(TestSubjects.DerivedUnBoundClassOveriddenMethodsWithBindingAttributes_InheritsFromBaseClassNoBindingWithBoundMethods), false, 0, new string[] { })]
[InlineData(typeof(TestSubjects.DerivedUnBoundClassOveriddenMethodsWithBindingAttributes_InheritsFromBaseClassWithBindingNoBoundMethods), true, 1, new string[] { })]
[InlineData(typeof(TestSubjects.DerivedUnBoundClassOveriddenMethodsWithBindingAttributes_InheritsFromBaseClassWithBindingWithBoundMethods), true, 1, new string[] { })]
[InlineData(typeof(TestSubjects.DerivedUnBoundClassOverriddenMethodsWithoutBindingAttributes_InheritsFromBaseClassNoBindingNoBoundMethods), false, 0, new string[] { })]
[InlineData(typeof(TestSubjects.DerivedUnBoundClassOverriddenMethodsWithoutBindingAttributes_InheritsFromBaseClassNoBindingWithBoundMethods), false, 0, new string[] { })]
[InlineData(typeof(TestSubjects.DerivedUnBoundClassOverriddenMethodsWithoutBindingAttributes_InheritsFromBaseClassWithBindingNoBoundMethods), true, 0, new string[] { })]
[InlineData(typeof(TestSubjects.DerivedUnBoundClassOverriddenMethodsWithoutBindingAttributes_InheritsFromBaseClassWithBindingWithBoundMethods), true, 1, new string[] { })]
public void ProcessType_WithInheritedBindingClasses_ShouldFindBoundMethods(Type bindingClassType, bool outcome, int expectedBoundMethodCount, IEnumerable<string> expectedErrors)
{
//ARRANGE
var sut = CreateBindingSourceProcessor();
var builder = new RuntimeBindingRegistryBuilder(sut, new ReqnrollAttributesFilter(), null, null, null);
//ACT
builder.BuildBindingsFromType(bindingClassType).Should().Be(outcome);
sut.BuildingCompleted();
//ASSERT
sut.StepDefinitionBindings.Should().HaveCount(expectedBoundMethodCount);

foreach (var expectedError in expectedErrors)
{
sut.ValidationErrors.Should().Contain(m => m.Contains(expectedError));
}
}



}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Reqnroll.RuntimeTests.Bindings.Discovery.TestSubjects;

internal class BaseClassNoBindingNoBoundMethods
{
public virtual void UnBoundMethod()
{
}
}

internal class BaseClassNoBindingWithBoundMethods
{
[Given("something")]
public virtual void GivenSomething() { }

public virtual void UnBoundMethod()
{
}
}

[Binding]
internal class BaseClassWithBindingNoBoundMethods
{
public virtual void UnBoundMethod()
{
}
}

[Binding]
internal class BaseClassWithBindingWithBoundMethods
{
[Given("something")]
public virtual void GivenSomething() { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Reqnroll.RuntimeTests.Bindings.Discovery.TestSubjects;

[Binding]
internal class DerivedBoundClassBoundMethods_InheritsFromBaseClassNoBindingNoBoundMethods : BaseClassNoBindingNoBoundMethods
{
public override void UnBoundMethod() { }

[Given("derived something")]
public void GivenDerivedSomething() { }
}

[Binding]
internal class DerivedBoundClassBoundMethods_InheritsFromBaseClassWithBindingNoBoundMethods : BaseClassWithBindingNoBoundMethods
{
public override void UnBoundMethod() { }

[Given("derived something")]
public void GivenDerivedSomething() { }
}

[Binding]
internal class DerivedBoundClassBoundMethods_InheritsFromBaseClassNoBindingWithBoundMethods : BaseClassNoBindingWithBoundMethods
{
[Given("derived something")]
public void GivenDerivedSomething() { }
}

[Binding]
internal class DerivedBoundClassBoundMethods_InheritsFromBaseClassWithBindingWithBoundMethods : BaseClassWithBindingWithBoundMethods
{
[Given("derived something")]
public void GivenDerivedSomething() { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Reqnroll.RuntimeTests.Bindings.Discovery.TestSubjects;

[Binding]
internal class DerivedBoundClassOverriddenMethodsWithoutBindingAttributes_InheritsFromBaseClassNoBindingBoundMethods : BaseClassNoBindingWithBoundMethods
{
public override void GivenSomething()
{
}
}

[Binding]
internal class DerivedBoundClassOverriddenMethodsWithoutBindingAttributes_InheritsFromBaseClassWithBindingBoundMethods : BaseClassWithBindingWithBoundMethods
{
public override void GivenSomething()
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Reqnroll.RuntimeTests.Bindings.Discovery.TestSubjects;

[Binding]
internal class DerivedBoundClassBoundMethodswithBindingaAttributes_InheritsFromBaseClassNoBindingNoBoundMethod : BaseClassNoBindingNoBoundMethods
{
[Given("derived something")]
public override void UnBoundMethod()
{
base.UnBoundMethod();
}
}

[Binding]
internal class DerivedBoundClassBoundMethodswithBindingaAttributes_InheritsFromBaseClassNoBindingBoundMethod : BaseClassNoBindingWithBoundMethods
{
[Given("something")]
public override void GivenSomething()
{
base.GivenSomething();
}
}

[Binding]
internal class DerivedBoundClassBoundMethodswithBindingaAttributes_InheritsFromBaseClassWithBindingNoBoundMethod : BaseClassWithBindingNoBoundMethods
{
[Given("derived something")]
public override void UnBoundMethod()
{
base.UnBoundMethod();
}
}

[Binding]
internal class DerivedBoundClassBoundMethodswithBindingaAttributes_InheritsFromBaseClassWithBindingBoundMethod : BaseClassWithBindingWithBoundMethods
{
[Given("something")]
public override void GivenSomething()
{
base.GivenSomething();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Reqnroll.RuntimeTests.Bindings.Discovery.TestSubjects;

internal class DerivedUnBoundClassNoBoundMethods_InheritsFromBaseClassNoBindingNoBoundMethods : BaseClassNoBindingNoBoundMethods
{
public void AnotherUnBoundMethod()
{
}
}

internal class DerivedUnBoundClassNoBoundMethods_InheritsFromBaseClassNoBindingWithBoundMethods : BaseClassNoBindingWithBoundMethods
{
public void AnotherUnBoundMethod()
{
}
}

internal class DerivedUnBoundClassNoBoundMethods_InheritsFromBaseClassWithBindingNoBoundMethods : BaseClassWithBindingNoBoundMethods
{
public void AnotherUnBoundMethod()
{
}
}

internal class DerivedUnBoundClassNoBoundMethods_InheritsFromBaseClassWithBindingWithBoundMethods : BaseClassWithBindingWithBoundMethods
{
public void AnotherUnBoundMethod()
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Reqnroll.RuntimeTests.Bindings.Discovery.TestSubjects;

internal class DerivedUnBoundClassOveriddenMethodsWithBindingAttributes_InheritsFromBaseClassNoBindingNoBoundMethods : BaseClassNoBindingNoBoundMethods
{
[Given("derived something")]
public override void UnBoundMethod()
{
}
}

internal class DerivedUnBoundClassOveriddenMethodsWithBindingAttributes_InheritsFromBaseClassNoBindingWithBoundMethods : BaseClassNoBindingWithBoundMethods
{
[Given("derived something")]
public override void GivenSomething()
{
}
}


internal class DerivedUnBoundClassOveriddenMethodsWithBindingAttributes_InheritsFromBaseClassWithBindingNoBoundMethods : BaseClassWithBindingNoBoundMethods
{
[Given("derived something")]
public override void UnBoundMethod()
{
}
}

internal class DerivedUnBoundClassOveriddenMethodsWithBindingAttributes_InheritsFromBaseClassWithBindingWithBoundMethods : BaseClassWithBindingWithBoundMethods
{
[Given("derived something")]
public override void GivenSomething()
{
}
}
Loading
Loading