diff --git a/Reqnroll/Bindings/Discovery/BindingSourceProcessor.cs b/Reqnroll/Bindings/Discovery/BindingSourceProcessor.cs index 79dd1b770..dd26d50bb 100644 --- a/Reqnroll/Bindings/Discovery/BindingSourceProcessor.cs +++ b/Reqnroll/Bindings/Discovery/BindingSourceProcessor.cs @@ -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) @@ -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(nameof(StepDefinitionBaseAttribute.ExpressionType)); @@ -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 ErrorMessages { get; } @@ -315,11 +315,33 @@ protected virtual void OnValidationError(BindingValidationResult validationResul protected virtual BindingValidationResult ValidateType(BindingSourceType bindingSourceType) { + bool BindingClassInheritsFromBindingClass(BindingSourceType bindingSourceType) + { + 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; } @@ -405,5 +427,19 @@ private void ApplyForScope(BindingScope[] scopes, Action 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; + } } } diff --git a/Tests/Reqnroll.RuntimeTests/Bindings/Discovery/BindingSourceProcessorTestsWithInheritedBindingClasses.cs b/Tests/Reqnroll.RuntimeTests/Bindings/Discovery/BindingSourceProcessorTestsWithInheritedBindingClasses.cs new file mode 100644 index 000000000..9c20e48eb --- /dev/null +++ b/Tests/Reqnroll.RuntimeTests/Bindings/Discovery/BindingSourceProcessorTestsWithInheritedBindingClasses.cs @@ -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 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)); + } + } + + + +} \ No newline at end of file diff --git a/Tests/Reqnroll.RuntimeTests/Bindings/Discovery/TestSubjects/BaseClasses.cs b/Tests/Reqnroll.RuntimeTests/Bindings/Discovery/TestSubjects/BaseClasses.cs new file mode 100644 index 000000000..69278fcec --- /dev/null +++ b/Tests/Reqnroll.RuntimeTests/Bindings/Discovery/TestSubjects/BaseClasses.cs @@ -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() { } +} diff --git a/Tests/Reqnroll.RuntimeTests/Bindings/Discovery/TestSubjects/DerivedBoundClassBoundMethods.cs b/Tests/Reqnroll.RuntimeTests/Bindings/Discovery/TestSubjects/DerivedBoundClassBoundMethods.cs new file mode 100644 index 000000000..909373898 --- /dev/null +++ b/Tests/Reqnroll.RuntimeTests/Bindings/Discovery/TestSubjects/DerivedBoundClassBoundMethods.cs @@ -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() { } +} \ No newline at end of file diff --git a/Tests/Reqnroll.RuntimeTests/Bindings/Discovery/TestSubjects/DerivedBoundClassOverriddenMethodsWithoutBindingAttributes.cs b/Tests/Reqnroll.RuntimeTests/Bindings/Discovery/TestSubjects/DerivedBoundClassOverriddenMethodsWithoutBindingAttributes.cs new file mode 100644 index 000000000..0c2378f56 --- /dev/null +++ b/Tests/Reqnroll.RuntimeTests/Bindings/Discovery/TestSubjects/DerivedBoundClassOverriddenMethodsWithoutBindingAttributes.cs @@ -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() + { + } +} diff --git a/Tests/Reqnroll.RuntimeTests/Bindings/Discovery/TestSubjects/DerivedBoundClassOverriddenMethodswithBindingaAttributes.cs b/Tests/Reqnroll.RuntimeTests/Bindings/Discovery/TestSubjects/DerivedBoundClassOverriddenMethodswithBindingaAttributes.cs new file mode 100644 index 000000000..62fd3c3df --- /dev/null +++ b/Tests/Reqnroll.RuntimeTests/Bindings/Discovery/TestSubjects/DerivedBoundClassOverriddenMethodswithBindingaAttributes.cs @@ -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(); + } +} diff --git a/Tests/Reqnroll.RuntimeTests/Bindings/Discovery/TestSubjects/DerivedUnBoundClassNoBoundMethods.cs b/Tests/Reqnroll.RuntimeTests/Bindings/Discovery/TestSubjects/DerivedUnBoundClassNoBoundMethods.cs new file mode 100644 index 000000000..ebca55e77 --- /dev/null +++ b/Tests/Reqnroll.RuntimeTests/Bindings/Discovery/TestSubjects/DerivedUnBoundClassNoBoundMethods.cs @@ -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() + { + } +} diff --git a/Tests/Reqnroll.RuntimeTests/Bindings/Discovery/TestSubjects/DerivedUnBoundClassOveriddenMethodsWithBindingAttributes.cs b/Tests/Reqnroll.RuntimeTests/Bindings/Discovery/TestSubjects/DerivedUnBoundClassOveriddenMethodsWithBindingAttributes.cs new file mode 100644 index 000000000..c4abf93ea --- /dev/null +++ b/Tests/Reqnroll.RuntimeTests/Bindings/Discovery/TestSubjects/DerivedUnBoundClassOveriddenMethodsWithBindingAttributes.cs @@ -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() + { + } +} diff --git a/Tests/Reqnroll.RuntimeTests/Bindings/Discovery/TestSubjects/DerivedUnBoundClassOverriddenMethodsWithoutBindingAttributes.cs b/Tests/Reqnroll.RuntimeTests/Bindings/Discovery/TestSubjects/DerivedUnBoundClassOverriddenMethodsWithoutBindingAttributes.cs new file mode 100644 index 000000000..f7517d3d7 --- /dev/null +++ b/Tests/Reqnroll.RuntimeTests/Bindings/Discovery/TestSubjects/DerivedUnBoundClassOverriddenMethodsWithoutBindingAttributes.cs @@ -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 DerivedUnBoundClassOverriddenMethodsWithoutBindingAttributes_InheritsFromBaseClassNoBindingNoBoundMethods : BaseClassNoBindingNoBoundMethods +{ + public override void UnBoundMethod() + { + } +} + +internal class DerivedUnBoundClassOverriddenMethodsWithoutBindingAttributes_InheritsFromBaseClassNoBindingWithBoundMethods : BaseClassNoBindingWithBoundMethods +{ + public override void GivenSomething() + { + } +} + +internal class DerivedUnBoundClassOverriddenMethodsWithoutBindingAttributes_InheritsFromBaseClassWithBindingNoBoundMethods : BaseClassWithBindingNoBoundMethods +{ + public override void UnBoundMethod() + { + } +} + +internal class DerivedUnBoundClassOverriddenMethodsWithoutBindingAttributes_InheritsFromBaseClassWithBindingWithBoundMethods : BaseClassWithBindingWithBoundMethods +{ + public override void GivenSomething() + { + } +} diff --git a/Tests/Reqnroll.RuntimeTests/Bindings/Discovery/TestSubjects/DerivedUnboundClassWithBoundMethods.cs b/Tests/Reqnroll.RuntimeTests/Bindings/Discovery/TestSubjects/DerivedUnboundClassWithBoundMethods.cs new file mode 100644 index 000000000..91189c9d1 --- /dev/null +++ b/Tests/Reqnroll.RuntimeTests/Bindings/Discovery/TestSubjects/DerivedUnboundClassWithBoundMethods.cs @@ -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 DerivedUnboundClassWithBoundMethods_InheritsFromBaseClassNoBindingNoBoundMethods : BaseClassNoBindingNoBoundMethods +{ + [Given("something else")] + public void BoundMethodInDerivedUnboundClass() { } +} + +internal class DerivedUnboundClassWithBoundMethods_InheritsFromBaseClassNoBindingWithBoundMethods : BaseClassNoBindingWithBoundMethods +{ + [Given("something else")] + public void BoundMethodInDerivedUnboundClass() { } + +} + +internal class DerivedUnboundClassWithBoundMethods_InheritsFromBaseClassWithBindingNoBoundMethods : BaseClassWithBindingNoBoundMethods +{ + [Given("something else")] + public void BoundMethodInDerivedUnboundClass() { } + +} + +internal class DerivedUnboundClassWithBoundMethods_InheritsFromBaseClassWithBindingWithBoundMethods : BaseClassWithBindingWithBoundMethods +{ + [Given("something else")] + public void BoundMethodInDerivedUnboundClass() { } + +} +