From a0d8f395ad8ce644c6586097d77a878e1319d5d3 Mon Sep 17 00:00:00 2001 From: Yazide Boujlil Date: Mon, 10 Sep 2012 13:16:02 +0200 Subject: [PATCH 01/26] Split of StepArgumentTypeConverter logic Converted StepArgumentTypeConverter into a chain of responsibility delegating the conversion logic to: - IdentityConverter which returns the untouched value if it can be assigned to a variable of the target type (logic comes from StepDefinitionMatchService and TestExecutionEngine) - StepArgumentTransformationConverter which tries to perform the conversion using user defined step argument transformations - SimpleConverter which handle the simple conversions: enums, GUID and whatever is supported by System.Convert.ChangeType --- Runtime/Bindings/IndentityConverter.cs | 18 +++ Runtime/Bindings/SimpleConverter.cs | 54 ++++++++ .../StepArgumentTransformationConverter.cs | 88 ++++++++++++ Runtime/Bindings/StepArgumentTypeConverter.cs | 129 ++---------------- Runtime/TechTalk.SpecFlow.csproj | 3 + 5 files changed, 175 insertions(+), 117 deletions(-) create mode 100644 Runtime/Bindings/IndentityConverter.cs create mode 100644 Runtime/Bindings/SimpleConverter.cs create mode 100644 Runtime/Bindings/StepArgumentTransformationConverter.cs diff --git a/Runtime/Bindings/IndentityConverter.cs b/Runtime/Bindings/IndentityConverter.cs new file mode 100644 index 000000000..08d6ed90e --- /dev/null +++ b/Runtime/Bindings/IndentityConverter.cs @@ -0,0 +1,18 @@ +using System.Globalization; +using TechTalk.SpecFlow.Bindings.Reflection; + +namespace TechTalk.SpecFlow.Bindings +{ + internal class IndentityConverter : IStepArgumentTypeConverter + { + public object Convert(object value, IBindingType typeToConvertTo, CultureInfo cultureInfo) + { + return value; + } + + public bool CanConvert(object value, IBindingType typeToConvertTo, CultureInfo cultureInfo) + { + return typeToConvertTo.IsAssignableTo(value.GetType()); + } + } +} diff --git a/Runtime/Bindings/SimpleConverter.cs b/Runtime/Bindings/SimpleConverter.cs new file mode 100644 index 000000000..d53013467 --- /dev/null +++ b/Runtime/Bindings/SimpleConverter.cs @@ -0,0 +1,54 @@ +using System; +using System.Globalization; +using TechTalk.SpecFlow.Assist.ValueRetrievers; +using TechTalk.SpecFlow.Bindings.Reflection; + +namespace TechTalk.SpecFlow.Bindings +{ + internal class SimpleConverter : IStepArgumentTypeConverter + { + public object Convert(object value, IBindingType typeToConvertTo, CultureInfo cultureInfo) + { + var runtimeType = (RuntimeBindingType)typeToConvertTo; + + if (runtimeType.Type.IsEnum && value is string) + return Enum.Parse(runtimeType.Type, (string)value, true); + + if (runtimeType.Type == typeof(Guid?) && string.IsNullOrEmpty(value as string)) + return null; + + if (runtimeType.Type == typeof(Guid) || runtimeType.Type == typeof(Guid?)) + return new GuidValueRetriever().GetValue(value as string); + + return System.Convert.ChangeType(value, runtimeType.Type, cultureInfo); + } + + public bool CanConvert(object value, IBindingType typeToConvertTo, CultureInfo cultureInfo) + { + if (!(typeToConvertTo is RuntimeBindingType)) + return false; + + try + { + Convert(value, typeToConvertTo, cultureInfo); + return true; + } + catch (InvalidCastException) + { + return false; + } + catch (OverflowException) + { + return false; + } + catch (FormatException) + { + return false; + } + catch (ArgumentException) + { + return false; + } + } + } +} diff --git a/Runtime/Bindings/StepArgumentTransformationConverter.cs b/Runtime/Bindings/StepArgumentTransformationConverter.cs new file mode 100644 index 000000000..e13e633e0 --- /dev/null +++ b/Runtime/Bindings/StepArgumentTransformationConverter.cs @@ -0,0 +1,88 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Text.RegularExpressions; +using TechTalk.SpecFlow.Bindings.Reflection; +using TechTalk.SpecFlow.Infrastructure; +using TechTalk.SpecFlow.Tracing; + +namespace TechTalk.SpecFlow.Bindings +{ + internal class StepArgumentTransformationConverter : IStepArgumentTypeConverter + { + private readonly IStepArgumentTypeConverter stepArgumentTypeConverter; + private readonly ITestTracer testTracer; + private readonly IBindingRegistry bindingRegistry; + private readonly IContextManager contextManager; + private readonly IBindingInvoker bindingInvoker; + + public StepArgumentTransformationConverter(IStepArgumentTypeConverter stepArgumentTypeConverter, ITestTracer testTracer, IBindingRegistry bindingRegistry, IContextManager contextManager, IBindingInvoker bindingInvoker) + { + this.stepArgumentTypeConverter = stepArgumentTypeConverter; + this.testTracer = testTracer; + this.bindingRegistry = bindingRegistry; + this.contextManager = contextManager; + this.bindingInvoker = bindingInvoker; + } + + public object Convert(object value, IBindingType typeToConvertTo, CultureInfo cultureInfo) + { + var stepTransformation = GetMatchingStepTransformation(value, typeToConvertTo, true); + + if (stepTransformation == null) + throw new SpecFlowException("The StepTransformationConverter cannot convert the specified value."); + + return DoTransform(stepTransformation, value, cultureInfo); + } + + public bool CanConvert(object value, IBindingType typeToConvertTo, CultureInfo cultureInfo) + { + return GetMatchingStepTransformation(value, typeToConvertTo, false) != null; + } + + private IStepArgumentTransformationBinding GetMatchingStepTransformation(object value, IBindingType typeToConvertTo, bool traceWarning) + { + var stepTransformations = bindingRegistry.GetStepTransformations().Where(t => CanConvert(t, value, typeToConvertTo)).ToArray(); + + if (stepTransformations.Length > 1 && traceWarning) + { + testTracer.TraceWarning(string.Format("Multiple step transformation matches to the input ({0}, target type: {1}). We use the first.", value, typeToConvertTo)); + } + + return stepTransformations.FirstOrDefault(); + } + + private bool CanConvert(IStepArgumentTransformationBinding stepTransformationBinding, object value, IBindingType typeToConvertTo) + { + if (!stepTransformationBinding.Method.ReturnType.TypeEquals(typeToConvertTo)) + return false; + + if (stepTransformationBinding.Regex != null && value is string) + return stepTransformationBinding.Regex.IsMatch((string)value); + + return true; + } + + private object DoTransform(IStepArgumentTransformationBinding stepTransformation, object value, CultureInfo cultureInfo) + { + object[] arguments; + if (stepTransformation.Regex != null && value is string) + arguments = GetStepTransformationArgumentsFromRegex(stepTransformation, (string)value, cultureInfo); + else + arguments = new object[] { value }; + + TimeSpan duration; + return bindingInvoker.InvokeBinding(stepTransformation, contextManager, arguments, testTracer, out duration); + } + + private object[] GetStepTransformationArgumentsFromRegex(IStepArgumentTransformationBinding stepTransformation, string stepSnippet, CultureInfo cultureInfo) + { + var match = stepTransformation.Regex.Match(stepSnippet); + var argumentStrings = match.Groups.Cast().Skip(1).Select(g => g.Value); + var bindingParameters = stepTransformation.Method.Parameters.ToArray(); + return argumentStrings + .Select((arg, argIndex) => stepArgumentTypeConverter.Convert(arg, bindingParameters[argIndex].Type, cultureInfo)) + .ToArray(); + } + } +} diff --git a/Runtime/Bindings/StepArgumentTypeConverter.cs b/Runtime/Bindings/StepArgumentTypeConverter.cs index e603e1cc2..fbd55c821 100644 --- a/Runtime/Bindings/StepArgumentTypeConverter.cs +++ b/Runtime/Bindings/StepArgumentTypeConverter.cs @@ -1,11 +1,9 @@ -using System; using System.Globalization; using System.Linq; -using System.Text.RegularExpressions; -using TechTalk.SpecFlow.Assist.ValueRetrievers; using TechTalk.SpecFlow.Bindings.Reflection; using TechTalk.SpecFlow.Infrastructure; using TechTalk.SpecFlow.Tracing; +using System.Collections.Generic; namespace TechTalk.SpecFlow.Bindings { @@ -17,135 +15,32 @@ public interface IStepArgumentTypeConverter public class StepArgumentTypeConverter : IStepArgumentTypeConverter { - private readonly ITestTracer testTracer; - private readonly IBindingRegistry bindingRegistry; - private readonly IContextManager contextManager; - private readonly IBindingInvoker bindingInvoker; + private readonly IEnumerable converters; public StepArgumentTypeConverter(ITestTracer testTracer, IBindingRegistry bindingRegistry, IContextManager contextManager, IBindingInvoker bindingInvoker) { - this.testTracer = testTracer; - this.bindingRegistry = bindingRegistry; - this.contextManager = contextManager; - this.bindingInvoker = bindingInvoker; - } - - 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) + converters = new IStepArgumentTypeConverter[] { - testTracer.TraceWarning(string.Format("Multiple step transformation matches to the input ({0}, target type: {1}). We use the first.", value, typeToConvertTo)); - } - - return stepTransformations.Length > 0 ? stepTransformations[0] : null; + new IndentityConverter(), + new StepArgumentTransformationConverter(this, testTracer, bindingRegistry, contextManager, bindingInvoker), + new SimpleConverter() + }; } public object Convert(object value, IBindingType typeToConvertTo, CultureInfo cultureInfo) { - if (value == null) throw new ArgumentNullException("value"); - - if (typeToConvertTo == value.GetType()) - return value; - - var stepTransformation = GetMatchingStepTransformation(value, typeToConvertTo, true); - if (stepTransformation != null) - return DoTransform(stepTransformation, value, cultureInfo); - - return ConvertSimple(typeToConvertTo, value, cultureInfo); - } - - private object DoTransform(IStepArgumentTransformationBinding stepTransformation, object value, CultureInfo cultureInfo) - { - object[] arguments; - if (stepTransformation.Regex != null && value is string) - arguments = GetStepTransformationArgumentsFromRegex(stepTransformation, (string)value, cultureInfo); - else - arguments = new object[] {value}; - - TimeSpan duration; - return bindingInvoker.InvokeBinding(stepTransformation, contextManager, arguments, testTracer, out duration); - } - - private object[] GetStepTransformationArgumentsFromRegex(IStepArgumentTransformationBinding stepTransformation, string stepSnippet, CultureInfo cultureInfo) - { - var match = stepTransformation.Regex.Match(stepSnippet); - var argumentStrings = match.Groups.Cast().Skip(1).Select(g => g.Value); - var bindingParameters = stepTransformation.Method.Parameters.ToArray(); - return argumentStrings - .Select((arg, argIndex) => this.Convert(arg, bindingParameters[argIndex].Type, cultureInfo)) - .ToArray(); + var converter = GetConverter(value, typeToConvertTo, cultureInfo); + return converter.Convert(value, typeToConvertTo, cultureInfo); } public bool CanConvert(object value, IBindingType typeToConvertTo, CultureInfo cultureInfo) { - if (value == null) throw new ArgumentNullException("value"); - - if (typeToConvertTo == value.GetType()) - return true; - - var stepTransformation = GetMatchingStepTransformation(value, typeToConvertTo, false); - if (stepTransformation != null) - return true; - - return CanConvertSimple(typeToConvertTo, value, cultureInfo); - } - - private bool CanConvert(IStepArgumentTransformationBinding stepTransformationBinding, object value, IBindingType typeToConvertTo) - { - if (!stepTransformationBinding.Method.ReturnType.TypeEquals(typeToConvertTo)) - return false; - - if (stepTransformationBinding.Regex != null && value is string) - return stepTransformationBinding.Regex.IsMatch((string) value); - return true; - } - - private static object ConvertSimple(IBindingType typeToConvertTo, object value, CultureInfo cultureInfo) - { - if (!(typeToConvertTo is RuntimeBindingType)) - throw new SpecFlowException("The StepArgumentTypeConverter can be used with runtime types only."); - - return ConvertSimple(((RuntimeBindingType) typeToConvertTo).Type, value, cultureInfo); - } - - private static object ConvertSimple(Type typeToConvertTo, object value, CultureInfo cultureInfo) - { - if (typeToConvertTo.IsEnum && value is string) - return Enum.Parse(typeToConvertTo, (string)value, true); - - if (typeToConvertTo == typeof(Guid?) && string.IsNullOrEmpty(value as string)) - return null; - - if (typeToConvertTo == typeof(Guid) || typeToConvertTo == typeof(Guid?)) - return new GuidValueRetriever().GetValue(value as string); - - return System.Convert.ChangeType(value, typeToConvertTo, cultureInfo); + return GetConverter(value, typeToConvertTo, cultureInfo) != null; } - public static bool CanConvertSimple(IBindingType typeToConvertTo, object value, CultureInfo cultureInfo) + private IStepArgumentTypeConverter GetConverter(object value, IBindingType typeToConvertTo, CultureInfo cultureInfo) { - try - { - ConvertSimple(typeToConvertTo, value, cultureInfo); - return true; - } - catch (InvalidCastException) - { - return false; - } - catch (OverflowException) - { - return false; - } - catch (FormatException) - { - return false; - } - catch (ArgumentException) - { - return false; - } + return converters.FirstOrDefault(x => x.CanConvert(value, typeToConvertTo, cultureInfo)); } } } diff --git a/Runtime/TechTalk.SpecFlow.csproj b/Runtime/TechTalk.SpecFlow.csproj index 590300157..67c3c7ddd 100644 --- a/Runtime/TechTalk.SpecFlow.csproj +++ b/Runtime/TechTalk.SpecFlow.csproj @@ -132,6 +132,7 @@ + @@ -148,6 +149,8 @@ + + From b1f4309b09805517351863309255e2c785491fd7 Mon Sep 17 00:00:00 2001 From: Yazide Boujlil Date: Tue, 11 Sep 2012 21:08:06 +0200 Subject: [PATCH 02/26] Improved StepArgumentTransformation validation Fixed bug causing StepArgumentTransformation regex to be bypassed when argument is not a string. Added check for StepArgumentTransformation to have one and only one parameter when no regex is defined. --- .../StepArgumentTransformationConverter.cs | 8 +- Tests/RuntimeTests/StepTransformationTests.cs | 297 ++++++++++-------- 2 files changed, 172 insertions(+), 133 deletions(-) diff --git a/Runtime/Bindings/StepArgumentTransformationConverter.cs b/Runtime/Bindings/StepArgumentTransformationConverter.cs index e13e633e0..1d8dafb65 100644 --- a/Runtime/Bindings/StepArgumentTransformationConverter.cs +++ b/Runtime/Bindings/StepArgumentTransformationConverter.cs @@ -57,10 +57,12 @@ private bool CanConvert(IStepArgumentTransformationBinding stepTransformationBin if (!stepTransformationBinding.Method.ReturnType.TypeEquals(typeToConvertTo)) return false; - if (stepTransformationBinding.Regex != null && value is string) - return stepTransformationBinding.Regex.IsMatch((string)value); + if (stepTransformationBinding.Regex != null) + { + return value is string && stepTransformationBinding.Regex.IsMatch((string)value); + } - return true; + return stepTransformationBinding.Method.Parameters.Count() == 1; } private object DoTransform(IStepArgumentTransformationBinding stepTransformation, object value, CultureInfo cultureInfo) diff --git a/Tests/RuntimeTests/StepTransformationTests.cs b/Tests/RuntimeTests/StepTransformationTests.cs index aa08878f0..ed5e575e9 100644 --- a/Tests/RuntimeTests/StepTransformationTests.cs +++ b/Tests/RuntimeTests/StepTransformationTests.cs @@ -1,131 +1,168 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Reflection; -using Moq; -using NUnit.Framework; -using TechTalk.SpecFlow.Bindings; -using TechTalk.SpecFlow.Bindings.Reflection; -using TechTalk.SpecFlow.Configuration; -using TechTalk.SpecFlow.ErrorHandling; -using TechTalk.SpecFlow.Infrastructure; -using TechTalk.SpecFlow.Tracing; - -namespace TechTalk.SpecFlow.RuntimeTests -{ - public class User - { - public string Name { get; set; } - } - - [Binding] - public class UserCreator - { - [StepArgumentTransformation("user (w+)")] - public User Create(string name) - { - return new User {Name = name}; - } - - [StepArgumentTransformation] - public IEnumerable CreateUsers(Table table) - { - return table.Rows.Select(tableRow => - new User { Name = tableRow["Name"] }); - } - } - - [TestFixture] - public class StepTransformationTests - { - private readonly Mock bindingRegistryStub = new Mock(); - private readonly Mock contextManagerStub = new Mock(); - private readonly Mock methodBindingInvokerStub = new Mock(); - private readonly List stepTransformations = new List(); - - [SetUp] - public void SetUp() - { - // ScenarioContext is needed, because the [Binding]-instances live there - var scenarioContext = new ScenarioContext(null, null, null); - contextManagerStub.Setup(cm => cm.ScenarioContext).Returns(scenarioContext); - - bindingRegistryStub.Setup(br => br.GetStepTransformations()).Returns(stepTransformations); - } - - private IStepArgumentTransformationBinding CreateStepTransformationBinding(string regexString, IBindingMethod transformMethod) - { - return new StepArgumentTransformationBinding(regexString, transformMethod); - } - - private IStepArgumentTransformationBinding CreateStepTransformationBinding(string regexString, MethodInfo transformMethod) - { - return new StepArgumentTransformationBinding(regexString, new RuntimeBindingMethod(transformMethod)); - } - - [Test] - public void UserConverterShouldConvertStringToUser() - { - UserCreator stepTransformationInstance = new UserCreator(); - var transformMethod = stepTransformationInstance.GetType().GetMethod("Create"); - var stepTransformationBinding = CreateStepTransformationBinding(@"user (\w+)", transformMethod); - - Assert.True(stepTransformationBinding.Regex.IsMatch("user xyz")); - - var invoker = new BindingInvoker(new RuntimeConfiguration(), new Mock().Object); - TimeSpan duration; - var result = invoker.InvokeBinding(stepTransformationBinding, contextManagerStub.Object, new object[] { "xyz" }, new Mock().Object, out duration); - Assert.NotNull(result); - Assert.That(result.GetType(), Is.EqualTo(typeof(User))); - Assert.That(((User)result).Name, Is.EqualTo("xyz")); - } - - [Test] - public void StepArgumentTypeConverterShouldUseUserConverterForConversion() - { - UserCreator stepTransformationInstance = new UserCreator(); - var transformMethod = new RuntimeBindingMethod(stepTransformationInstance.GetType().GetMethod("Create")); - var stepTransformationBinding = CreateStepTransformationBinding(@"user (\w+)", transformMethod); - stepTransformations.Add(stepTransformationBinding); - TimeSpan duration; - var resultUser = new User(); - methodBindingInvokerStub.Setup(i => i.InvokeBinding(stepTransformationBinding, It.IsAny(), It.IsAny(), It.IsAny(), out duration)) - .Returns(resultUser); - - var stepArgumentTypeConverter = CreateStepArgumentTypeConverter(); - - var result = stepArgumentTypeConverter.Convert("user xyz", typeof(User), new CultureInfo("en-US")); - Assert.That(result, Is.EqualTo(resultUser)); - } - - private StepArgumentTypeConverter CreateStepArgumentTypeConverter() - { - return new StepArgumentTypeConverter(new Mock().Object, bindingRegistryStub.Object, contextManagerStub.Object, methodBindingInvokerStub.Object); - } - - [Test] - public void ShouldUseStepArgumentTransformationToConvertTable() - { - var table = new Table("Name"); - - UserCreator stepTransformationInstance = new UserCreator(); - var transformMethod = new RuntimeBindingMethod(stepTransformationInstance.GetType().GetMethod("CreateUsers")); - var stepTransformationBinding = CreateStepTransformationBinding(@"", transformMethod); - stepTransformations.Add(stepTransformationBinding); - TimeSpan duration; - var resultUsers = new User[3]; - methodBindingInvokerStub.Setup(i => i.InvokeBinding(stepTransformationBinding, It.IsAny(), new object[] { table }, It.IsAny(), out duration)) - .Returns(resultUsers); - - var stepArgumentTypeConverter = CreateStepArgumentTypeConverter(); - - - var result = stepArgumentTypeConverter.Convert(table, typeof(IEnumerable), new CultureInfo("en-US")); - - Assert.That(result, Is.Not.Null); - Assert.That(result, Is.EqualTo(resultUsers)); - } - } - +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +using Moq; +using NUnit.Framework; +using TechTalk.SpecFlow.Bindings; +using TechTalk.SpecFlow.Bindings.Reflection; +using TechTalk.SpecFlow.Configuration; +using TechTalk.SpecFlow.ErrorHandling; +using TechTalk.SpecFlow.Infrastructure; +using TechTalk.SpecFlow.Tracing; + +namespace TechTalk.SpecFlow.RuntimeTests +{ + public class User + { + public string Name { get; set; } + } + + [Binding] + public class UserCreator + { + [StepArgumentTransformation(@"user (\w+)")] + public User Create(string name) + { + return new User {Name = name}; + } + + [StepArgumentTransformation] + public IEnumerable CreateUsers(Table table) + { + return table.Rows.Select(tableRow => + new User { Name = tableRow["Name"] }); + } + } + + [TestFixture] + public class StepTransformationTests + { + private readonly Mock bindingRegistryStub = new Mock(); + private readonly Mock contextManagerStub = new Mock(); + private readonly List stepTransformations = new List(); + + [SetUp] + public void SetUp() + { + // ScenarioContext is needed, because the [Binding]-instances live there + var scenarioContext = new ScenarioContext(null, null, null); + contextManagerStub.Setup(cm => cm.ScenarioContext).Returns(scenarioContext); + + bindingRegistryStub.Setup(br => br.GetStepTransformations()).Returns(stepTransformations); + } + + private IStepArgumentTransformationBinding CreateStepTransformationBinding(MethodInfo transformMethod) + { + var regexString = transformMethod.GetCustomAttributes(typeof(StepArgumentTransformationAttribute), false) + .OfType().Select(x => x.Regex).FirstOrDefault(); + return new StepArgumentTransformationBinding(regexString, new RuntimeBindingMethod(transformMethod)); + } + + [Test] + public void UserConverterShouldConvertStringToUser() + { + UserCreator stepTransformationInstance = new UserCreator(); + var transformMethod = stepTransformationInstance.GetType().GetMethod("Create"); + var stepTransformationBinding = CreateStepTransformationBinding(transformMethod); + + Assert.True(stepTransformationBinding.Regex.IsMatch("user xyz")); + + var invoker = new BindingInvoker(new RuntimeConfiguration(), new Mock().Object); + TimeSpan duration; + var result = invoker.InvokeBinding(stepTransformationBinding, contextManagerStub.Object, new object[] { "xyz" }, new Mock().Object, out duration); + Assert.NotNull(result); + Assert.That(result.GetType(), Is.EqualTo(typeof(User))); + Assert.That(((User)result).Name, Is.EqualTo("xyz")); + } + + [Test] + public void StepArgumentTypeConverterShouldUseUserConverterForConversion() + { + UserCreator stepTransformationInstance = new UserCreator(); + var transformMethod = stepTransformationInstance.GetType().GetMethod("Create"); + var stepTransformationBinding = CreateStepTransformationBinding(transformMethod); + stepTransformations.Clear(); + stepTransformations.Add(stepTransformationBinding); + TimeSpan duration; + var resultUser = new User(); + var methodBindingInvokerStub = new Mock(); + methodBindingInvokerStub.Setup(i => i.InvokeBinding(stepTransformationBinding, It.IsAny(), It.IsAny(), It.IsAny(), out duration)) + .Returns(resultUser); + + var stepArgumentTypeConverter = CreateStepArgumentTypeConverter(methodBindingInvokerStub.Object); + + var result = stepArgumentTypeConverter.Convert("user xyz", typeof(User), new CultureInfo("en-US")); + Assert.That(result, Is.EqualTo(resultUser)); + } + + [Test] + public void StepArgumentTypeConverterShouldNotUseUserConverterForStringConversionIfRegexDoesNotMatch() + { + var transformMethod = typeof(UserCreator).GetMethod("Create"); + var stepTransformationBinding = CreateStepTransformationBinding(transformMethod); + stepTransformations.Clear(); + stepTransformations.Add(stepTransformationBinding); + TimeSpan duration; + var resultUser = new User(); + var methodBindingInvokerStub = new Mock(); + methodBindingInvokerStub.Setup(i => i.InvokeBinding(stepTransformationBinding, It.IsAny(), It.IsAny(), It.IsAny(), out duration)) + .Returns(resultUser); + + var stepArgumentTypeConverter = CreateStepArgumentTypeConverter(methodBindingInvokerStub.Object); + + var result = stepArgumentTypeConverter.CanConvert("xyz", new RuntimeBindingType(typeof(User)), new CultureInfo("en-US")); + Assert.That(result, Is.False); + } + + [Test] + public void StepArgumentTypeConverterShouldNotUseUserConverterForNonStringConvertion() + { + var transformMethod = typeof(UserCreator).GetMethod("Create"); + var stepTransformationBinding = CreateStepTransformationBinding(transformMethod); + stepTransformations.Clear(); + stepTransformations.Add(stepTransformationBinding); + TimeSpan duration; + var resultUser = new User(); + var methodBindingInvokerStub = new Mock(); + methodBindingInvokerStub.Setup(i => i.InvokeBinding(stepTransformationBinding, It.IsAny(), It.IsAny(), It.IsAny(), out duration)) + .Returns(resultUser); + + var stepArgumentTypeConverter = CreateStepArgumentTypeConverter(methodBindingInvokerStub.Object); + + var table = new Table("Name"); + table.AddRow("xyz"); + var result = stepArgumentTypeConverter.CanConvert(table, new RuntimeBindingType(typeof(User)), new CultureInfo("en-US")); + Assert.That(result, Is.False); + } + + private StepArgumentTypeConverter CreateStepArgumentTypeConverter(IBindingInvoker bindingInvoker) + { + return new StepArgumentTypeConverter(new Mock().Object, bindingRegistryStub.Object, contextManagerStub.Object, bindingInvoker); + } + + [Test] + public void ShouldUseStepArgumentTransformationToConvertTable() + { + var table = new Table("Name"); + + UserCreator stepTransformationInstance = new UserCreator(); + var transformMethod = stepTransformationInstance.GetType().GetMethod("CreateUsers"); + var stepTransformationBinding = CreateStepTransformationBinding(transformMethod); + stepTransformations.Clear(); + stepTransformations.Add(stepTransformationBinding); + TimeSpan duration; + var resultUsers = new User[3]; + var methodBindingInvokerStub = new Mock(); + methodBindingInvokerStub.Setup(i => i.InvokeBinding(stepTransformationBinding, It.IsAny(), new object[] { table }, It.IsAny(), out duration)) + .Returns(resultUsers); + + var stepArgumentTypeConverter = CreateStepArgumentTypeConverter(methodBindingInvokerStub.Object); + var result = stepArgumentTypeConverter.Convert(table, typeof(IEnumerable), new CultureInfo("en-US")); + + Assert.That(result, Is.Not.Null); + Assert.That(result, Is.EqualTo(resultUsers)); + } + } } \ No newline at end of file From aff34a3933d49a0cfe272b8fbfee324fed4defa0 Mon Sep 17 00:00:00 2001 From: Yazide Boujlil Date: Tue, 11 Sep 2012 21:13:46 +0200 Subject: [PATCH 03/26] StepArgumentTypeConverter support spaces in enums Sames behaviour as in SpecFlow Assist namespace --- Runtime/Bindings/SimpleConverter.cs | 2 +- Tests/RuntimeTests/StepArgumentTypeConverterTest.cs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Runtime/Bindings/SimpleConverter.cs b/Runtime/Bindings/SimpleConverter.cs index d53013467..049c119f7 100644 --- a/Runtime/Bindings/SimpleConverter.cs +++ b/Runtime/Bindings/SimpleConverter.cs @@ -12,7 +12,7 @@ public object Convert(object value, IBindingType typeToConvertTo, CultureInfo cu var runtimeType = (RuntimeBindingType)typeToConvertTo; if (runtimeType.Type.IsEnum && value is string) - return Enum.Parse(runtimeType.Type, (string)value, true); + return Enum.Parse(runtimeType.Type, ((string)value).Replace(" ", ""), true); if (runtimeType.Type == typeof(Guid?) && string.IsNullOrEmpty(value as string)) return null; diff --git a/Tests/RuntimeTests/StepArgumentTypeConverterTest.cs b/Tests/RuntimeTests/StepArgumentTypeConverterTest.cs index 8d1c6b75e..3aa587bd6 100644 --- a/Tests/RuntimeTests/StepArgumentTypeConverterTest.cs +++ b/Tests/RuntimeTests/StepArgumentTypeConverterTest.cs @@ -67,6 +67,13 @@ public void ShouldConvertStringToEnumerationType() Assert.That(result, Is.EqualTo(TestEnumeration.Value1)); } + [Test] + public void ShouldConvertStringToEnumerationTypeWithSpaces() + { + var result = _stepArgumentTypeConverter.Convert("Value 1", typeof(TestEnumeration), _enUSCulture); + Assert.That(result, Is.EqualTo(TestEnumeration.Value1)); + } + [Test] public void ShouldConvertStringToEnumerationTypeWithDifferingCase() { From 858f5824d49a9ec029a6c946ad1e1651503c10f2 Mon Sep 17 00:00:00 2001 From: Yazide Boujlil Date: Tue, 11 Sep 2012 21:46:30 +0200 Subject: [PATCH 04/26] StepArgumentTypeConverter support for tables Added support for table conversion directly in StepArgumentTypeConverter. --- Runtime/Bindings/HorizontalTableConverter.cs | 76 ++++++ Runtime/Bindings/StepArgumentTypeConverter.cs | 2 + Runtime/Bindings/TableConverterBase.cs | 52 ++++ Runtime/Bindings/VerticalTableConverter.cs | 143 ++++++++++ Runtime/TechTalk.SpecFlow.csproj | 3 + Tests/RuntimeTests/RuntimeTests.csproj | 1 + Tests/RuntimeTests/StepTransformationTests.cs | 4 +- Tests/RuntimeTests/TableConverterTests.cs | 252 ++++++++++++++++++ 8 files changed, 531 insertions(+), 2 deletions(-) create mode 100644 Runtime/Bindings/HorizontalTableConverter.cs create mode 100644 Runtime/Bindings/TableConverterBase.cs create mode 100644 Runtime/Bindings/VerticalTableConverter.cs create mode 100644 Tests/RuntimeTests/TableConverterTests.cs diff --git a/Runtime/Bindings/HorizontalTableConverter.cs b/Runtime/Bindings/HorizontalTableConverter.cs new file mode 100644 index 000000000..6278c9f89 --- /dev/null +++ b/Runtime/Bindings/HorizontalTableConverter.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using TechTalk.SpecFlow.Bindings.Reflection; +using System.Globalization; +using TechTalk.SpecFlow.Assist; + +namespace TechTalk.SpecFlow.Bindings +{ + internal class HorizontalTableConverter : TableConverterBase + { + private readonly IStepArgumentTypeConverter stepArgumentTypeConverter; + + public HorizontalTableConverter(IStepArgumentTypeConverter stepArgumentTypeConverter) + { + this.stepArgumentTypeConverter = stepArgumentTypeConverter; + } + + protected override object Convert(Table table, IBindingType typeToConvertTo, CultureInfo cultureInfo) + { + var itemRuntimeType = GetItemRuntimeType(typeToConvertTo); + var rowFetcher = GetValidRowFetcherDelegate(table, itemRuntimeType, cultureInfo); + var result = Array.CreateInstance(itemRuntimeType.Type, table.RowCount); + + for (int i = 0; i < result.Length; i++) + { + var item = stepArgumentTypeConverter.Convert(rowFetcher(i), itemRuntimeType, cultureInfo); + result.SetValue(item, i); + } + + return result; + } + + protected override bool CanConvert(Table table, IBindingType typeToConvertTo, CultureInfo cultureInfo) + { + var itemRuntimeType = GetItemRuntimeType(typeToConvertTo); + if (itemRuntimeType == null || IsVerticalTable(table, itemRuntimeType)) + return false; + + var rowFetcher = GetValidRowFetcherDelegate(table, itemRuntimeType, cultureInfo); + return rowFetcher != null; + } + + private RuntimeBindingType GetItemRuntimeType(IBindingType typeToConvertTo) + { + var runtimeType = typeToConvertTo as RuntimeBindingType; + if (runtimeType == null) + return null; + + var type = runtimeType.Type; + + if (type.IsArray && type.GetArrayRank() == 1) + return new RuntimeBindingType(type.GetElementType()); + + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + return new RuntimeBindingType(type.GetGenericArguments()[0]); + + return null; + } + + private Func GetValidRowFetcherDelegate(Table table, IBindingType typeToConvertTo, CultureInfo cultureInfo) + { + if (table.RowCount == 0) + return x => null; + + var pivotTable = new PivotTable(table); + if (stepArgumentTypeConverter.CanConvert(pivotTable.GetInstanceTable(0), typeToConvertTo, cultureInfo)) + return x => pivotTable.GetInstanceTable(x); + + if (table.Header.Count == 1 && + stepArgumentTypeConverter.CanConvert(table.Rows[0][0], typeToConvertTo, cultureInfo)) + return x => table.Rows[x][0]; + + return null; + } + } +} diff --git a/Runtime/Bindings/StepArgumentTypeConverter.cs b/Runtime/Bindings/StepArgumentTypeConverter.cs index fbd55c821..067cd9f14 100644 --- a/Runtime/Bindings/StepArgumentTypeConverter.cs +++ b/Runtime/Bindings/StepArgumentTypeConverter.cs @@ -23,6 +23,8 @@ public StepArgumentTypeConverter(ITestTracer testTracer, IBindingRegistry bindin { new IndentityConverter(), new StepArgumentTransformationConverter(this, testTracer, bindingRegistry, contextManager, bindingInvoker), + new VerticalTableConverter(this), + new HorizontalTableConverter(this), new SimpleConverter() }; } diff --git a/Runtime/Bindings/TableConverterBase.cs b/Runtime/Bindings/TableConverterBase.cs new file mode 100644 index 000000000..d83d40f72 --- /dev/null +++ b/Runtime/Bindings/TableConverterBase.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using TechTalk.SpecFlow.Bindings.Reflection; +using System.Globalization; +using System.Reflection; + +namespace TechTalk.SpecFlow.Bindings +{ + internal abstract class TableConverterBase : IStepArgumentTypeConverter + { + protected abstract object Convert(Table table, IBindingType typeToConvertTo, CultureInfo cultureInfo); + + protected abstract bool CanConvert(Table table, IBindingType typeToConvertTo, CultureInfo cultureInfo); + + public object Convert(object value, IBindingType typeToConvertTo, CultureInfo cultureInfo) + { + return Convert((Table)value, typeToConvertTo, cultureInfo); + } + + public bool CanConvert(object value, IBindingType typeToConvertTo, CultureInfo cultureInfo) + { + var table = value as Table; + if (table == null) + return false; + + return CanConvert(table, typeToConvertTo, cultureInfo); + } + + protected static bool IsVerticalTable(Table table, RuntimeBindingType runtimeBindingType) + { + if (table.Header.Count != 2) + return false; + + if (table.RowCount == 0) + return true; + + var writableProperties = GetWritableProperties(runtimeBindingType.Type); + return writableProperties.ContainsKey(SanitizePropertyName(table.Rows[0][0])); + } + + protected static Dictionary GetWritableProperties(Type type) + { + return type.GetProperties().Where(x => x.CanWrite).ToDictionary(x => SanitizePropertyName(x.Name)); + } + + protected static string SanitizePropertyName(string name) + { + return name.Replace(" ", "").ToLowerInvariant(); + } + } +} diff --git a/Runtime/Bindings/VerticalTableConverter.cs b/Runtime/Bindings/VerticalTableConverter.cs new file mode 100644 index 000000000..bd425e23f --- /dev/null +++ b/Runtime/Bindings/VerticalTableConverter.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using TechTalk.SpecFlow.Bindings.Reflection; +using System.Globalization; +using System.Reflection; +using TechTalk.SpecFlow.Assist; + +namespace TechTalk.SpecFlow.Bindings +{ + internal class VerticalTableConverter : TableConverterBase + { + private readonly IStepArgumentTypeConverter stepArgumentTypeConverter; + + public VerticalTableConverter(IStepArgumentTypeConverter stepArgumentTypeConverter) + { + this.stepArgumentTypeConverter = stepArgumentTypeConverter; + } + + protected override object Convert(Table table, IBindingType typeToConvertTo, CultureInfo cultureInfo) + { + var runtimeBindingType = (RuntimeBindingType)typeToConvertTo; + var verticalTable = GetVerticalTable(table, runtimeBindingType); + var values = GetValues(verticalTable); + var objectInitializationData = GetObjectInitializationData(values.Keys, runtimeBindingType.Type); + var bindingTypes = objectInitializationData.GetBindingTypes(); + + var convertedValues = values.ToDictionary( + x => x.Key, + x => stepArgumentTypeConverter.Convert(x.Value, bindingTypes[x.Key], cultureInfo)); + var constructorParameters = objectInitializationData.Constructor.GetParameters() + .Select(x => convertedValues[SanitizePropertyName(x.Name)]).ToArray(); + var result = objectInitializationData.Constructor.Invoke(constructorParameters); + + foreach (var property in objectInitializationData.PropertiesToSet) + { + var value = convertedValues[SanitizePropertyName(property.Name)]; + property.SetValue(result, value, null); + } + + return result; + } + + protected override bool CanConvert(Table table, IBindingType typeToConvertTo, CultureInfo cultureInfo) + { + var runtimeBindingType = typeToConvertTo as RuntimeBindingType; + if (runtimeBindingType == null) + return false; + + var verticalTable = GetVerticalTable(table, runtimeBindingType); + if (verticalTable == null) + return false; + + var values = GetValues(verticalTable); + var objectInitializationData = GetObjectInitializationData(values.Keys, runtimeBindingType.Type); + if (objectInitializationData == null) + return false; + + var bindingTypes = objectInitializationData.GetBindingTypes(); + return values.All(x => stepArgumentTypeConverter.CanConvert(x.Value, bindingTypes[x.Key], cultureInfo)); + } + + private Table GetVerticalTable(Table table, RuntimeBindingType runtimeBindingType) + { + if (IsVerticalTable(table, runtimeBindingType)) + return table; + + if (table.Rows.Count == 1) + { + var pivotedTable = new PivotTable(table).GetInstanceTable(0); + + if (IsVerticalTable(pivotedTable, runtimeBindingType)) + return pivotedTable; + } + + return null; + } + + private Dictionary GetValues(Table table) + { + return table.Rows.ToDictionary(x => SanitizePropertyName(x[0]), x => x[1]); + } + + private ObjectInitializationData GetObjectInitializationData(IEnumerable columns, Type type) + { + var writableProperties = GetWritableProperties(type); + var readonlyColumns = new HashSet(columns.Where(x => !writableProperties.ContainsKey(x))); + var writableColumns = new HashSet(columns.Where(writableProperties.ContainsKey)); + + var constructor = GetConstructor(type, readonlyColumns, writableColumns); + if (constructor == null) + return null; + + var constructorColumns = new HashSet(constructor.GetParameters().Select(x => SanitizePropertyName(x.Name))); + var propertiesToSet = columns.Where(x => !constructorColumns.Contains(x)).Select(x => writableProperties[x]).ToList(); + + return new ObjectInitializationData + { + Constructor = constructor, + PropertiesToSet = propertiesToSet + }; + } + + private ConstructorInfo GetConstructor(Type type, HashSet requiredParameters, HashSet optionalParameters) + { + var constructors = type.GetConstructors().OrderBy(x => x.GetParameters().Length); + foreach (var constructor in constructors) + { + var parameterNames = constructor.GetParameters().Select(x => SanitizePropertyName(x.Name)).ToList(); + var requiredCount = parameterNames.Count(requiredParameters.Contains); + if (requiredCount != requiredParameters.Count) + continue; + + var optionalCount = parameterNames.Count(optionalParameters.Contains); + if (parameterNames.Count == requiredCount + optionalCount) + return constructor; + } + + return null; + } + + private class ObjectInitializationData + { + public ConstructorInfo Constructor { get; set; } + + public List PropertiesToSet { get; set; } + + public Dictionary GetBindingTypes() + { + var bindingTypes = new Dictionary(); + foreach (var parameter in Constructor.GetParameters()) + { + bindingTypes.Add(SanitizePropertyName(parameter.Name), new RuntimeBindingType(parameter.ParameterType)); + } + foreach (var property in PropertiesToSet) + { + bindingTypes.Add(SanitizePropertyName(property.Name), new RuntimeBindingType(property.PropertyType)); + } + return bindingTypes; + } + } + } +} diff --git a/Runtime/TechTalk.SpecFlow.csproj b/Runtime/TechTalk.SpecFlow.csproj index 67c3c7ddd..a12021d8e 100644 --- a/Runtime/TechTalk.SpecFlow.csproj +++ b/Runtime/TechTalk.SpecFlow.csproj @@ -128,6 +128,7 @@ + @@ -155,6 +156,8 @@ + + diff --git a/Tests/RuntimeTests/RuntimeTests.csproj b/Tests/RuntimeTests/RuntimeTests.csproj index 772b361d0..0aba33ffc 100644 --- a/Tests/RuntimeTests/RuntimeTests.csproj +++ b/Tests/RuntimeTests/RuntimeTests.csproj @@ -167,6 +167,7 @@ + diff --git a/Tests/RuntimeTests/StepTransformationTests.cs b/Tests/RuntimeTests/StepTransformationTests.cs index ed5e575e9..5dfa18065 100644 --- a/Tests/RuntimeTests/StepTransformationTests.cs +++ b/Tests/RuntimeTests/StepTransformationTests.cs @@ -133,8 +133,8 @@ public void StepArgumentTypeConverterShouldNotUseUserConverterForNonStringConver var table = new Table("Name"); table.AddRow("xyz"); - var result = stepArgumentTypeConverter.CanConvert(table, new RuntimeBindingType(typeof(User)), new CultureInfo("en-US")); - Assert.That(result, Is.False); + var result = stepArgumentTypeConverter.Convert(table, new RuntimeBindingType(typeof(User)), new CultureInfo("en-US")); + Assert.That(result, Is.Not.EqualTo(resultUser)); } private StepArgumentTypeConverter CreateStepArgumentTypeConverter(IBindingInvoker bindingInvoker) diff --git a/Tests/RuntimeTests/TableConverterTests.cs b/Tests/RuntimeTests/TableConverterTests.cs new file mode 100644 index 000000000..5f2c5d8e6 --- /dev/null +++ b/Tests/RuntimeTests/TableConverterTests.cs @@ -0,0 +1,252 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using NUnit.Framework; +using Rhino.Mocks; +using Constraints = Rhino.Mocks.Constraints; +using Should; +using TechTalk.SpecFlow.RuntimeTests.AssistTests.ExampleEntities; +using TestStatus = TechTalk.SpecFlow.Infrastructure.TestStatus; +using Rhino.Mocks.Interfaces; + +namespace TechTalk.SpecFlow.RuntimeTests +{ + [Binding] + public class BindingsForTableConverterTests + { + [Given("sample step with Table to Person conversion")] + public virtual void PersonArg(Person param) + { + + } + + [Given("sample step with Table to Person array conversion")] + public virtual void PersonArrayArg(Person[] param) + { + + } + + [Given("sample step with Table to IEnumerable conversion")] + public virtual void UserEnumerationArg(IEnumerable param) + { + + } + + [Given("sample step with Table to UserPerson conversion")] + public virtual void UserPersonArg(UserPerson param) + { + + } + + [StepArgumentTransformation(@"user (\w+)")] + public virtual User Create(string name) + { + return new User { Name = name }; + } + + [StepArgumentTransformation(@"(\w+) (\w+)")] + public virtual Person Create(string firstName, string lastName) + { + return new Person { FirstName = firstName, LastName = lastName }; + } + } + + public class UserPerson + { + public User User { get; set; } + public Person Person { get; set; } + public Guid Guid { get; set; } + public Style Style { get; set; } + } + + [TestFixture] + public class TableConverterTests : StepExecutionTestsBase + { + private T ConversionTest( + string given, Action stepDefinition, + Table table, bool shouldSucceed) + { + T result = default(T); + BindingsForTableConverterTests bindingInstance; + TestRunner testRunner = GetTestRunnerFor(out bindingInstance); + + bindingInstance.Expect(b => b.Create(null)).IgnoreArguments().CallOriginalMethod(OriginalCallOptions.NoExpectation); + bindingInstance.Expect(b => b.Create(null, null)).IgnoreArguments().CallOriginalMethod(OriginalCallOptions.NoExpectation); + bindingInstance.Expect(b => stepDefinition(b, default(T))) + .IgnoreArguments().Repeat.Times(shouldSucceed ? 1 : 0) + .WhenCalled(mi => result = (T)mi.Arguments[0]); + MockRepository.ReplayAll(); + + testRunner.Given(given, null, table); + + Assert.AreEqual(shouldSucceed ? TestStatus.OK : TestStatus.TestError, GetLastTestStatus()); + MockRepository.VerifyAll(); + + return result; + } + + private Person PersonConversionTest(Table table, bool shouldSucceed) + { + return ConversionTest("sample step with Table to Person conversion", (b, a) => b.PersonArg(a), table, shouldSucceed); + } + + private Person[] PersonArrayConversionTest(Table table, bool shouldSucceed) + { + return ConversionTest("sample step with Table to Person array conversion", (b, a) => b.PersonArrayArg(a), table, shouldSucceed); + } + + private IEnumerable UserEnumerationConversionTest(Table table, bool shouldSucceed) + { + return ConversionTest>("sample step with Table to IEnumerable conversion", (b, a) => b.UserEnumerationArg(a), table, shouldSucceed); + } + + private UserPerson UserPersonConversionTest(Table table, bool shouldSucceed) + { + return ConversionTest("sample step with Table to UserPerson conversion", (b, a) => b.UserPersonArg(a), table, shouldSucceed); + } + + [Test] + public void Table_converters_will_return_an_instance_of_T() + { + var table = new Table("Field", "Value"); + var result = PersonConversionTest(table, true); + result.ShouldNotBeNull(); + } + + [Test] + public void Table_converters_will_set_values_with_a_vertical_table_when_there_is_one_row_and_one_column() + { + var table = new Table("FirstName"); + table.AddRow("Homer"); + + var result = PersonConversionTest(table, true); + result.ShouldNotBeNull(); + result.FirstName.ShouldEqual("Homer"); + } + + [Test] + public void When_one_row_exists_with_two_headers_and_the_first_row_value_is_not_a_property_then_treat_as_horizontal_table() + { + var table = new Table("FirstName", "LastName"); + table.AddRow("Homer", "Simpson"); + + var result = PersonConversionTest(table, true); + result.ShouldNotBeNull(); + result.FirstName.ShouldEqual("Homer"); + result.LastName.ShouldEqual("Simpson"); + } + + [Test] + public void When_one_row_exists_with_two_headers_and_the_first_row_value_is_a_property_then_treat_as_a_vertical_table() + { + var table = new Table("FirstName", "LastName"); + table.AddRow("FirstName", "Homer"); + + var result = PersonConversionTest(table, true); + result.ShouldNotBeNull(); + result.FirstName.ShouldEqual("Homer"); + result.LastName.ShouldBeNull(); + } + + [Test] + public void When_one_row_exists_with_two_headers_and_the_first_row_value_is_a_property_without_perfect_name_match_then_treat_as_a_vertical_table() + { + var table = new Table("FirstName", "LastName"); + table.AddRow("First name", "Homer"); + + var result = PersonConversionTest(table, true); + result.ShouldNotBeNull(); + result.FirstName.ShouldEqual("Homer"); + result.LastName.ShouldBeNull(); + } + + [Test] + public void When_one_row_exists_with_three_headers_then_treat_as_horizontal_table() + { + var table = new Table("FirstName", "MiddleInitial", "LastName"); + table.AddRow("Homer", "J", "Simpson"); + + var result = PersonConversionTest(table, true); + result.ShouldNotBeNull(); + result.FirstName.ShouldEqual("Homer"); + result.MiddleInitial.ShouldEqual('J'); + result.LastName.ShouldEqual("Simpson"); + } + + [Test] + public void Table_converters_will_return_an_empty_array_when_converting_an_empty_horizontal_table() + { + var table = new Table("FirstName", "MiddleInitial", "LastName"); + + var result = PersonArrayConversionTest(table, true); + result.ShouldNotBeNull(); + result.ShouldBeEmpty(); + } + + [Test] + public void Table_converters_will_return_an_array_with_one_item_for_each_row_of_an_horizontal_table() + { + var table = new Table("FirstName", "MiddleInitial", "LastName"); + table.AddRow("Homer", "J", "Simpson"); + table.AddRow("Mona", "J", "Simpson"); + + var result = PersonArrayConversionTest(table, true); + result.ShouldNotBeNull(); + result.Length.ShouldEqual(2); + result[0].ShouldNotBeNull(); + result[0].FirstName.ShouldEqual("Homer"); + result[1].ShouldNotBeNull(); + result[1].FirstName.ShouldEqual("Mona"); + } + + [Test] + public void Table_converters_will_successfully_convert_an_horizontal_table_to_an_IEnumerable() + { + var table = new Table("Name"); + table.AddRow("Homer"); + table.AddRow("Mona"); + + var result = UserEnumerationConversionTest(table, true); + result.ShouldNotBeNull(); + result.Count().ShouldEqual(2); + Assert.That(result.All(x => x != null), Is.True); + } + + [Test] + public void Table_converters_will_not_convert_a_vertical_table_to_an_array() + { + var table = new Table("Field", "Value"); + table.AddRow("FirstName", "Homer"); + table.AddRow("LastName", "Simpson"); + + PersonArrayConversionTest(table, false); + } + + [Test] + public void Table_converters_will_not_convert_an_horizontal_with_multiple_rows_table_to_a_single_object() + { + var table = new Table("FirstName", "MiddleInitial", "LastName"); + table.AddRow("Homer", "J", "Simpson"); + table.AddRow("Mona", "J", "Simpson"); + + PersonConversionTest(table, false); + } + + [Test] + public void Table_converters_will_use_other_converters_to_convert_each_value_from_the_table() + { + var table = new Table("User", "Person", "Guid", "Style"); + table.AddRow("user Admin", "Homer Simpson", "B48D8AF4-405F-4286-B83E-774EA773CFA3", "very cool"); + + var result = UserPersonConversionTest(table, true); + result.ShouldNotBeNull(); + result.User.ShouldNotBeNull(); + result.User.Name.ShouldEqual("Admin"); + result.Person.ShouldNotBeNull(); + result.Person.FirstName.ShouldEqual("Homer"); + result.Person.LastName.ShouldEqual("Simpson"); + result.Guid.ShouldEqual(new Guid("B48D8AF4-405F-4286-B83E-774EA773CFA3")); + result.Style.ShouldEqual(Style.VeryCool); + } + } +} From d9620260c79d58d22d1c95ac9105bb7fcba6d747 Mon Sep 17 00:00:00 2001 From: Yazide Boujlil Date: Wed, 12 Sep 2012 09:33:44 +0200 Subject: [PATCH 05/26] Added test for single header horizontal tables If HorizontalTableConvert can't find a conversion from a vertical table to the target item type, and the vertical table only has one property, it will try to find a converter for the string value of this property. --- Tests/RuntimeTests/TableConverterTests.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Tests/RuntimeTests/TableConverterTests.cs b/Tests/RuntimeTests/TableConverterTests.cs index 5f2c5d8e6..9149595ea 100644 --- a/Tests/RuntimeTests/TableConverterTests.cs +++ b/Tests/RuntimeTests/TableConverterTests.cs @@ -248,5 +248,23 @@ public void Table_converters_will_use_other_converters_to_convert_each_value_fro result.Guid.ShouldEqual(new Guid("B48D8AF4-405F-4286-B83E-774EA773CFA3")); result.Style.ShouldEqual(Style.VeryCool); } + + [Test] + public void Table_converters_will_try_to_convert_the_string_value_of_a_single_header_horizontal_table_if_there_is_no_conversion_available_for_the_pivoted_vertical_tables() + { + var table = new Table("Person"); + table.AddRow("Homer Simpson"); + table.AddRow("Mona Simpson"); + + var result = PersonArrayConversionTest(table, true); + result.ShouldNotBeNull(); + result.Length.ShouldEqual(2); + result[0].ShouldNotBeNull(); + result[0].FirstName.ShouldEqual("Homer"); + result[0].LastName.ShouldEqual("Simpson"); + result[1].ShouldNotBeNull(); + result[1].FirstName.ShouldEqual("Mona"); + result[1].LastName.ShouldEqual("Simpson"); + } } } From 2acba814c420a513560a8de38feefec2530c964a Mon Sep 17 00:00:00 2001 From: Yazide Boujlil Date: Wed, 12 Sep 2012 13:35:13 +0200 Subject: [PATCH 06/26] Changed constructor selection in Table conversion VerticalTableConverter will use the constructor that includes the maximum number of columns with no matching writable property. Columns with no matching constructor parameter or writer property are ignored. --- Runtime/Bindings/VerticalTableConverter.cs | 45 +++++++++--------- Tests/RuntimeTests/TableConverterTests.cs | 53 +++++++++++++++++++++- 2 files changed, 74 insertions(+), 24 deletions(-) diff --git a/Runtime/Bindings/VerticalTableConverter.cs b/Runtime/Bindings/VerticalTableConverter.cs index bd425e23f..d97bc31eb 100644 --- a/Runtime/Bindings/VerticalTableConverter.cs +++ b/Runtime/Bindings/VerticalTableConverter.cs @@ -25,9 +25,11 @@ protected override object Convert(Table table, IBindingType typeToConvertTo, Cul var objectInitializationData = GetObjectInitializationData(values.Keys, runtimeBindingType.Type); var bindingTypes = objectInitializationData.GetBindingTypes(); - var convertedValues = values.ToDictionary( - x => x.Key, - x => stepArgumentTypeConverter.Convert(x.Value, bindingTypes[x.Key], cultureInfo)); + var convertedValues = values + .Where(x => bindingTypes.ContainsKey(x.Key)) + .ToDictionary( + x => x.Key, + x => stepArgumentTypeConverter.Convert(x.Value, bindingTypes[x.Key], cultureInfo)); var constructorParameters = objectInitializationData.Constructor.GetParameters() .Select(x => convertedValues[SanitizePropertyName(x.Name)]).ToArray(); var result = objectInitializationData.Constructor.Invoke(constructorParameters); @@ -57,7 +59,9 @@ protected override bool CanConvert(Table table, IBindingType typeToConvertTo, Cu return false; var bindingTypes = objectInitializationData.GetBindingTypes(); - return values.All(x => stepArgumentTypeConverter.CanConvert(x.Value, bindingTypes[x.Key], cultureInfo)); + return values + .Where(x => bindingTypes.ContainsKey(x.Key)) + .All(x => stepArgumentTypeConverter.CanConvert(x.Value, bindingTypes[x.Key], cultureInfo)); } private Table GetVerticalTable(Table table, RuntimeBindingType runtimeBindingType) @@ -84,15 +88,17 @@ private Dictionary GetValues(Table table) private ObjectInitializationData GetObjectInitializationData(IEnumerable columns, Type type) { var writableProperties = GetWritableProperties(type); - var readonlyColumns = new HashSet(columns.Where(x => !writableProperties.ContainsKey(x))); - var writableColumns = new HashSet(columns.Where(writableProperties.ContainsKey)); + var requiredParameters = new HashSet(columns.Where(x => !writableProperties.ContainsKey(x))); + var optionalParameters = new HashSet(columns.Where(writableProperties.ContainsKey)); - var constructor = GetConstructor(type, readonlyColumns, writableColumns); + var constructor = GetBestMatchingConstructor(type, requiredParameters, optionalParameters); if (constructor == null) return null; var constructorColumns = new HashSet(constructor.GetParameters().Select(x => SanitizePropertyName(x.Name))); - var propertiesToSet = columns.Where(x => !constructorColumns.Contains(x)).Select(x => writableProperties[x]).ToList(); + var propertiesToSet = columns + .Where(x => !constructorColumns.Contains(x) && writableProperties.ContainsKey(x)) + .Select(x => writableProperties[x]).ToList(); return new ObjectInitializationData { @@ -101,22 +107,15 @@ private ObjectInitializationData GetObjectInitializationData(IEnumerable }; } - private ConstructorInfo GetConstructor(Type type, HashSet requiredParameters, HashSet optionalParameters) + private ConstructorInfo GetBestMatchingConstructor(Type type, HashSet requiredParameters, HashSet optionalParameters) { - var constructors = type.GetConstructors().OrderBy(x => x.GetParameters().Length); - foreach (var constructor in constructors) - { - var parameterNames = constructor.GetParameters().Select(x => SanitizePropertyName(x.Name)).ToList(); - var requiredCount = parameterNames.Count(requiredParameters.Contains); - if (requiredCount != requiredParameters.Count) - continue; - - var optionalCount = parameterNames.Count(optionalParameters.Contains); - if (parameterNames.Count == requiredCount + optionalCount) - return constructor; - } - - return null; + return type.GetConstructors() + .Select(c => new { Constructor = c, Parameters = c.GetParameters().Select(p => SanitizePropertyName(p.Name)).ToList() }) + .OrderByDescending(x => x.Parameters.Count(requiredParameters.Contains)) + .ThenBy(x => x.Parameters.Count) + .Where(x => x.Parameters.All(p => requiredParameters.Contains(p) || optionalParameters.Contains(p))) + .Select(x => x.Constructor) + .FirstOrDefault(); } private class ObjectInitializationData diff --git a/Tests/RuntimeTests/TableConverterTests.cs b/Tests/RuntimeTests/TableConverterTests.cs index 9149595ea..bb52548f8 100644 --- a/Tests/RuntimeTests/TableConverterTests.cs +++ b/Tests/RuntimeTests/TableConverterTests.cs @@ -38,6 +38,12 @@ public virtual void UserPersonArg(UserPerson param) } + [Given("sample step with Table to UserWithConstructorParameters conversion")] + public virtual void UserWithConstructorParametersArg(UserWithConstructorParameters param) + { + + } + [StepArgumentTransformation(@"user (\w+)")] public virtual User Create(string name) { @@ -59,6 +65,22 @@ public class UserPerson public Style Style { get; set; } } + public class UserWithConstructorParameters + { + public int Id { get; private set; } + public string Name { get; set; } + + public UserWithConstructorParameters() + { + + } + + public UserWithConstructorParameters(int id) + { + Id = id; + } + } + [TestFixture] public class TableConverterTests : StepExecutionTestsBase { @@ -105,6 +127,11 @@ private UserPerson UserPersonConversionTest(Table table, bool shouldSucceed) return ConversionTest("sample step with Table to UserPerson conversion", (b, a) => b.UserPersonArg(a), table, shouldSucceed); } + private UserWithConstructorParameters UserWithConstructorParametersConversionTest(Table table, bool shouldSucceed) + { + return ConversionTest("sample step with Table to UserWithConstructorParameters conversion", (b, a) => b.UserWithConstructorParametersArg(a), table, shouldSucceed); + } + [Test] public void Table_converters_will_return_an_instance_of_T() { @@ -255,7 +282,7 @@ public void Table_converters_will_try_to_convert_the_string_value_of_a_single_he var table = new Table("Person"); table.AddRow("Homer Simpson"); table.AddRow("Mona Simpson"); - + var result = PersonArrayConversionTest(table, true); result.ShouldNotBeNull(); result.Length.ShouldEqual(2); @@ -266,5 +293,29 @@ public void Table_converters_will_try_to_convert_the_string_value_of_a_single_he result[1].FirstName.ShouldEqual("Mona"); result[1].LastName.ShouldEqual("Simpson"); } + + [Test] + public void Table_converters_will_use_the_constructor_that_includes_the_maximum_number_of_columns_with_no_matching_writable_property() + { + var table = new Table("Id", "Name"); + table.AddRow("444", "Homer"); + + var result = UserWithConstructorParametersConversionTest(table, true); + result.ShouldNotBeNull(); + result.Id.ShouldEqual(444); + result.Name.ShouldEqual("Homer"); + } + + [Test] + public void Table_converters_will_ignore_columns_with_not_matching_writable_property_or_constructor_parameter() + { + var table = new Table("Id", "Name", "Comment"); + table.AddRow("444", "Homer", "Some comment"); + + var result = UserWithConstructorParametersConversionTest(table, true); + result.ShouldNotBeNull(); + result.Id.ShouldEqual(444); + result.Name.ShouldEqual("Homer"); + } } } From 014320d2251431bfb6849897de9e41d7c0de452a Mon Sep 17 00:00:00 2001 From: Stefan Bruck Date: Mon, 19 Nov 2012 17:04:56 +0100 Subject: [PATCH 07/26] Fixed nullable types issues Fixed issues in Invalid handling of empty strings in tables when converting to nullable enum types --- .../ValueRetrievers/EnumValueRetriever.cs | 127 +++---- .../EnumValueRetrieverTests.cs | 330 +++++++++--------- changelog.txt | 5 + 3 files changed, 243 insertions(+), 219 deletions(-) diff --git a/Runtime/Assist/ValueRetrievers/EnumValueRetriever.cs b/Runtime/Assist/ValueRetrievers/EnumValueRetriever.cs index f4e054d4d..358dce6db 100644 --- a/Runtime/Assist/ValueRetrievers/EnumValueRetriever.cs +++ b/Runtime/Assist/ValueRetrievers/EnumValueRetriever.cs @@ -1,63 +1,66 @@ -using System; - -namespace TechTalk.SpecFlow.Assist.ValueRetrievers -{ - internal class EnumValueRetriever - { - public object GetValue(string value, Type enumType) - { - CheckThatTheValueIsAnEnum(value, enumType); - - return ConvertTheStringToAnEnum(value, enumType); - } - - private object ConvertTheStringToAnEnum(string value, Type enumType) - { - return Enum.Parse(GetTheEnumType(enumType), ParseTheValue(value), true); - } - - private static Type GetTheEnumType(Type enumType) - { - return ThisIsNotANullableEnum(enumType) ? enumType : enumType.GetGenericArguments()[0]; - } - - private void CheckThatTheValueIsAnEnum(string value, Type enumType) - { - if (ThisIsNotANullableEnum(enumType)) - CheckThatThisNotAnObviouslyIncorrectNonNullableValue(value); - - try - { - ConvertTheStringToAnEnum(value, enumType); - } - catch - { - throw new InvalidOperationException(string.Format("No enum with value {0} found", value)); - } - } - - private void CheckThatThisNotAnObviouslyIncorrectNonNullableValue(string value) - { - if (value == null) - throw GetInvalidOperationException("{null}"); - if (value == string.Empty) - throw GetInvalidOperationException("{empty}"); - } - - private static bool ThisIsNotANullableEnum(Type enumType) - { - return enumType.IsGenericType == false; - } - - private string ParseTheValue(string value) - { - value = value.Replace(" ", ""); - return value; - } - - private InvalidOperationException GetInvalidOperationException(string value) - { - return new InvalidOperationException(string.Format("No enum with value {0} found", value)); - } - } +using System; + +namespace TechTalk.SpecFlow.Assist.ValueRetrievers +{ + internal class EnumValueRetriever + { + public object GetValue(string value, Type enumType) + { + CheckThatTheValueIsAnEnum(value, enumType); + + return ConvertTheStringToAnEnum(value, enumType); + } + + private object ConvertTheStringToAnEnum(string value, Type enumType) + { + if (!ThisIsNotANullableEnum(enumType) && string.IsNullOrEmpty(value)) + return null; + + return Enum.Parse(GetTheEnumType(enumType), ParseTheValue(value), true); + } + + private static Type GetTheEnumType(Type enumType) + { + return ThisIsNotANullableEnum(enumType) ? enumType : enumType.GetGenericArguments()[0]; + } + + private void CheckThatTheValueIsAnEnum(string value, Type enumType) + { + if (ThisIsNotANullableEnum(enumType)) + CheckThatThisNotAnObviouslyIncorrectNonNullableValue(value); + + try + { + ConvertTheStringToAnEnum(value, enumType); + } + catch + { + throw new InvalidOperationException(string.Format("No enum with value {0} found", value)); + } + } + + private void CheckThatThisNotAnObviouslyIncorrectNonNullableValue(string value) + { + if (value == null) + throw GetInvalidOperationException("{null}"); + if (value == string.Empty) + throw GetInvalidOperationException("{empty}"); + } + + private static bool ThisIsNotANullableEnum(Type enumType) + { + return enumType.IsGenericType == false; + } + + private string ParseTheValue(string value) + { + value = value.Replace(" ", ""); + return value; + } + + private InvalidOperationException GetInvalidOperationException(string value) + { + return new InvalidOperationException(string.Format("No enum with value {0} found", value)); + } + } } \ No newline at end of file diff --git a/Tests/RuntimeTests/AssistTests/ValueRetrieverTests/EnumValueRetrieverTests.cs b/Tests/RuntimeTests/AssistTests/ValueRetrieverTests/EnumValueRetrieverTests.cs index 31f42c48e..b946e558c 100644 --- a/Tests/RuntimeTests/AssistTests/ValueRetrieverTests/EnumValueRetrieverTests.cs +++ b/Tests/RuntimeTests/AssistTests/ValueRetrieverTests/EnumValueRetrieverTests.cs @@ -1,158 +1,174 @@ -using System; -using NUnit.Framework; -using Should; -using TechTalk.SpecFlow.Assist.ValueRetrievers; -using TechTalk.SpecFlow.RuntimeTests.AssistTests.ExampleEntities; - -namespace TechTalk.SpecFlow.RuntimeTests.AssistTests.ValueRetrieverTests -{ - [TestFixture] - public class EnumValueRetrieverTests - { - [Test] - public void Throws_an_exception_when_the_value_is_not_an_enum() - { - var retriever = new EnumValueRetriever(); - - var exceptionThrown = false; - try - { - retriever.GetValue("NotDefinied", typeof (Sex)); - } - catch (InvalidOperationException exception) - { - if (exception.Message == "No enum with value NotDefinied found") - exceptionThrown = true; - } - exceptionThrown.ShouldBeTrue(); - } - - [Test] - public void Should_return_the_value_when_it_matches_the_enum() - { - var retriever = new EnumValueRetriever(); - retriever.GetValue("Male", typeof (Sex)).ShouldEqual(Sex.Male); - } - - [Test] - public void Should_return_the_value_when_it_includes_extra_spaces() - { - var retriever = new EnumValueRetriever(); - retriever.GetValue("Unknown Sex", typeof (Sex)).ShouldEqual(Sex.UnknownSex); - } - - [Test] - public void Returns_the_value_regardless_of_proper_casing() - { - var retriever = new EnumValueRetriever(); - retriever.GetValue("feMale", typeof (Sex)).ShouldEqual(Sex.Female); - } - - [Test] - public void Returns_the_proper_value_when_spaces_and_casing_is_wrong() - { - var retriever = new EnumValueRetriever(); - retriever.GetValue("unknown sex", typeof (Sex)).ShouldEqual(Sex.UnknownSex); - } - - [Test] - public void Throws_an_exception_when_the_value_is_null() - { - var retriever = new EnumValueRetriever(); - - var exceptionThrown = false; - try - { - retriever.GetValue(null, typeof (Sex)); - } - catch (InvalidOperationException exception) - { - if (exception.Message == "No enum with value {null} found") - exceptionThrown = true; - } - exceptionThrown.ShouldBeTrue(); - } - - [Test] - public void Throws_an_exception_when_the_value_is_empty() - { - var retriever = new EnumValueRetriever(); - - var exceptionThrown = false; - try - { - retriever.GetValue(string.Empty, typeof (Sex)); - } - catch (InvalidOperationException exception) - { - if (exception.Message == "No enum with value {empty} found") - exceptionThrown = true; - } - exceptionThrown.ShouldBeTrue(); - } - - [Test] - public void Does_not_throw_an_exception_when_the_value_is_null_and_enum_type_is_not_nullable() - { - var retriever = new EnumValueRetriever(); - - var exceptionThrown = false; - try - { - retriever.GetValue(null, typeof (Sex?)); - } - catch (InvalidOperationException exception) - { - if (exception.Message == "No enum with value {null} found") - exceptionThrown = true; - } - exceptionThrown.ShouldBeFalse(); - } - - [Test] - public void Does_not_throw_an_exception_when_the_value_is_empty_and_enum_type_is_not_nullable() - { - var retriever = new EnumValueRetriever(); - - var exceptionThrown = false; - try - { - retriever.GetValue(string.Empty, typeof (Sex)); - } - catch (InvalidOperationException exception) - { - if (exception.Message == "No enum with value {empty} found") - exceptionThrown = true; - } - exceptionThrown.ShouldBeTrue(); - } - - [Test] - public void Should_return_the_value_when_it_matches_the_nullable_enum() - { - var retriever = new EnumValueRetriever(); - retriever.GetValue("Male", typeof (Sex?)).ShouldEqual(Sex.Male); - } - - [Test] - public void Should_return_the_value_when_it_includes_extra_spaces_on_the_nullable_enum() - { - var retriever = new EnumValueRetriever(); - retriever.GetValue("Unknown Sex", typeof (Sex?)).ShouldEqual(Sex.UnknownSex); - } - - [Test] - public void Returns_the_value_regardless_of_proper_casing_on_a_nullable_enum() - { - var retriever = new EnumValueRetriever(); - retriever.GetValue("feMale", typeof (Sex?)).ShouldEqual(Sex.Female); - } - - [Test] - public void Returns_the_proper_value_when_spaces_and_casing_is_wrong_on_a_nullable_enum() - { - var retriever = new EnumValueRetriever(); - retriever.GetValue("unknown sex", typeof (Sex?)).ShouldEqual(Sex.UnknownSex); - } - } +using System; +using NUnit.Framework; +using Should; +using TechTalk.SpecFlow.Assist.ValueRetrievers; +using TechTalk.SpecFlow.RuntimeTests.AssistTests.ExampleEntities; + +namespace TechTalk.SpecFlow.RuntimeTests.AssistTests.ValueRetrieverTests +{ + [TestFixture] + public class EnumValueRetrieverTests + { + [Test] + public void Throws_an_exception_when_the_value_is_not_an_enum() + { + var retriever = new EnumValueRetriever(); + + var exceptionThrown = false; + try + { + retriever.GetValue("NotDefinied", typeof (Sex)); + } + catch (InvalidOperationException exception) + { + if (exception.Message == "No enum with value NotDefinied found") + exceptionThrown = true; + } + exceptionThrown.ShouldBeTrue(); + } + + [Test] + public void Should_return_the_value_when_it_matches_the_enum() + { + var retriever = new EnumValueRetriever(); + retriever.GetValue("Male", typeof (Sex)).ShouldEqual(Sex.Male); + } + + [Test] + public void Should_return_the_value_when_it_includes_extra_spaces() + { + var retriever = new EnumValueRetriever(); + retriever.GetValue("Unknown Sex", typeof (Sex)).ShouldEqual(Sex.UnknownSex); + } + + [Test] + public void Returns_the_value_regardless_of_proper_casing() + { + var retriever = new EnumValueRetriever(); + retriever.GetValue("feMale", typeof (Sex)).ShouldEqual(Sex.Female); + } + + [Test] + public void Returns_the_proper_value_when_spaces_and_casing_is_wrong() + { + var retriever = new EnumValueRetriever(); + retriever.GetValue("unknown sex", typeof (Sex)).ShouldEqual(Sex.UnknownSex); + } + + [Test] + public void Throws_an_exception_when_the_value_is_null() + { + var retriever = new EnumValueRetriever(); + + var exceptionThrown = false; + try + { + retriever.GetValue(null, typeof (Sex)); + } + catch (InvalidOperationException exception) + { + if (exception.Message == "No enum with value {null} found") + exceptionThrown = true; + } + exceptionThrown.ShouldBeTrue(); + } + + [Test] + public void Throws_an_exception_when_the_value_is_empty() + { + var retriever = new EnumValueRetriever(); + + var exceptionThrown = false; + try + { + retriever.GetValue(string.Empty, typeof (Sex)); + } + catch (InvalidOperationException exception) + { + if (exception.Message == "No enum with value {empty} found") + exceptionThrown = true; + } + exceptionThrown.ShouldBeTrue(); + } + + [Test] + public void Does_not_throw_an_exception_when_the_value_is_null_and_enum_type_is_nullable() + { + var retriever = new EnumValueRetriever(); + + var exceptionThrown = false; + try + { + retriever.GetValue(null, typeof (Sex?)); + } + catch (InvalidOperationException exception) + { + exceptionThrown = true; + } + exceptionThrown.ShouldBeFalse(); + } + + [Test] + public void Throws_an_exception_when_the_value_is_empty_and_enum_type_is_not_nullable() + { + var retriever = new EnumValueRetriever(); + + var exceptionThrown = false; + try + { + retriever.GetValue(string.Empty, typeof (Sex)); + } + catch (InvalidOperationException exception) + { + if (exception.Message == "No enum with value {empty} found") + exceptionThrown = true; + } + exceptionThrown.ShouldBeTrue(); + } + + [Test] + public void Does_not_throw_an_exception_when_the_value_is_empty_and_enum_type_is_nullable() + { + var retriever = new EnumValueRetriever(); + + var exceptionThrown = false; + try + { + retriever.GetValue(string.Empty, typeof(Sex?)); + } + catch (InvalidOperationException exception) + { + exceptionThrown = true; + } + exceptionThrown.ShouldBeFalse(); + } + + [Test] + public void Should_return_the_value_when_it_matches_the_nullable_enum() + { + var retriever = new EnumValueRetriever(); + retriever.GetValue("Male", typeof (Sex?)).ShouldEqual(Sex.Male); + } + + [Test] + public void Should_return_the_value_when_it_includes_extra_spaces_on_the_nullable_enum() + { + var retriever = new EnumValueRetriever(); + retriever.GetValue("Unknown Sex", typeof (Sex?)).ShouldEqual(Sex.UnknownSex); + } + + [Test] + public void Returns_the_value_regardless_of_proper_casing_on_a_nullable_enum() + { + var retriever = new EnumValueRetriever(); + retriever.GetValue("feMale", typeof (Sex?)).ShouldEqual(Sex.Female); + } + + [Test] + public void Returns_the_proper_value_when_spaces_and_casing_is_wrong_on_a_nullable_enum() + { + var retriever = new EnumValueRetriever(); + retriever.GetValue("unknown sex", typeof (Sex?)).ShouldEqual(Sex.UnknownSex); + } + } } \ No newline at end of file diff --git a/changelog.txt b/changelog.txt index 3d8cf71da..5fade718b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,8 @@ +1.9.2 - 2012/11/19 + +Fixed issues: ++ Correct handling of empty strings in tables when converting to nullable enum types + 1.9.1 - 2012/10/12 (Visual Studio Integration update) Fixed issues: From b228142236e8e3f4483c7491adf602b2bc51d7ef Mon Sep 17 00:00:00 2001 From: Stefan Bruck Date: Mon, 19 Nov 2012 17:11:18 +0100 Subject: [PATCH 08/26] Revert "Fixed nullable types issues" This reverts commit 014320d2251431bfb6849897de9e41d7c0de452a. --- .../ValueRetrievers/EnumValueRetriever.cs | 127 ++++--- .../EnumValueRetrieverTests.cs | 330 +++++++++--------- changelog.txt | 5 - 3 files changed, 219 insertions(+), 243 deletions(-) diff --git a/Runtime/Assist/ValueRetrievers/EnumValueRetriever.cs b/Runtime/Assist/ValueRetrievers/EnumValueRetriever.cs index 358dce6db..f4e054d4d 100644 --- a/Runtime/Assist/ValueRetrievers/EnumValueRetriever.cs +++ b/Runtime/Assist/ValueRetrievers/EnumValueRetriever.cs @@ -1,66 +1,63 @@ -using System; - -namespace TechTalk.SpecFlow.Assist.ValueRetrievers -{ - internal class EnumValueRetriever - { - public object GetValue(string value, Type enumType) - { - CheckThatTheValueIsAnEnum(value, enumType); - - return ConvertTheStringToAnEnum(value, enumType); - } - - private object ConvertTheStringToAnEnum(string value, Type enumType) - { - if (!ThisIsNotANullableEnum(enumType) && string.IsNullOrEmpty(value)) - return null; - - return Enum.Parse(GetTheEnumType(enumType), ParseTheValue(value), true); - } - - private static Type GetTheEnumType(Type enumType) - { - return ThisIsNotANullableEnum(enumType) ? enumType : enumType.GetGenericArguments()[0]; - } - - private void CheckThatTheValueIsAnEnum(string value, Type enumType) - { - if (ThisIsNotANullableEnum(enumType)) - CheckThatThisNotAnObviouslyIncorrectNonNullableValue(value); - - try - { - ConvertTheStringToAnEnum(value, enumType); - } - catch - { - throw new InvalidOperationException(string.Format("No enum with value {0} found", value)); - } - } - - private void CheckThatThisNotAnObviouslyIncorrectNonNullableValue(string value) - { - if (value == null) - throw GetInvalidOperationException("{null}"); - if (value == string.Empty) - throw GetInvalidOperationException("{empty}"); - } - - private static bool ThisIsNotANullableEnum(Type enumType) - { - return enumType.IsGenericType == false; - } - - private string ParseTheValue(string value) - { - value = value.Replace(" ", ""); - return value; - } - - private InvalidOperationException GetInvalidOperationException(string value) - { - return new InvalidOperationException(string.Format("No enum with value {0} found", value)); - } - } +using System; + +namespace TechTalk.SpecFlow.Assist.ValueRetrievers +{ + internal class EnumValueRetriever + { + public object GetValue(string value, Type enumType) + { + CheckThatTheValueIsAnEnum(value, enumType); + + return ConvertTheStringToAnEnum(value, enumType); + } + + private object ConvertTheStringToAnEnum(string value, Type enumType) + { + return Enum.Parse(GetTheEnumType(enumType), ParseTheValue(value), true); + } + + private static Type GetTheEnumType(Type enumType) + { + return ThisIsNotANullableEnum(enumType) ? enumType : enumType.GetGenericArguments()[0]; + } + + private void CheckThatTheValueIsAnEnum(string value, Type enumType) + { + if (ThisIsNotANullableEnum(enumType)) + CheckThatThisNotAnObviouslyIncorrectNonNullableValue(value); + + try + { + ConvertTheStringToAnEnum(value, enumType); + } + catch + { + throw new InvalidOperationException(string.Format("No enum with value {0} found", value)); + } + } + + private void CheckThatThisNotAnObviouslyIncorrectNonNullableValue(string value) + { + if (value == null) + throw GetInvalidOperationException("{null}"); + if (value == string.Empty) + throw GetInvalidOperationException("{empty}"); + } + + private static bool ThisIsNotANullableEnum(Type enumType) + { + return enumType.IsGenericType == false; + } + + private string ParseTheValue(string value) + { + value = value.Replace(" ", ""); + return value; + } + + private InvalidOperationException GetInvalidOperationException(string value) + { + return new InvalidOperationException(string.Format("No enum with value {0} found", value)); + } + } } \ No newline at end of file diff --git a/Tests/RuntimeTests/AssistTests/ValueRetrieverTests/EnumValueRetrieverTests.cs b/Tests/RuntimeTests/AssistTests/ValueRetrieverTests/EnumValueRetrieverTests.cs index b946e558c..31f42c48e 100644 --- a/Tests/RuntimeTests/AssistTests/ValueRetrieverTests/EnumValueRetrieverTests.cs +++ b/Tests/RuntimeTests/AssistTests/ValueRetrieverTests/EnumValueRetrieverTests.cs @@ -1,174 +1,158 @@ -using System; -using NUnit.Framework; -using Should; -using TechTalk.SpecFlow.Assist.ValueRetrievers; -using TechTalk.SpecFlow.RuntimeTests.AssistTests.ExampleEntities; - -namespace TechTalk.SpecFlow.RuntimeTests.AssistTests.ValueRetrieverTests -{ - [TestFixture] - public class EnumValueRetrieverTests - { - [Test] - public void Throws_an_exception_when_the_value_is_not_an_enum() - { - var retriever = new EnumValueRetriever(); - - var exceptionThrown = false; - try - { - retriever.GetValue("NotDefinied", typeof (Sex)); - } - catch (InvalidOperationException exception) - { - if (exception.Message == "No enum with value NotDefinied found") - exceptionThrown = true; - } - exceptionThrown.ShouldBeTrue(); - } - - [Test] - public void Should_return_the_value_when_it_matches_the_enum() - { - var retriever = new EnumValueRetriever(); - retriever.GetValue("Male", typeof (Sex)).ShouldEqual(Sex.Male); - } - - [Test] - public void Should_return_the_value_when_it_includes_extra_spaces() - { - var retriever = new EnumValueRetriever(); - retriever.GetValue("Unknown Sex", typeof (Sex)).ShouldEqual(Sex.UnknownSex); - } - - [Test] - public void Returns_the_value_regardless_of_proper_casing() - { - var retriever = new EnumValueRetriever(); - retriever.GetValue("feMale", typeof (Sex)).ShouldEqual(Sex.Female); - } - - [Test] - public void Returns_the_proper_value_when_spaces_and_casing_is_wrong() - { - var retriever = new EnumValueRetriever(); - retriever.GetValue("unknown sex", typeof (Sex)).ShouldEqual(Sex.UnknownSex); - } - - [Test] - public void Throws_an_exception_when_the_value_is_null() - { - var retriever = new EnumValueRetriever(); - - var exceptionThrown = false; - try - { - retriever.GetValue(null, typeof (Sex)); - } - catch (InvalidOperationException exception) - { - if (exception.Message == "No enum with value {null} found") - exceptionThrown = true; - } - exceptionThrown.ShouldBeTrue(); - } - - [Test] - public void Throws_an_exception_when_the_value_is_empty() - { - var retriever = new EnumValueRetriever(); - - var exceptionThrown = false; - try - { - retriever.GetValue(string.Empty, typeof (Sex)); - } - catch (InvalidOperationException exception) - { - if (exception.Message == "No enum with value {empty} found") - exceptionThrown = true; - } - exceptionThrown.ShouldBeTrue(); - } - - [Test] - public void Does_not_throw_an_exception_when_the_value_is_null_and_enum_type_is_nullable() - { - var retriever = new EnumValueRetriever(); - - var exceptionThrown = false; - try - { - retriever.GetValue(null, typeof (Sex?)); - } - catch (InvalidOperationException exception) - { - exceptionThrown = true; - } - exceptionThrown.ShouldBeFalse(); - } - - [Test] - public void Throws_an_exception_when_the_value_is_empty_and_enum_type_is_not_nullable() - { - var retriever = new EnumValueRetriever(); - - var exceptionThrown = false; - try - { - retriever.GetValue(string.Empty, typeof (Sex)); - } - catch (InvalidOperationException exception) - { - if (exception.Message == "No enum with value {empty} found") - exceptionThrown = true; - } - exceptionThrown.ShouldBeTrue(); - } - - [Test] - public void Does_not_throw_an_exception_when_the_value_is_empty_and_enum_type_is_nullable() - { - var retriever = new EnumValueRetriever(); - - var exceptionThrown = false; - try - { - retriever.GetValue(string.Empty, typeof(Sex?)); - } - catch (InvalidOperationException exception) - { - exceptionThrown = true; - } - exceptionThrown.ShouldBeFalse(); - } - - [Test] - public void Should_return_the_value_when_it_matches_the_nullable_enum() - { - var retriever = new EnumValueRetriever(); - retriever.GetValue("Male", typeof (Sex?)).ShouldEqual(Sex.Male); - } - - [Test] - public void Should_return_the_value_when_it_includes_extra_spaces_on_the_nullable_enum() - { - var retriever = new EnumValueRetriever(); - retriever.GetValue("Unknown Sex", typeof (Sex?)).ShouldEqual(Sex.UnknownSex); - } - - [Test] - public void Returns_the_value_regardless_of_proper_casing_on_a_nullable_enum() - { - var retriever = new EnumValueRetriever(); - retriever.GetValue("feMale", typeof (Sex?)).ShouldEqual(Sex.Female); - } - - [Test] - public void Returns_the_proper_value_when_spaces_and_casing_is_wrong_on_a_nullable_enum() - { - var retriever = new EnumValueRetriever(); - retriever.GetValue("unknown sex", typeof (Sex?)).ShouldEqual(Sex.UnknownSex); - } - } +using System; +using NUnit.Framework; +using Should; +using TechTalk.SpecFlow.Assist.ValueRetrievers; +using TechTalk.SpecFlow.RuntimeTests.AssistTests.ExampleEntities; + +namespace TechTalk.SpecFlow.RuntimeTests.AssistTests.ValueRetrieverTests +{ + [TestFixture] + public class EnumValueRetrieverTests + { + [Test] + public void Throws_an_exception_when_the_value_is_not_an_enum() + { + var retriever = new EnumValueRetriever(); + + var exceptionThrown = false; + try + { + retriever.GetValue("NotDefinied", typeof (Sex)); + } + catch (InvalidOperationException exception) + { + if (exception.Message == "No enum with value NotDefinied found") + exceptionThrown = true; + } + exceptionThrown.ShouldBeTrue(); + } + + [Test] + public void Should_return_the_value_when_it_matches_the_enum() + { + var retriever = new EnumValueRetriever(); + retriever.GetValue("Male", typeof (Sex)).ShouldEqual(Sex.Male); + } + + [Test] + public void Should_return_the_value_when_it_includes_extra_spaces() + { + var retriever = new EnumValueRetriever(); + retriever.GetValue("Unknown Sex", typeof (Sex)).ShouldEqual(Sex.UnknownSex); + } + + [Test] + public void Returns_the_value_regardless_of_proper_casing() + { + var retriever = new EnumValueRetriever(); + retriever.GetValue("feMale", typeof (Sex)).ShouldEqual(Sex.Female); + } + + [Test] + public void Returns_the_proper_value_when_spaces_and_casing_is_wrong() + { + var retriever = new EnumValueRetriever(); + retriever.GetValue("unknown sex", typeof (Sex)).ShouldEqual(Sex.UnknownSex); + } + + [Test] + public void Throws_an_exception_when_the_value_is_null() + { + var retriever = new EnumValueRetriever(); + + var exceptionThrown = false; + try + { + retriever.GetValue(null, typeof (Sex)); + } + catch (InvalidOperationException exception) + { + if (exception.Message == "No enum with value {null} found") + exceptionThrown = true; + } + exceptionThrown.ShouldBeTrue(); + } + + [Test] + public void Throws_an_exception_when_the_value_is_empty() + { + var retriever = new EnumValueRetriever(); + + var exceptionThrown = false; + try + { + retriever.GetValue(string.Empty, typeof (Sex)); + } + catch (InvalidOperationException exception) + { + if (exception.Message == "No enum with value {empty} found") + exceptionThrown = true; + } + exceptionThrown.ShouldBeTrue(); + } + + [Test] + public void Does_not_throw_an_exception_when_the_value_is_null_and_enum_type_is_not_nullable() + { + var retriever = new EnumValueRetriever(); + + var exceptionThrown = false; + try + { + retriever.GetValue(null, typeof (Sex?)); + } + catch (InvalidOperationException exception) + { + if (exception.Message == "No enum with value {null} found") + exceptionThrown = true; + } + exceptionThrown.ShouldBeFalse(); + } + + [Test] + public void Does_not_throw_an_exception_when_the_value_is_empty_and_enum_type_is_not_nullable() + { + var retriever = new EnumValueRetriever(); + + var exceptionThrown = false; + try + { + retriever.GetValue(string.Empty, typeof (Sex)); + } + catch (InvalidOperationException exception) + { + if (exception.Message == "No enum with value {empty} found") + exceptionThrown = true; + } + exceptionThrown.ShouldBeTrue(); + } + + [Test] + public void Should_return_the_value_when_it_matches_the_nullable_enum() + { + var retriever = new EnumValueRetriever(); + retriever.GetValue("Male", typeof (Sex?)).ShouldEqual(Sex.Male); + } + + [Test] + public void Should_return_the_value_when_it_includes_extra_spaces_on_the_nullable_enum() + { + var retriever = new EnumValueRetriever(); + retriever.GetValue("Unknown Sex", typeof (Sex?)).ShouldEqual(Sex.UnknownSex); + } + + [Test] + public void Returns_the_value_regardless_of_proper_casing_on_a_nullable_enum() + { + var retriever = new EnumValueRetriever(); + retriever.GetValue("feMale", typeof (Sex?)).ShouldEqual(Sex.Female); + } + + [Test] + public void Returns_the_proper_value_when_spaces_and_casing_is_wrong_on_a_nullable_enum() + { + var retriever = new EnumValueRetriever(); + retriever.GetValue("unknown sex", typeof (Sex?)).ShouldEqual(Sex.UnknownSex); + } + } } \ No newline at end of file diff --git a/changelog.txt b/changelog.txt index 5fade718b..3d8cf71da 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,8 +1,3 @@ -1.9.2 - 2012/11/19 - -Fixed issues: -+ Correct handling of empty strings in tables when converting to nullable enum types - 1.9.1 - 2012/10/12 (Visual Studio Integration update) Fixed issues: From 974c5e0308f559fc52ec70840a1326b6d9314c58 Mon Sep 17 00:00:00 2001 From: Stefan Bruck Date: Tue, 20 Nov 2012 15:15:11 +0100 Subject: [PATCH 09/26] Fixed nullable types issues --- .../ValueRetrievers/EnumValueRetriever.cs | 3 +++ .../EnumValueRetrieverTests.cs | 25 +++++++++++-------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/Runtime/Assist/ValueRetrievers/EnumValueRetriever.cs b/Runtime/Assist/ValueRetrievers/EnumValueRetriever.cs index f4e054d4d..d4b7407a8 100644 --- a/Runtime/Assist/ValueRetrievers/EnumValueRetriever.cs +++ b/Runtime/Assist/ValueRetrievers/EnumValueRetriever.cs @@ -13,6 +13,9 @@ public object GetValue(string value, Type enumType) private object ConvertTheStringToAnEnum(string value, Type enumType) { + if (!ThisIsNotANullableEnum(enumType) && string.IsNullOrEmpty(value)) + return null; + return Enum.Parse(GetTheEnumType(enumType), ParseTheValue(value), true); } diff --git a/Tests/RuntimeTests/AssistTests/ValueRetrieverTests/EnumValueRetrieverTests.cs b/Tests/RuntimeTests/AssistTests/ValueRetrieverTests/EnumValueRetrieverTests.cs index 31f42c48e..6a49ad3e3 100644 --- a/Tests/RuntimeTests/AssistTests/ValueRetrieverTests/EnumValueRetrieverTests.cs +++ b/Tests/RuntimeTests/AssistTests/ValueRetrieverTests/EnumValueRetrieverTests.cs @@ -92,7 +92,7 @@ public void Throws_an_exception_when_the_value_is_empty() } [Test] - public void Does_not_throw_an_exception_when_the_value_is_null_and_enum_type_is_not_nullable() + public void Does_not_throw_an_exception_when_the_value_is_null_and_enum_type_is_nullable() { var retriever = new EnumValueRetriever(); @@ -101,30 +101,35 @@ public void Does_not_throw_an_exception_when_the_value_is_null_and_enum_type_is_ { retriever.GetValue(null, typeof (Sex?)); } - catch (InvalidOperationException exception) + catch (InvalidOperationException) { - if (exception.Message == "No enum with value {null} found") - exceptionThrown = true; + exceptionThrown = true; } exceptionThrown.ShouldBeFalse(); } [Test] - public void Does_not_throw_an_exception_when_the_value_is_empty_and_enum_type_is_not_nullable() + public void Does_not_throw_an_exception_when_the_value_is_empty_and_enum_type_is_nullable() { var retriever = new EnumValueRetriever(); var exceptionThrown = false; try { - retriever.GetValue(string.Empty, typeof (Sex)); + retriever.GetValue(string.Empty, typeof (Sex?)); } - catch (InvalidOperationException exception) + catch (InvalidOperationException) { - if (exception.Message == "No enum with value {empty} found") - exceptionThrown = true; + exceptionThrown = true; } - exceptionThrown.ShouldBeTrue(); + exceptionThrown.ShouldBeFalse(); + } + + [Test] + public void Should_return_null_when_the_value_is_empty_and_enum_type_is_nullable() + { + var retriever = new EnumValueRetriever(); + retriever.GetValue(string.Empty, typeof(Sex?)).ShouldBeNull(); } [Test] From b5e9dc994540d3ff1cde7bce0498eefd6194421c Mon Sep 17 00:00:00 2001 From: Stefan Bruck Date: Tue, 20 Nov 2012 15:23:43 +0100 Subject: [PATCH 10/26] Updated changelog to contain nullable enum fix --- changelog.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/changelog.txt b/changelog.txt index 3d8cf71da..153cec8b9 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,8 @@ +1.9.2 - 2012/11/20 + +Fixed issues: ++ Empty strings in tables get converted to null in case of nullable enum as target types when using CreateSet + 1.9.1 - 2012/10/12 (Visual Studio Integration update) Fixed issues: From 507368327341e71b2f5e2a4a1b7757e0f4fb809d Mon Sep 17 00:00:00 2001 From: Robert Fonseca-Ensor Date: Thu, 18 Apr 2013 15:59:11 +1200 Subject: [PATCH 11/26] BindingInvoker now waits for any Task that a step returns (and report exceptions properly). This required an upgrade to .NET 4 in TechTalk.SpecFlow.csproj --- Runtime/Bindings/BindingInvoker.cs | 13 ++++++ Runtime/TechTalk.SpecFlow.csproj | 3 +- Tests/RuntimeTests/StepExecutionTests.cs | 57 ++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/Runtime/Bindings/BindingInvoker.cs b/Runtime/Bindings/BindingInvoker.cs index ff93dabce..2f7113520 100644 --- a/Runtime/Bindings/BindingInvoker.cs +++ b/Runtime/Bindings/BindingInvoker.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; +using System.Threading.Tasks; using TechTalk.SpecFlow.Bindings.Reflection; using TechTalk.SpecFlow.Compatibility; using TechTalk.SpecFlow.Configuration; @@ -43,6 +44,12 @@ public object InvokeBinding(IBinding binding, IContextManager contextManager, ob Array.Copy(arguments, 0, invokeArgs, 1, arguments.Length); invokeArgs[0] = contextManager; result = bindingAction.DynamicInvoke(invokeArgs); + + if (result is Task) + { + ((Task) result).Wait(); + } + stopwatch.Stop(); } @@ -64,6 +71,12 @@ public object InvokeBinding(IBinding binding, IContextManager contextManager, ob ex = ex.PreserveStackTrace(errorProvider.GetMethodText(binding.Method)); throw ex; } + catch (AggregateException agEx) //from Task.Wait(); + { + var ex = agEx.InnerExceptions.First(); + ex = ex.PreserveStackTrace(errorProvider.GetMethodText(binding.Method)); + throw ex; + } } private CultureInfoScope CreateCultureInfoScope(IContextManager contextManager) diff --git a/Runtime/TechTalk.SpecFlow.csproj b/Runtime/TechTalk.SpecFlow.csproj index 590300157..45f6ebf7e 100644 --- a/Runtime/TechTalk.SpecFlow.csproj +++ b/Runtime/TechTalk.SpecFlow.csproj @@ -10,10 +10,11 @@ Properties TechTalk.SpecFlow TechTalk.SpecFlow - v3.5 + v4.0 512 true ..\specflow.snk + true diff --git a/Tests/RuntimeTests/StepExecutionTests.cs b/Tests/RuntimeTests/StepExecutionTests.cs index 64fd9ccaf..c0c35f826 100644 --- a/Tests/RuntimeTests/StepExecutionTests.cs +++ b/Tests/RuntimeTests/StepExecutionTests.cs @@ -1,5 +1,7 @@ using System; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using NUnit.Framework; using Rhino.Mocks; using TechTalk.SpecFlow.Infrastructure; @@ -75,6 +77,13 @@ public virtual void DistinguishByTableParam2(Table table) { } + + + [Given("Returns a Task")] + public virtual Task ReturnsATask() + { + throw new NotSupportedException("should be mocked"); + } } [Binding] @@ -275,6 +284,54 @@ public void SholdRaiseBindingErrorIfWrongParamNumber() MockRepository.VerifyAll(); } + [Test] + public void SholdCallBindingThatReturnsTask() + { + StepExecutionTestsBindings bindingInstance; + TestRunner testRunner = GetTestRunnerFor(out bindingInstance); + + bool taskFinished = false; + + bindingInstance.Expect(b => b.ReturnsATask()).Return(Task.Factory.StartNew(() => + { + Thread.Sleep(800); + taskFinished = true; + })); + + MockRepository.ReplayAll(); + + testRunner.Given("Returns a Task"); + Assert.IsTrue(taskFinished); + Assert.AreEqual(TestStatus.OK, GetLastTestStatus()); + MockRepository.VerifyAll(); + } + + [Test] + public void SholdCallBindingThatReturnsTaskAndReportError() + { + StepExecutionTestsBindings bindingInstance; + TestRunner testRunner = GetTestRunnerFor(out bindingInstance); + + bool taskFinished = false; + + bindingInstance.Expect(b => b.ReturnsATask()).Return(Task.Factory.StartNew(() => + { + Thread.Sleep(800); + taskFinished = true; + throw new Exception("catch meee"); + })); + + MockRepository.ReplayAll(); + + testRunner.Given("Returns a Task"); + Assert.IsTrue(taskFinished); + Assert.AreEqual(TestStatus.TestError, GetLastTestStatus()); + Assert.AreEqual("catch meee", ContextManagerStub.ScenarioContext.TestError.Message); + + MockRepository.VerifyAll(); + } + + } } From 66a072df127c4ac3c8c157f897ddb223fc530574 Mon Sep 17 00:00:00 2001 From: object Date: Fri, 3 May 2013 16:07:37 +0200 Subject: [PATCH 12/26] Implemented Table extension methods CompareToProjectionOfSet and CompareToProjectionOfInstance. --- Runtime/Assist/EnumerableProjection.cs | 14 +- Runtime/Assist/ProjectionExtensionMethods.cs | 47 ++- .../AssistTests/ProjectionTests.cs | 375 ++++++++++-------- 3 files changed, 267 insertions(+), 169 deletions(-) diff --git a/Runtime/Assist/EnumerableProjection.cs b/Runtime/Assist/EnumerableProjection.cs index bb188cac8..dcf3d15f0 100644 --- a/Runtime/Assist/EnumerableProjection.cs +++ b/Runtime/Assist/EnumerableProjection.cs @@ -98,7 +98,7 @@ public void Reset() public class Projection { private readonly T item; - private IEnumerable properties; + private readonly IEnumerable properties; public Projection(T item, IEnumerable properties) { @@ -106,6 +106,16 @@ public Projection(T item, IEnumerable properties) this.properties = properties; } + public T Value + { + get { return item; } + } + + public object this[string key] + { + get { return item.GetPropertyValue(key); } + } + public override bool Equals(object obj) { if (obj is Projection) @@ -113,7 +123,7 @@ public override bool Equals(object obj) var otherProjection = obj as Projection; if (item != null && otherProjection.item != null) { - IEnumerable properties = this.properties; + var properties = this.properties; if (otherProjection.properties != null) { if (properties == null) diff --git a/Runtime/Assist/ProjectionExtensionMethods.cs b/Runtime/Assist/ProjectionExtensionMethods.cs index f3740a3f7..7da695087 100644 --- a/Runtime/Assist/ProjectionExtensionMethods.cs +++ b/Runtime/Assist/ProjectionExtensionMethods.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; namespace TechTalk.SpecFlow.Assist { @@ -22,6 +24,45 @@ public static IEnumerable> ToProjectionOfSet(this Table table, public static IEnumerable> ToProjectionOfInstance(this Table table, T instance) { return new EnumerableProjection(table); - } + } + + public static void CompareToProjectionOfSet( + this Table table, + IEnumerable query, + Func>, IEnumerable>, bool> compare) + { + var tableProjection = table.ToProjectionOfSet(query); + var queryProjection = query.ToProjection(); + if (!compare(tableProjection, queryProjection)) + { + throw new ComparisonException(@"Set projections do not match"); + } + } + + public static void CompareToProjectionOfSet( + this Table table, + IEnumerable query, + Func>, IEnumerable>, IEnumerable>> diff, + string identProperty) + { + var tableProjection = table.ToProjectionOfSet(query); + var queryProjection = query.ToProjection(); + var setDifference = diff(tableProjection, queryProjection); + if (setDifference.Any()) + { + var idents = string.Join(",", setDifference.Select(x => x[identProperty].ToString()).ToArray()); + throw new ComparisonException(string.Format(@"The following row projections do not match: {0}", idents)); + } + } + + public static void CompareToProjectionOfInstance(this Table table, T instance) + { + var tableProjection = table.ToProjectionOfInstance(instance); + var instanceProjection = (new List() {instance}).ToProjection(); + if (!tableProjection.Equals(instanceProjection)) + { + throw new ComparisonException(@"Instance projections do not match"); + } + } } -} \ No newline at end of file +} diff --git a/Tests/RuntimeTests/AssistTests/ProjectionTests.cs b/Tests/RuntimeTests/AssistTests/ProjectionTests.cs index 8ba3ba8aa..7a13a534d 100644 --- a/Tests/RuntimeTests/AssistTests/ProjectionTests.cs +++ b/Tests/RuntimeTests/AssistTests/ProjectionTests.cs @@ -1,208 +1,255 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Threading; -using NUnit.Framework; -using TechTalk.SpecFlow.Assist; -using TechTalk.SpecFlow.RuntimeTests.AssistTests.TestInfrastructure; - -namespace TechTalk.SpecFlow.RuntimeTests.AssistTests -{ - [TestFixture] - public class ProjectionTests - { - private SetComparisonTestObject testInstance; - private IEnumerable testCollection; - private Guid testGuid1 = Guid.NewGuid(); - private Guid testGuid2 = Guid.NewGuid(); - - [SetUp] - public void SetUp() - { - Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); - - testInstance = new SetComparisonTestObject - { - DateTimeProperty = DateTime.Today, - GuidProperty = testGuid1, - IntProperty = 1, - StringProperty = "a" - }; - testCollection = new[] - { - testInstance, - new SetComparisonTestObject - { - DateTimeProperty = DateTime.Today, - GuidProperty = testGuid2, - IntProperty = 2, - StringProperty = "b" - }, - }; - } - - [Test] - public void Table_with_all_columns_same_rows_and_order_should_be_sequence_equal_to_collection() - { - var table = CreateTableWithAllColumns(); - table.AddRow(DateTime.Today.ToString(), testGuid1.ToString(), 1.ToString(), "a"); +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading; +using NUnit.Framework; +using TechTalk.SpecFlow.Assist; +using TechTalk.SpecFlow.RuntimeTests.AssistTests.TestInfrastructure; + +namespace TechTalk.SpecFlow.RuntimeTests.AssistTests +{ + [TestFixture] + public class ProjectionTests + { + private SetComparisonTestObject testInstance; + private IEnumerable testCollection; + private Guid testGuid1 = Guid.NewGuid(); + private Guid testGuid2 = Guid.NewGuid(); + + [SetUp] + public void SetUp() + { + Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); + + testInstance = new SetComparisonTestObject + { + DateTimeProperty = DateTime.Today, + GuidProperty = testGuid1, + IntProperty = 1, + StringProperty = "a" + }; + testCollection = new[] + { + testInstance, + new SetComparisonTestObject + { + DateTimeProperty = DateTime.Today, + GuidProperty = testGuid2, + IntProperty = 2, + StringProperty = "b" + }, + }; + } + + [Test] + public void Table_with_all_columns_same_rows_and_order_should_be_sequence_equal_to_collection() + { + var table = CreateTableWithAllColumns(); + table.AddRow(DateTime.Today.ToString(), testGuid1.ToString(), 1.ToString(), "a"); table.AddRow(DateTime.Today.ToString(), testGuid2.ToString(), 2.ToString(), "b"); var setProjection = testCollection.ToProjection(); var tableProjection = table.ToProjection(); - Assert.IsTrue(tableProjection.SequenceEqual(setProjection)); - } - - [Test] - public void Table_with_all_columns_same_rows_but_different_order_should_not_be_sequence_equal_to_collection() - { - var table = CreateTableWithAllColumns(); - table.AddRow(DateTime.Today.ToString(), testGuid2.ToString(), 2.ToString(), "b"); + Assert.IsTrue(tableProjection.SequenceEqual(setProjection)); + } + + [Test] + public void Table_with_all_columns_same_rows_but_different_order_should_not_be_sequence_equal_to_collection() + { + var table = CreateTableWithAllColumns(); + table.AddRow(DateTime.Today.ToString(), testGuid2.ToString(), 2.ToString(), "b"); table.AddRow(DateTime.Today.ToString(), testGuid1.ToString(), 1.ToString(), "a"); var tableProjection = table.ToProjection(); var setProjection = testCollection.ToProjection(); - Assert.IsFalse(tableProjection.SequenceEqual(setProjection)); - } - - [Test] - public void Intersection_of_matching_table_and_collection_should_have_the_size_of_the_table() - { - var table = CreateTableWithAllColumns(); - table.AddRow(DateTime.Today.ToString(), testGuid1.ToString(), 1.ToString(), "a"); + Assert.IsFalse(tableProjection.SequenceEqual(setProjection)); + } + + [Test] + public void Table_with_all_columns_same_rows_but_different_order_should_throw_comparison_exception_for_sequence_equal() + { + var table = CreateTableWithAllColumns(); + table.AddRow(DateTime.Today.ToString(), testGuid2.ToString(), 2.ToString(), "b"); + table.AddRow(DateTime.Today.ToString(), testGuid1.ToString(), 1.ToString(), "a"); + + Assert.Throws(() => + table.CompareToProjectionOfSet(testCollection, + (x, y) => x.SequenceEqual(y))); + } + + [Test] + public void Intersection_of_matching_table_and_collection_should_have_the_size_of_the_table() + { + var table = CreateTableWithAllColumns(); + table.AddRow(DateTime.Today.ToString(), testGuid1.ToString(), 1.ToString(), "a"); table.AddRow(DateTime.Today.ToString(), testGuid2.ToString(), 2.ToString(), "b"); var tableProjection = table.ToProjection(); var setProjection = testCollection.ToProjection(); - Assert.AreEqual(table.RowCount, tableProjection.Intersect(setProjection).Count()); - } - - [Test] - public void Table_with_extra_columns_should_not_match_collection() - { - var table = CreateTableWithAllColumns(); - table.AddRow(DateTime.Today.AddDays(100).ToString(), Guid.NewGuid().ToString(), 1.ToString(), "a"); - table.AddRow(DateTime.Today.AddDays(200).ToString(), Guid.NewGuid().ToString(), 2.ToString(), "b"); - - var query = from x in testCollection + Assert.AreEqual(table.RowCount, tableProjection.Intersect(setProjection).Count()); + } + + [Test] + public void Table_with_extra_columns_should_not_match_collection() + { + var table = CreateTableWithAllColumns(); + table.AddRow(DateTime.Today.AddDays(100).ToString(), Guid.NewGuid().ToString(), 1.ToString(), "a"); + table.AddRow(DateTime.Today.AddDays(200).ToString(), Guid.NewGuid().ToString(), 2.ToString(), "b"); + + var query = from x in testCollection select new { x.IntProperty, x.StringProperty }; var tableProjection = table.ToProjectionOfSet(query); var queryProjection = query.ToProjection(); - Assert.AreEqual(table.RowCount, tableProjection.Except(queryProjection).Count()); - } - - [Test] - public void Table_with_subset_of_columns_with_matching_values_should_match_collection() - { - var table = CreateTableWithSubsetOfColumns(); - table.AddRow(1.ToString(), "a"); - table.AddRow(2.ToString(), "b"); - - var query = from x in testCollection + Assert.AreEqual(table.RowCount, tableProjection.Except(queryProjection).Count()); + } + + [Test] + public void Table_with_extra_columns_should_throw_comparison_exception_on_except() + { + var table = CreateTableWithAllColumns(); + table.AddRow(DateTime.Today.AddDays(100).ToString(), Guid.NewGuid().ToString(), 1.ToString(), "a"); + table.AddRow(DateTime.Today.AddDays(200).ToString(), Guid.NewGuid().ToString(), 2.ToString(), "b"); + + var query = from x in testCollection + select new { x.IntProperty, x.StringProperty }; + + Assert.Throws(() => + table.CompareToProjectionOfSet(query, + (x, y) => x.Except(y), "StringProperty")); + } + + [Test] + public void Table_with_subset_of_columns_with_matching_values_should_match_collection() + { + var table = CreateTableWithSubsetOfColumns(); + table.AddRow(1.ToString(), "a"); + table.AddRow(2.ToString(), "b"); + + var query = from x in testCollection select new { x.GuidProperty, x.IntProperty, x.StringProperty }; var tableProjection = table.ToProjectionOfSet(query); var queryProjection = query.ToProjection(); - Assert.AreEqual(0, tableProjection.Except(queryProjection).Count()); - } - - [Test] - public void Table_with_subset_of_columns_with_matching_values_and_order_should_be_sequence_equal_to_collection() - { - var table = CreateTableWithSubsetOfColumns(); - table.AddRow(1.ToString(), "a"); - table.AddRow(2.ToString(), "b"); - - var query = from x in testCollection + Assert.AreEqual(0, tableProjection.Except(queryProjection).Count()); + } + + [Test] + public void Table_with_subset_of_columns_with_matching_values_and_order_should_be_sequence_equal_to_collection() + { + var table = CreateTableWithSubsetOfColumns(); + table.AddRow(1.ToString(), "a"); + table.AddRow(2.ToString(), "b"); + + var query = from x in testCollection select new { x.GuidProperty, x.IntProperty, x.StringProperty }; var tableProjection = table.ToProjectionOfSet(query); var queryProjection = query.ToProjection(); - Assert.IsTrue(tableProjection.SequenceEqual(queryProjection)); - } - - [Test] - public void Table_with_all_columns_same_rows_and_order_should_be_sequence_equal_to_queryable_collection() - { - var table = CreateTableWithAllColumns(); - table.AddRow(DateTime.Today.ToString(), testGuid1.ToString(), 1.ToString(), "a"); - table.AddRow(DateTime.Today.ToString(), testGuid2.ToString(), 2.ToString(), "b"); - + Assert.IsTrue(tableProjection.SequenceEqual(queryProjection)); + } + + [Test] + public void Table_with_all_columns_same_rows_and_order_should_be_sequence_equal_to_queryable_collection() + { + var table = CreateTableWithAllColumns(); + table.AddRow(DateTime.Today.ToString(), testGuid1.ToString(), 1.ToString(), "a"); + table.AddRow(DateTime.Today.ToString(), testGuid2.ToString(), 2.ToString(), "b"); + var query = testCollection.AsQueryable(); var tableProjection = table.ToProjectionOfSet(query); var queryProjection = query.ToProjection(); - Assert.IsTrue(tableProjection.SequenceEqual(queryProjection)); - } - - [Test] - public void Table_with_all_columns_same_rows_and_order_should_be_sequence_equal_to_list() - { - var table = CreateTableWithAllColumns(); - table.AddRow(DateTime.Today.ToString(), testGuid1.ToString(), 1.ToString(), "a"); - table.AddRow(DateTime.Today.ToString(), testGuid2.ToString(), 2.ToString(), "b"); - + Assert.IsTrue(tableProjection.SequenceEqual(queryProjection)); + } + + [Test] + public void Table_with_all_columns_same_rows_and_order_should_be_sequence_equal_to_list() + { + var table = CreateTableWithAllColumns(); + table.AddRow(DateTime.Today.ToString(), testGuid1.ToString(), 1.ToString(), "a"); + table.AddRow(DateTime.Today.ToString(), testGuid2.ToString(), 2.ToString(), "b"); + var query = testCollection.ToList(); var tableProjection = table.ToProjectionOfSet(query); var queryProjection = query.ToProjection(); - Assert.IsTrue(tableProjection.SequenceEqual(queryProjection)); - } - - [Test] - public void Table_with_single_element_and_all_columns_should_be_equal_to_matching_instance() - { - var table = CreateTableWithAllColumns(); + Assert.IsTrue(tableProjection.SequenceEqual(queryProjection)); + } + + [Test] + public void Table_with_single_element_and_all_columns_should_be_equal_to_matching_instance() + { + var table = CreateTableWithAllColumns(); table.AddRow(DateTime.Today.ToString(), testGuid1.ToString(), 1.ToString(), "a"); var tableProjection = table.ToProjection(); - Assert.AreEqual(tableProjection, testInstance); - } - - [Test] - public void Table_with_single_element_and_all_columns_should_not_be_equal_to_unmatching_instance() - { - var table = CreateTableWithAllColumns(); + Assert.AreEqual(tableProjection, testInstance); + } + + [Test] + public void Table_with_single_element_and_all_columns_should_not_be_equal_to_unmatching_instance() + { + var table = CreateTableWithAllColumns(); table.AddRow(DateTime.Today.ToString(), Guid.NewGuid().ToString(), 1.ToString(), "b"); var tableProjection = table.ToProjection(); - Assert.AreNotEqual(tableProjection, testInstance); - } - - [Test] - public void Table_with_subset_of_columns_should_be_equal_to_matching_instance() - { - var table = CreateTableWithSubsetOfColumns(); - table.AddRow(1.ToString(), "a"); - + Assert.AreNotEqual(tableProjection, testInstance); + } + + [Test] + public void Table_with_single_element_and_all_columns_should_throw_comparison_exception_for_unmatching_instance() + { + var table = CreateTableWithAllColumns(); + table.AddRow(DateTime.Today.ToString(), Guid.NewGuid().ToString(), 1.ToString(), "b"); + + Assert.Throws(() => table.CompareToProjectionOfInstance(testInstance)); + } + + [Test] + public void Table_with_subset_of_columns_should_be_equal_to_matching_instance() + { + var table = CreateTableWithSubsetOfColumns(); + table.AddRow(1.ToString(), "a"); + var instance = new { IntProperty = testInstance.IntProperty, StringProperty = testInstance.StringProperty }; var tableProjection = table.ToProjectionOfInstance(instance); - Assert.AreEqual(tableProjection, instance); - } - - [Test] - public void Table_with_subset_of_columns_should_not_be_equal_to_unmatching_instance() - { - var table = CreateTableWithSubsetOfColumns(); - table.AddRow(1.ToString(), "b"); - + Assert.AreEqual(tableProjection, instance); + } + + [Test] + public void Table_with_subset_of_columns_should_not_be_equal_to_unmatching_instance() + { + var table = CreateTableWithSubsetOfColumns(); + table.AddRow(1.ToString(), "b"); + var instance = new { IntProperty = testInstance.IntProperty, StringProperty = testInstance.StringProperty }; var tableProjection = table.ToProjectionOfInstance(instance); - Assert.AreNotEqual(tableProjection, instance); - } - - private Table CreateTableWithAllColumns() - { - return new Table("DateTimeProperty", "GuidProperty", "IntProperty", "StringProperty"); - } - - private Table CreateTableWithSubsetOfColumns() - { - return new Table("IntProperty", "StringProperty"); - } - } -} + Assert.AreNotEqual(tableProjection, instance); + } + + [Test] + public void Table_with_subset_of_columns_should_throw_comparison_exception_for_unmatching_instance() + { + var table = CreateTableWithSubsetOfColumns(); + table.AddRow(1.ToString(), "b"); + + var instance = new { IntProperty = testInstance.IntProperty, StringProperty = testInstance.StringProperty }; + + Assert.Throws(() => table.CompareToProjectionOfInstance(instance)); + } + + private Table CreateTableWithAllColumns() + { + return new Table("DateTimeProperty", "GuidProperty", "IntProperty", "StringProperty"); + } + + private Table CreateTableWithSubsetOfColumns() + { + return new Table("IntProperty", "StringProperty"); + } + } +} From 2061c742fc46dbea3bcdfeaa9b631ccf57921143 Mon Sep 17 00:00:00 2001 From: stefc Date: Tue, 23 Jul 2013 15:49:43 +0200 Subject: [PATCH 13/26] Update Languages.xml Add German "Gegeben seien" ;) --- Languages.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Languages.xml b/Languages.xml index 0a464a277..428cbd5dd 100644 --- a/Languages.xml +++ b/Languages.xml @@ -91,6 +91,7 @@ Beispiele Angenommen Gegeben sei + Gegeben seien Wenn Dann Und @@ -662,4 +663,4 @@ 並且 但是 - \ No newline at end of file + From d1569311cfcff0b49f1ffd7be2ea9dcab26a6caa Mon Sep 17 00:00:00 2001 From: Daniel Lo Nigro Date: Wed, 28 Aug 2013 10:49:14 +1000 Subject: [PATCH 14/26] Updated Australian English Updated Australian English as per https://github.com/cucumber/gherkin/pull/196 --- Languages.xml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Languages.xml b/Languages.xml index 0a464a277..f69823fb7 100644 --- a/Languages.xml +++ b/Languages.xml @@ -111,16 +111,16 @@ But - Crikey - Background - Mate - Blokes - Cobber - Ya know how - When - Ya gotta - N - Cept + Pretty much + First off + Awww, look mate + Reckon it's like + You'll wanna + Y'know + It's just unbelievable + But at the end of the day I reckon + Too right + Yeah nah OH HAI @@ -662,4 +662,4 @@ 並且 但是 - \ No newline at end of file + From fca81afcfb3e39010707baff82f9cfbc962fff22 Mon Sep 17 00:00:00 2001 From: valipour Date: Sat, 9 Nov 2013 23:07:40 +0000 Subject: [PATCH 15/26] Add description to testOutput parameter of nunitexecutionreport --- Tools/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/Program.cs b/Tools/Program.cs index 8025264eb..66e159a6f 100644 --- a/Tools/Program.cs +++ b/Tools/Program.cs @@ -52,7 +52,7 @@ public static void StepDefinitionReport( public static void NUnitExecutionReport([Required(Description = "Visual Studio Project File containing specs")] string projectFile, [Optional("TestResult.xml", Description = "Xml Test Result file generated by NUnit. Defaults to TestResult.xml")] string xmlTestResult, [Optional("", Description = "Xslt file to use, defaults to built-in stylesheet if not provided")] string xsltFile, - [Optional("TestResult.txt", "testOutput")] string labeledTestOutput, + [Optional("TestResult.txt", "testOutput", Description = "The labeled test output file generated by nunit-console. Defaults to TestResult.txt")] string labeledTestOutput, [Optional("TestResult.html", "out", Description = "Generated Output File. Defaults to TestResult.html")] string outputFile) { var reportParameters = From 494789114bd3934b71bcfd1c8639a2ee02378fbc Mon Sep 17 00:00:00 2001 From: Brian Bolte Date: Tue, 8 Apr 2014 15:15:13 -0400 Subject: [PATCH 16/26] Add overload to Assist's CreateSet accepting a Func to create each instance. --- Runtime/Assist/TableExtensionMethods.cs | 15 +++++++++++++++ .../CreateSetHelperMethodTests_WithFunc.cs | 18 ++++++++++++++++++ changelog.txt | 1 + 3 files changed, 34 insertions(+) diff --git a/Runtime/Assist/TableExtensionMethods.cs b/Runtime/Assist/TableExtensionMethods.cs index d74e5214b..c581ae141 100644 --- a/Runtime/Assist/TableExtensionMethods.cs +++ b/Runtime/Assist/TableExtensionMethods.cs @@ -55,5 +55,20 @@ public static IEnumerable CreateSet(this Table table, Func methodToCrea return list; } + + public static IEnumerable CreateSet(this Table table, Func methodToCreateEachInstance) + { + var list = new List(); + + var pivotTable = new PivotTable(table); + for (var index = 0; index < table.Rows.Count(); index++) + { + var instance = methodToCreateEachInstance(table.Rows[index]); + pivotTable.GetInstanceTable(index).FillInstance(instance); + list.Add(instance); + } + + return list; + } } } \ No newline at end of file diff --git a/Tests/RuntimeTests/AssistTests/TableHelperExtensionMethods/CreateSetHelperMethodTests_WithFunc.cs b/Tests/RuntimeTests/AssistTests/TableHelperExtensionMethods/CreateSetHelperMethodTests_WithFunc.cs index 907487d91..17bef9c7e 100644 --- a/Tests/RuntimeTests/AssistTests/TableHelperExtensionMethods/CreateSetHelperMethodTests_WithFunc.cs +++ b/Tests/RuntimeTests/AssistTests/TableHelperExtensionMethods/CreateSetHelperMethodTests_WithFunc.cs @@ -71,5 +71,23 @@ public void Still_loads_the_instance_with_the_values_from_the_table() ObjectAssertExtensions.ShouldEqual(people.First().FirstName, "John"); ObjectAssertExtensions.ShouldEqual(people.Last().FirstName, "Howard"); } + + [Test] + public void Calls_instance_creation_method_using_row_as_parameter() + { + var table = new Table("FullName", "Sex", "IsRational"); + table.AddRow("John Smith", "Male", "False"); + table.AddRow("Howard Jones", "Male", "True"); + + var people = table.CreateSet(row => new Person { FirstName = row.GetString("FullName").Split(' ').First() }); + + var john = people.First(); + var howard = people.Last(); + + ObjectAssertExtensions.ShouldEqual(john.FirstName, "John"); + ObjectAssertExtensions.ShouldEqual(john.IsRational, false); + ObjectAssertExtensions.ShouldEqual(howard.FirstName, "Howard"); + ObjectAssertExtensions.ShouldEqual(howard.IsRational, true); + } } } \ No newline at end of file diff --git a/changelog.txt b/changelog.txt index 7b98fd23b..2adaf88be 100644 --- a/changelog.txt +++ b/changelog.txt @@ -13,6 +13,7 @@ Fixed issues: New Features: + VS2010/VS2012: Table outlining support in the editor (Issue 244, by RaringCoder) ++ Assist: Added overload to CreateSet accepting a Func to create each instance 1.9.1 - 2012/10/12 (Visual Studio Integration update) From f60e5d442a4e8232d8ff2e4a18b88b5aa567deaa Mon Sep 17 00:00:00 2001 From: kondvilkarrakesh Date: Sun, 22 Jun 2014 14:34:40 +0800 Subject: [PATCH 17/26] Specflow Test execution report with tags New Features: + Specflow Test execution report derived through MSTest or NUnit displays the tags attached with each scenario or feature in a seperate column viz. "Category" + Minor fix for a toggle functionality in the XSLT file to make it work consistently with IE and Chrome. New Features: + Specflow Test execution report derived through MSTest or NUnit displays the tags attached with each scenario or feature in a seperate column viz. "Category" + Minor fix for a toggle functionality in the XSLT file to make it work consistently with IE and Chrome. --- .../MsTestExecutionReport/MsTestToNUnit.xslt | 18 ++++++++- .../NUnitExecutionReport.xslt | 40 ++++++++++++++++--- changelog.txt | 5 +++ 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/Reporting/MsTestExecutionReport/MsTestToNUnit.xslt b/Reporting/MsTestExecutionReport/MsTestToNUnit.xslt index 87c931941..6151b63d3 100644 --- a/Reporting/MsTestExecutionReport/MsTestToNUnit.xslt +++ b/Reporting/MsTestExecutionReport/MsTestToNUnit.xslt @@ -10,6 +10,7 @@ + @@ -190,6 +191,7 @@ + . @@ -252,7 +254,21 @@ - + + + + + + + + + + + + + + + diff --git a/Reporting/NUnitExecutionReport/NUnitExecutionReport.xslt b/Reporting/NUnitExecutionReport/NUnitExecutionReport.xslt index 02e08f30e..ab442eadb 100644 --- a/Reporting/NUnitExecutionReport/NUnitExecutionReport.xslt +++ b/Reporting/NUnitExecutionReport/NUnitExecutionReport.xslt @@ -63,7 +63,7 @@ font-style:italic; margin-left: 2em; color: #888888; - } +reportTable } ]]> @@ -178,6 +178,7 @@ + Category Status Time(s) @@ -204,7 +205,10 @@ ] - + + + + @@ -223,8 +227,8 @@ - - + +
               
@@ -236,8 +240,8 @@
       
     
     
-      
-        
+      
+        
           
         
       
@@ -425,4 +429,28 @@
       
     
   
+  
+  
+    
+      
+        
+          
+            
+            
+              
+                

+              
+              
+                , 
+              
+            
+          
+        
+      
+      
+         
+      
+    
+  
+
 
diff --git a/changelog.txt b/changelog.txt
index 7b98fd23b..cd8075b60 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,3 +1,8 @@
+1.9.3 - 2013/06/22 (Specflow Test Execution Report with Tags)
+New Features:
++ Specflow Test execution report derived through MSTest or NUnit displays the tags attached with each scenario or feature in a seperate column viz. "Category"
++ Minor fix for a toggle functionality in the XSLT file to make it work consistently with IE and Chrome.
+
 1.9.2 - 2013/03/25 (Visual Studio Integration update)
 
 Fixed issues:

From a069f5778bd392c88063c392aef0ef3b0fa569d2 Mon Sep 17 00:00:00 2001
From: kondvilkarrakesh 
Date: Sun, 22 Jun 2014 16:20:36 +0800
Subject: [PATCH 18/26] Revert "Specflow Test execution report with tags"

This reverts commit f60e5d442a4e8232d8ff2e4a18b88b5aa567deaa.
---
 .../MsTestExecutionReport/MsTestToNUnit.xslt  | 18 +--------
 .../NUnitExecutionReport.xslt                 | 40 +++----------------
 changelog.txt                                 |  5 ---
 3 files changed, 7 insertions(+), 56 deletions(-)

diff --git a/Reporting/MsTestExecutionReport/MsTestToNUnit.xslt b/Reporting/MsTestExecutionReport/MsTestToNUnit.xslt
index 6151b63d3..87c931941 100644
--- a/Reporting/MsTestExecutionReport/MsTestToNUnit.xslt
+++ b/Reporting/MsTestExecutionReport/MsTestToNUnit.xslt
@@ -10,7 +10,6 @@
   
 
   
-  
 
   
     
@@ -191,7 +190,6 @@
       
       
       
-      
       
       
         .
@@ -254,21 +252,7 @@
           
         
       
-
-        
-          
-          
-            
-            
-              
-             
-             
-            
-          
-          
-            
-        
-
+      
       
         
           
diff --git a/Reporting/NUnitExecutionReport/NUnitExecutionReport.xslt b/Reporting/NUnitExecutionReport/NUnitExecutionReport.xslt
index ab442eadb..02e08f30e 100644
--- a/Reporting/NUnitExecutionReport/NUnitExecutionReport.xslt
+++ b/Reporting/NUnitExecutionReport/NUnitExecutionReport.xslt
@@ -63,7 +63,7 @@
             font-style:italic;
             margin-left: 2em;
             color: #888888;
-reportTable          }
+          }
           ]]>
         
       
@@ -178,7 +178,6 @@ reportTable          }
             
           
         
-        Category
         Status
         Time(s)
       
@@ -205,10 +204,7 @@ reportTable          }
             ]
         
       
-      
-        
-      
-        
+      
         
         
            
@@ -227,8 +223,8 @@ reportTable          }
       
     
     
-      
-        
+      
+        
           
               
@@ -240,8 +236,8 @@ reportTable          }
       
     
     
-      
-        
+      
+        
           
         
       
@@ -429,28 +425,4 @@ reportTable          }
       
     
   
-  
-  
-    
-      
-        
-          
-            
-            
-              
-                

-              
-              
-                , 
-              
-            
-          
-        
-      
-      
-         
-      
-    
-  
-
 
diff --git a/changelog.txt b/changelog.txt
index cd8075b60..7b98fd23b 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,8 +1,3 @@
-1.9.3 - 2013/06/22 (Specflow Test Execution Report with Tags)
-New Features:
-+ Specflow Test execution report derived through MSTest or NUnit displays the tags attached with each scenario or feature in a seperate column viz. "Category"
-+ Minor fix for a toggle functionality in the XSLT file to make it work consistently with IE and Chrome.
-
 1.9.2 - 2013/03/25 (Visual Studio Integration update)
 
 Fixed issues:

From ddb20d5af2dcf0c466b56a322e7cf7edc5864bab Mon Sep 17 00:00:00 2001
From: kondvilkarrakesh 
Date: Sun, 22 Jun 2014 16:37:20 +0800
Subject: [PATCH 19/26] Add "category" nodes for tags through MSTest Report

Added "nunit:category" node for representing tags when MSTest
Report(trx) is used for generating the Specflow report.
---
 .../MsTestExecutionReport/MsTestToNUnit.xslt  | 37 ++++++++++++++-----
 changelog.txt                                 |  5 +++
 2 files changed, 32 insertions(+), 10 deletions(-)

diff --git a/Reporting/MsTestExecutionReport/MsTestToNUnit.xslt b/Reporting/MsTestExecutionReport/MsTestToNUnit.xslt
index 87c931941..a7e088db8 100644
--- a/Reporting/MsTestExecutionReport/MsTestToNUnit.xslt
+++ b/Reporting/MsTestExecutionReport/MsTestToNUnit.xslt
@@ -1,8 +1,8 @@
 
-
 
   
+  
 
   
     
@@ -33,7 +34,8 @@
       
         
       
-       
+      
+      
       
         
       
@@ -46,7 +48,7 @@
         
         
           
-          
+
           
             
               
@@ -89,12 +91,12 @@
 
             0.000
             0
-            
+
             
               
             
           
-       
+        
       
     
   
@@ -190,7 +192,8 @@
       
       
       
-      
+      
+
       
         .
       
@@ -252,7 +255,21 @@
           
         
       
-      
+
+      
+        
+          
+            
+              
+                
+              
+            
+
+          
+
+        
+      
+
       
         
           
@@ -263,8 +280,8 @@
           
         
       
-      
+
     
   
-  
+
 
diff --git a/changelog.txt b/changelog.txt
index 7b98fd23b..152904308 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,3 +1,8 @@
+1.9.3 - 2014/06/22 (Specflow Ms Test Report)
+New Features:
++ Specflow Test Execution report now includes the "tags" in the Nunit Report which can be displayed through a custom XSLT by the users.
+
+
 1.9.2 - 2013/03/25 (Visual Studio Integration update)
 
 Fixed issues:

From bc81bfaf1790a12a7c1c7ce28b74a38ed73571ba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Altamir=20J=C3=BAnior=20Dias?=
 
Date: Tue, 24 Jun 2014 11:24:00 -0300
Subject: [PATCH 20/26] Update TEHelpers.cs

Inclusion of two new replacements in field mapping for hyphen and question mark
---
 Runtime/Assist/TEHelpers.cs | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/Runtime/Assist/TEHelpers.cs b/Runtime/Assist/TEHelpers.cs
index 95c31a257..4ad793587 100644
--- a/Runtime/Assist/TEHelpers.cs
+++ b/Runtime/Assist/TEHelpers.cs
@@ -65,8 +65,11 @@ internal static bool IsMemberMatchingToColumnName(MemberInfo member, string colu
 
         internal static bool MatchesThisColumnName(this string propertyName, string columnName)
         {
-            return propertyName.Equals(columnName.Replace(" ", string.Empty), StringComparison.OrdinalIgnoreCase);
-
+            return propertyName.Equals(
+                columnName
+                .Replace(" ", string.Empty)
+                .Replace("-", string.Empty)
+                .Replace("?", string.Empty), StringComparison.OrdinalIgnoreCase);
         }
 
         internal static void LoadInstanceWithKeyValuePairs(Table table, object instance)
@@ -190,4 +193,4 @@ private static bool TheFirstRowValueIsTheNameOfAProperty(Table table, Type type)
                 .Any(property => IsMemberMatchingToColumnName(property, firstRowValue));
         }
     }
-}
\ No newline at end of file
+}

From e77b4d463121551c0ac7ee697da029221a308748 Mon Sep 17 00:00:00 2001
From: "@altamir-junior-dias" 
Date: Tue, 24 Jun 2014 15:10:08 -0300
Subject: [PATCH 21/26] New tests

Create new tests to properties with hyphen and question mark
---
 .../PropertyExtensionMethodsTests.cs          | 22 ++++++++++++++++++-
 1 file changed, 21 insertions(+), 1 deletion(-)

diff --git a/Tests/RuntimeTests/AssistTests/PropertyExtensionMethodsTests.cs b/Tests/RuntimeTests/AssistTests/PropertyExtensionMethodsTests.cs
index 87976115f..89b92a62d 100644
--- a/Tests/RuntimeTests/AssistTests/PropertyExtensionMethodsTests.cs
+++ b/Tests/RuntimeTests/AssistTests/PropertyExtensionMethodsTests.cs
@@ -66,9 +66,29 @@ public void Can_set_the_value_on_the_property_regardless_of_casing()
             person.FullName.ShouldEqual("John Galt");
         }
 
+        [Test]
+        public void Can_set_the_value_on_the_property_regardless_of_hyphen()
+        {
+            var person = new Person { NoBreakSupplier = "Howard Roark" };
+            person.SetPropertyValue("No-Break Supplier", "John Galt");
+
+            person.NoBreakSupplier.ShouldEqual("John Galt");
+        }
+
+        [Test]
+        public void Can_set_the_value_on_the_property_regardless_of_question_mark()
+        {
+            var person = new Person { ClientProfile = true };
+            person.SetPropertyValue("Client Profile?", true);
+
+            person.ClientProfile.ShouldEqual(true);
+        }
+
         public class Person
         {
-            public string FullName { get; set; }
+            public string FullName { get; set; }
+            public string NoBreakSupplier { get; set; }
+            public bool ClientProfile { get; set; }
         }
     }
 }

From cd6bc3b8771763fc4b8c9617d1839d86f86a8543 Mon Sep 17 00:00:00 2001
From: "@altamir-junior-dias" 
Date: Wed, 25 Jun 2014 13:50:21 -0300
Subject: [PATCH 22/26] Adjusts for style points

---
 Runtime/Assist/TEHelpers.cs | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/Runtime/Assist/TEHelpers.cs b/Runtime/Assist/TEHelpers.cs
index 4ad793587..34b913eb8 100644
--- a/Runtime/Assist/TEHelpers.cs
+++ b/Runtime/Assist/TEHelpers.cs
@@ -65,11 +65,12 @@ internal static bool IsMemberMatchingToColumnName(MemberInfo member, string colu
 
         internal static bool MatchesThisColumnName(this string propertyName, string columnName)
         {
-            return propertyName.Equals(
-                columnName
+            var cleanedColumnName = columnName
                 .Replace(" ", string.Empty)
                 .Replace("-", string.Empty)
-                .Replace("?", string.Empty), StringComparison.OrdinalIgnoreCase);
+                .Replace("?", string.Empty);
+
+            return propertyName.Equals(cleanedColumnName, StringComparison.OrdinalIgnoreCase);
         }
 
         internal static void LoadInstanceWithKeyValuePairs(Table table, object instance)

From add7e36d9ab6a9177d575869c537c46149b9522d Mon Sep 17 00:00:00 2001
From: RaringCoder 
Date: Tue, 23 Sep 2014 09:36:13 +0100
Subject: [PATCH 23/26] #328 Anchored the link label correctly

Made the link label anchor bottom | right to follow the rest of the
controls.
---
 .../UI/GenerateStepDefinitionSkeletonForm.Designer.cs            | 1 +
 1 file changed, 1 insertion(+)

diff --git a/IdeIntegration/Vs2010Integration/UI/GenerateStepDefinitionSkeletonForm.Designer.cs b/IdeIntegration/Vs2010Integration/UI/GenerateStepDefinitionSkeletonForm.Designer.cs
index 70dc7a5e5..7c20cfa7e 100644
--- a/IdeIntegration/Vs2010Integration/UI/GenerateStepDefinitionSkeletonForm.Designer.cs
+++ b/IdeIntegration/Vs2010Integration/UI/GenerateStepDefinitionSkeletonForm.Designer.cs
@@ -197,6 +197,7 @@ private void InitializeComponent()
             // 
             // helpLinkLabel
             // 
+            this.helpLinkLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
             this.helpLinkLabel.AutoSize = true;
             this.helpLinkLabel.Location = new System.Drawing.Point(286, 312);
             this.helpLinkLabel.Name = "helpLinkLabel";

From 6a85639175d1f5eab655d57124dd0b88cdd93c17 Mon Sep 17 00:00:00 2001
From: Uma Singareddy 
Date: Tue, 7 Oct 2014 12:50:04 -0500
Subject: [PATCH 24/26] Fixing CompareToInstance and CompareToSet methods to
 account for fields as well

---
 Runtime/Assist/EnumerableProjection.cs        |   6 +-
 .../InstanceComparisonExtensionMethods.cs     |  41 ++++--
 Runtime/Assist/PropertyExtensionMethods.cs    |  27 ----
 Runtime/Assist/SetComparer.cs                 |  17 ++-
 Runtime/Assist/TableDiffExceptionBuilder.cs   |   2 +-
 Runtime/TechTalk.SpecFlow.csproj              |   2 +-
 ...InstanceComparisonExtensionMethodsTests.cs | 123 ++++++++++--------
 .../PropertyExtensionMethodsTests.cs          |  16 +--
 ...ComparisonExtensionMethods_MessageTests.cs |  46 +++++++
 .../SetComparisonTestObject.cs                |   7 +
 Tests/RuntimeTests/RuntimeTests.csproj        |  13 +-
 Tests/RuntimeTests/StepExecutionTestsBase.cs  |   1 -
 12 files changed, 173 insertions(+), 128 deletions(-)
 delete mode 100644 Runtime/Assist/PropertyExtensionMethods.cs

diff --git a/Runtime/Assist/EnumerableProjection.cs b/Runtime/Assist/EnumerableProjection.cs
index dcf3d15f0..bbf17f290 100644
--- a/Runtime/Assist/EnumerableProjection.cs
+++ b/Runtime/Assist/EnumerableProjection.cs
@@ -113,7 +113,7 @@ public T Value
 
         public object this[string key]
         {
-            get { return item.GetPropertyValue(key); }
+            get { return item.GetMemberValue(key); }
         }
 
         public override bool Equals(object obj)
@@ -156,8 +156,8 @@ private static bool Compare(T t1, T t2, IEnumerable properties)
                 if (t1.GetType().GetProperty(property) == null || t2.GetType().GetProperty(property) == null)
                     return false;
 
-                var thisValue = t1.GetPropertyValue(property);
-                var otherValue = t2.GetPropertyValue(property);
+                var thisValue = t1.GetMemberValue(property);
+                var otherValue = t2.GetMemberValue(property);
                 if (thisValue != null)
                 {
                     if (otherValue == null)
diff --git a/Runtime/Assist/InstanceComparisonExtensionMethods.cs b/Runtime/Assist/InstanceComparisonExtensionMethods.cs
index 57f36bb20..aa7769d4b 100644
--- a/Runtime/Assist/InstanceComparisonExtensionMethods.cs
+++ b/Runtime/Assist/InstanceComparisonExtensionMethods.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using TechTalk.SpecFlow.Assist.ValueComparers;
@@ -50,10 +50,22 @@ private static string DescribeTheErrorForThisDifference(Difference difference)
         private static IEnumerable FindAnyDifferences(Table table, T instance)
         {
             return from row in table.Rows
-                   where ThePropertyDoesNotExist(instance, row) || TheValuesDoNotMatch(instance, row)
+                   where TheMemberDoesNotExist(instance, row) || TheValuesDoNotMatch(instance, row)
                    select CreateDifferenceForThisRow(instance, row);
         }
 
+        private static bool TheMemberDoesNotExist(T instance, TableRow row)
+        {
+            return ThePropertyDoesNotExist(instance, row) && TheFieldDoesNotExist(instance, row);
+        }
+
+        private static bool TheFieldDoesNotExist(T instance, TableRow row)
+        {
+            return instance.GetType().GetFields()
+                .Any(property => TEHelpers.IsMemberMatchingToColumnName(property, row.Id())) == false;
+        }
+
+
         private static bool ThereAreAnyDifferences(IEnumerable differences)
         {
             return differences.Count() > 0;
@@ -68,7 +80,7 @@ private static bool ThePropertyDoesNotExist(T instance, TableRow row)
         private static bool TheValuesDoNotMatch(T instance, TableRow row)
         {
             var expected = GetTheExpectedValue(row);
-            var propertyValue = instance.GetPropertyValue(row.Id());
+            var propertyValue = instance.GetMemberValue(row.Id());
 
             var valueComparers = new IValueComparer[]
                                      {
@@ -93,19 +105,19 @@ private static string GetTheExpectedValue(TableRow row)
 
         private static Difference CreateDifferenceForThisRow(T instance, TableRow row)
         {
-            if (ThePropertyDoesNotExist(instance, row))
+            if (TheMemberDoesNotExist(instance, row))
                 return new Difference
-                           {
-                               Property = row.Id(),
-                               DoesNotExist = true
-                           };
+                {
+                    Property = row.Id(),
+                    DoesNotExist = true
+                };
 
             return new Difference
-                       {
-                           Property = row.Id(),
-                           Expected = row.Value(),
-                           Actual = instance.GetPropertyValue(row.Id())
-                       };
+            {
+                Property = row.Id(),
+                Expected = row.Value(),
+                Actual = instance.GetMemberValue(row.Id())
+            };
         }
 
         private class Difference
@@ -137,4 +149,5 @@ public ComparisonException(string message)
         {
         }
     }
-}
\ No newline at end of file
+}
+
diff --git a/Runtime/Assist/PropertyExtensionMethods.cs b/Runtime/Assist/PropertyExtensionMethods.cs
deleted file mode 100644
index 966b7a602..000000000
--- a/Runtime/Assist/PropertyExtensionMethods.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using System.Linq;
-using System.Reflection;
-
-namespace TechTalk.SpecFlow.Assist
-{
-    internal static class PropertyExtensionMethods
-    {
-        public static object GetPropertyValue(this object @object, string propertyName)
-        {
-            var property = GetThePropertyOnThisObject(@object, propertyName);
-            return property.GetValue(@object, null);
-        }
-
-        public static void SetPropertyValue(this object @object, string propertyName, object value)
-        {
-            var property = GetThePropertyOnThisObject(@object, propertyName);
-            property.SetValue(@object, value, null);
-        }
-
-        private static PropertyInfo GetThePropertyOnThisObject(object @object, string propertyName)
-        {
-            var type = @object.GetType();
-            return type.GetProperties()
-                .FirstOrDefault(x => TEHelpers.IsMemberMatchingToColumnName(x, propertyName));
-        }
-    }
-}
diff --git a/Runtime/Assist/SetComparer.cs b/Runtime/Assist/SetComparer.cs
index 8e2dc9e70..2a9f7753f 100644
--- a/Runtime/Assist/SetComparer.cs
+++ b/Runtime/Assist/SetComparer.cs
@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Reflection;
 
 namespace TechTalk.SpecFlow.Assist
 {
@@ -141,14 +142,20 @@ private static List GetTheActualItems(IEnumerable set)
 
         private void AssertThatAllColumnsInTheTableMatchToPropertiesOnTheType()
         {
-            var propertiesThatDoNotExist = from columnHeader in table.Header
-                                           where (typeof (T).GetProperties().Any(property => TEHelpers.IsMemberMatchingToColumnName(property, columnHeader)) == false)
-                                           select columnHeader;
+            var type = typeof(T);
+            var memberList = new List();
+            memberList.AddRange(type.GetProperties());
+            memberList.AddRange(type.GetFields());
 
-            if (propertiesThatDoNotExist.Any())
+            var membersThatDontExist = from columnHeader in table.Header
+                                       where (memberList.Any(member => TEHelpers.IsMemberMatchingToColumnName(member, columnHeader)) == false)
+                                       select columnHeader;
+
+            if (membersThatDontExist.Any())
                 throw new ComparisonException(
-                    propertiesThatDoNotExist.Aggregate(@"The following fields do not exist:",
+                    membersThatDontExist.Aggregate(@"The following fields do not exist:",
                                                        (running, next) => running + string.Format("{0}{1}", Environment.NewLine, next)));
         }
+
     }
 }
\ No newline at end of file
diff --git a/Runtime/Assist/TableDiffExceptionBuilder.cs b/Runtime/Assist/TableDiffExceptionBuilder.cs
index f0f64e68a..14e13466b 100644
--- a/Runtime/Assist/TableDiffExceptionBuilder.cs
+++ b/Runtime/Assist/TableDiffExceptionBuilder.cs
@@ -29,7 +29,7 @@ public string GetTheTableDiffExceptionMessage(TableDifferenceResults tableDif
             {
                 var line = "+ |";
                 foreach (var header in tableDifferenceResults.Table.Header)
-                    line += string.Format(" {0} |", item.GetPropertyValue(header));
+                    line += string.Format(" {0} |", item.GetMemberValue(header));
                 realData.AppendLine(line);
             }
 
diff --git a/Runtime/TechTalk.SpecFlow.csproj b/Runtime/TechTalk.SpecFlow.csproj
index bab4a4d5e..3f5066a78 100644
--- a/Runtime/TechTalk.SpecFlow.csproj
+++ b/Runtime/TechTalk.SpecFlow.csproj
@@ -66,7 +66,7 @@
     
     
     
-    
+    
     
     
     
diff --git a/Tests/RuntimeTests/AssistTests/InstanceComparisonExtensionMethodsTests.cs b/Tests/RuntimeTests/AssistTests/InstanceComparisonExtensionMethodsTests.cs
index e458c4d16..f8d4090ea 100644
--- a/Tests/RuntimeTests/AssistTests/InstanceComparisonExtensionMethodsTests.cs
+++ b/Tests/RuntimeTests/AssistTests/InstanceComparisonExtensionMethodsTests.cs
@@ -17,6 +17,45 @@ public void SetUp()
             Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
         }
 
+        [Test]
+        public void Does_not_throw_exception_when_value_of_matching_string_field_matches()
+        {
+            var table = new Table("Field", "Value");
+            table.AddRow("StringField", "Howard Roark");
+
+            var test = new InstanceComparisonTestObjectWithFields { StringField = "Howard Roark" };
+
+            ComparisonTestResult comparisonResult = ExceptionWasThrownByThisComparison(table, test);
+
+            comparisonResult.ExceptionWasThrown.ShouldBeFalse(comparisonResult.ExceptionMessage);
+        }
+
+        [Test]
+        public void Throws_exception_when_field_matching_name_does_not_exist()
+        {
+            var table = new Table("Field", "Value");
+            table.AddRow("DecimalField", "3.5");
+
+            var test = new InstanceComparisonTestObjectWithFields();
+
+            ComparisonTestResult comparisonResult = ExceptionWasThrownByThisComparison(table, test);
+
+            comparisonResult.ExceptionWasThrown.ShouldBeTrue();
+        }
+
+        [Test]
+        public void Throws_exception_when_field_matches_on_name_but_not_on_value()
+        {
+            var table = new Table("Field", "Value");
+            table.AddRow("IntField", "3");
+
+            var test = new InstanceComparisonTestObjectWithFields { IntField = 5 };
+
+            ComparisonTestResult comparisonResult = ExceptionWasThrownByThisComparison(table, test);
+
+            comparisonResult.ExceptionWasThrown.ShouldBeTrue();
+        }
+
         [Test]
         public void Throws_exception_when_value_of_matching_string_property_does_not_match()
         {
@@ -51,10 +90,10 @@ public void Throws_exception_when_first_row_matches_but_second_does_not()
             table.AddRow("IntProperty", "20");
 
             var test = new InstanceComparisonTestObject
-                           {
-                               StringProperty = "Howard Roark",
-                               IntProperty = 10
-                           };
+            {
+                StringProperty = "Howard Roark",
+                IntProperty = 10
+            };
 
             ComparisonTestResult comparisonResult = ExceptionWasThrownByThisComparison(table, test);
 
@@ -136,10 +175,10 @@ public void Exception_returns_an_exception_for_two_errors_when_there_are_two_dif
             table.AddRow("IntProperty", "1");
 
             var test = new InstanceComparisonTestObject
-                           {
-                               StringProperty = "Peter Keating",
-                               IntProperty = 2
-                           };
+            {
+                StringProperty = "Peter Keating",
+                IntProperty = 2
+            };
 
             var exception = GetExceptionThrownByThisComparison(table, test);
 
@@ -170,16 +209,16 @@ public void Will_property_handle_true_boolean_matches()
             table.AddRow("BoolProperty", "true");
 
             ComparisonTestResult comparisonResult = ExceptionWasThrownByThisComparison(table, new InstanceComparisonTestObject
-                                                                                                        {
-                                                                                                            BoolProperty = true
-                                                                                                        });
+            {
+                BoolProperty = true
+            });
 
             comparisonResult.ExceptionWasThrown.ShouldBeFalse(comparisonResult.ExceptionMessage);
 
             comparisonResult = ExceptionWasThrownByThisComparison(table, new InstanceComparisonTestObject
-                                                                                                    {
-                                                                                                        BoolProperty = false
-                                                                                                    });
+            {
+                BoolProperty = false
+            });
 
             comparisonResult.ExceptionWasThrown.ShouldBeTrue(comparisonResult.ExceptionMessage);
         }
@@ -191,9 +230,9 @@ public void Will_match_guids_without_case_insensitivity()
             table.AddRow("GuidProperty", "DFFC3F4E-670A-400A-8212-C6841E2EA055");
 
             ComparisonTestResult comparisonResult = ExceptionWasThrownByThisComparison(table, new InstanceComparisonTestObject
-                                                                                                        {
-                                                                                                            GuidProperty = new Guid("DFFC3F4E-670A-400A-8212-C6841E2EA055")
-                                                                                                        });
+            {
+                GuidProperty = new Guid("DFFC3F4E-670A-400A-8212-C6841E2EA055")
+            });
 
             comparisonResult.ExceptionWasThrown.ShouldBeFalse(comparisonResult.ExceptionMessage);
         }
@@ -204,9 +243,9 @@ public void Will_match_decimals_regardless_of_trailing_zeroes()
             var table = new Table("Field", "Value");
             table.AddRow("DecimalProperty", "4.23");
             var comparisonResult = ExceptionWasThrownByThisComparison(table, new InstanceComparisonTestObject
-                                                                                 {
-                                                                                     DecimalProperty = 4.23000000M
-                                                                                 });
+            {
+                DecimalProperty = 4.23000000M
+            });
 
             comparisonResult.ExceptionWasThrown.ShouldBeFalse();
         }
@@ -264,12 +303,12 @@ public void Can_compare_a_horizontal_table()
             table.AddRow("Test", "42", "23.01", "11.56");
 
             var test = new InstanceComparisonTestObject
-                           {
-                               StringProperty = "Test",
-                               IntProperty = 42,
-                               DecimalProperty = 23.01M,
-                               FloatProperty = 11.56F
-                           };
+            {
+                StringProperty = "Test",
+                IntProperty = 42,
+                DecimalProperty = 23.01M,
+                FloatProperty = 11.56F
+            };
 
             var comparisonResult = ExceptionWasThrownByThisComparison(table, test);
             comparisonResult.ExceptionWasThrown.ShouldBeFalse(comparisonResult.ExceptionMessage);
@@ -356,35 +395,7 @@ public void Supports_all_standard_types()
             comparisonResult.ExceptionWasThrown.ShouldBeFalse(comparisonResult.ExceptionMessage);
         }
 
-        private static ComparisonException GetExceptionThrownByThisComparison(Table table, InstanceComparisonTestObject test)
-        {
-            try
-            {
-                table.CompareToInstance(test);
-            }
-            catch (ComparisonException ex)
-            {
-                return ex;
-            }
-            return null;
-        }
-
-        private static ComparisonTestResult ExceptionWasThrownByThisComparison(Table table, InstanceComparisonTestObject test)
-        {
-            var result = new ComparisonTestResult { ExceptionWasThrown = false };
-            try
-            {
-                table.CompareToInstance(test);
-            }
-            catch (ComparisonException ex)
-            {
-                result.ExceptionWasThrown = true;
-                result.ExceptionMessage = ex.Message;
-            }
-            return result;
-        }
-
-        private static ComparisonException GetExceptionThrownByThisComparison(Table table, StandardTypesComparisonTestObject test)
+        private static ComparisonException GetExceptionThrownByThisComparison(Table table, object test)
         {
             try
             {
@@ -397,7 +408,7 @@ private static ComparisonException GetExceptionThrownByThisComparison(Table tabl
             return null;
         }
 
-        private static ComparisonTestResult ExceptionWasThrownByThisComparison(Table table, StandardTypesComparisonTestObject test)
+        private static ComparisonTestResult ExceptionWasThrownByThisComparison(Table table, object test)
         {
             var result = new ComparisonTestResult { ExceptionWasThrown = false };
             try
diff --git a/Tests/RuntimeTests/AssistTests/PropertyExtensionMethodsTests.cs b/Tests/RuntimeTests/AssistTests/PropertyExtensionMethodsTests.cs
index 89b92a62d..f81c0dfe1 100644
--- a/Tests/RuntimeTests/AssistTests/PropertyExtensionMethodsTests.cs
+++ b/Tests/RuntimeTests/AssistTests/PropertyExtensionMethodsTests.cs
@@ -14,7 +14,7 @@ public void Can_get_the_property_of_an_object_through_GetPropertyValue()
             const string expectedValue = "John Galt";
 
             var person = new Person { FullName = expectedValue };
-            var value = person.GetPropertyValue("FullName");
+            var value = person.GetMemberValue("FullName");
 
             Assert.AreEqual(expectedValue, value);
         }
@@ -24,7 +24,7 @@ public void Can_get_the_property_of_an_object_even_if_the_name_has_extra_spaces(
         {
             var person = new Person {FullName = "Howard Roark"};
 
-            person.GetPropertyValue("Full Name")
+            person.GetMemberValue("Full Name")
                 .ShouldEqual("Howard Roark");
         }
 
@@ -33,7 +33,7 @@ public void Can_get_the_property_of_an_object_even_if_the_casing_is_wrong()
         {
             var person = new Person { FullName = "Howard Roark" };
 
-            person.GetPropertyValue("fullname")
+            person.GetMemberValue("fullname")
                 .ShouldEqual("Howard Roark");
         }
 
@@ -43,7 +43,7 @@ public void Can_set_the_value_on_the_property_through_SetPropertyValue()
             const string expectedValue = "John Galt";
 
             var person = new Person { FullName = "Howard Roark" };
-            person.SetPropertyValue("FullName", expectedValue);
+            person.SetMemberValue("FullName", expectedValue);
 
             Assert.AreEqual(expectedValue, person.FullName);
         }
@@ -52,7 +52,7 @@ public void Can_set_the_value_on_the_property_through_SetPropertyValue()
         public void Can_set_the_value_on_the_property_regardless_of_spaces()
         {
             var person = new Person { FullName = "Howard Roark" };
-            person.SetPropertyValue("Full Name", "John Galt");
+            person.SetMemberValue("Full Name", "John Galt");
 
             person.FullName.ShouldEqual("John Galt");
         }
@@ -61,7 +61,7 @@ public void Can_set_the_value_on_the_property_regardless_of_spaces()
         public void Can_set_the_value_on_the_property_regardless_of_casing()
         {
             var person = new Person { FullName = "Howard Roark" };
-            person.SetPropertyValue("full name", "John Galt");
+            person.SetMemberValue("full name", "John Galt");
 
             person.FullName.ShouldEqual("John Galt");
         }
@@ -70,7 +70,7 @@ public void Can_set_the_value_on_the_property_regardless_of_casing()
         public void Can_set_the_value_on_the_property_regardless_of_hyphen()
         {
             var person = new Person { NoBreakSupplier = "Howard Roark" };
-            person.SetPropertyValue("No-Break Supplier", "John Galt");
+            person.SetMemberValue("No-Break Supplier", "John Galt");
 
             person.NoBreakSupplier.ShouldEqual("John Galt");
         }
@@ -79,7 +79,7 @@ public void Can_set_the_value_on_the_property_regardless_of_hyphen()
         public void Can_set_the_value_on_the_property_regardless_of_question_mark()
         {
             var person = new Person { ClientProfile = true };
-            person.SetPropertyValue("Client Profile?", true);
+            person.SetMemberValue("Client Profile?", true);
 
             person.ClientProfile.ShouldEqual(true);
         }
diff --git a/Tests/RuntimeTests/AssistTests/SetComparisonExtensionMethods_MessageTests.cs b/Tests/RuntimeTests/AssistTests/SetComparisonExtensionMethods_MessageTests.cs
index 4175e4b2e..37caba02e 100644
--- a/Tests/RuntimeTests/AssistTests/SetComparisonExtensionMethods_MessageTests.cs
+++ b/Tests/RuntimeTests/AssistTests/SetComparisonExtensionMethods_MessageTests.cs
@@ -10,6 +10,39 @@ public class SetComparisonExtensionMethods_MessageTests
     {
         [Test]
         public void Returns_the_names_of_any_fields_that_do_not_exist()
+        {
+            var table = new Table("StringField", "AFieldThatDoesNotExist", "AnotherFieldThatDoesNotExist");
+
+            var items = new[] { new SetComparisonTestObjectWithFields(),  };
+
+            var exception = GetTheExceptionThrowByComparingThese(table, items);
+
+            exception.Message.AgnosticLineBreak().ShouldEqual(
+               @"The following fields do not exist:
+AFieldThatDoesNotExist
+AnotherFieldThatDoesNotExist".AgnosticLineBreak());
+        }
+
+        [Test]
+        public void Returns_descriptive_message_when_two_results_exist_for_the_field_but_there_should_be_no_results()
+        {
+            var table = new Table("IntField");
+
+            var items = new[] { new SetComparisonTestObjectWithFields(), new SetComparisonTestObjectWithFields(),  };
+
+            var exception = GetTheExceptionThrowByComparingThese(table, items);
+
+            exception.Message.AgnosticLineBreak().ShouldEqual(
+                @"
+  | IntField |
++ | 0        |
++ | 0        |
+".AgnosticLineBreak());
+        }
+
+
+        [Test]
+        public void Returns_the_names_of_any_properties_that_do_not_exist()
         {
             var table = new Table("StringProperty", "AFieldThatDoesNotExist", "AnotherFieldThatDoesNotExist");
 
@@ -182,5 +215,18 @@ private static ComparisonException GetTheExceptionThrowByComparingThese(Table ta
             }
             return null;
         }
+        
+        private static ComparisonException GetTheExceptionThrowByComparingThese(Table table, SetComparisonTestObjectWithFields[] items)
+        {
+            try
+            {
+                table.CompareToSet(items);
+            }
+            catch (ComparisonException ex)
+            {
+                return ex;
+            }
+            return null;
+        }
     }
 }
\ No newline at end of file
diff --git a/Tests/RuntimeTests/AssistTests/TestInfrastructure/SetComparisonTestObject.cs b/Tests/RuntimeTests/AssistTests/TestInfrastructure/SetComparisonTestObject.cs
index 656ae9d8c..2fa528e76 100644
--- a/Tests/RuntimeTests/AssistTests/TestInfrastructure/SetComparisonTestObject.cs
+++ b/Tests/RuntimeTests/AssistTests/TestInfrastructure/SetComparisonTestObject.cs
@@ -12,4 +12,11 @@ class SetComparisonTestObject
         public Guid GuidProperty { get; set; }
 
     }
+
+    class SetComparisonTestObjectWithFields
+    {
+        public string StringField;
+        public int IntField;
+
+    }
 }
\ No newline at end of file
diff --git a/Tests/RuntimeTests/RuntimeTests.csproj b/Tests/RuntimeTests/RuntimeTests.csproj
index 2a396a1c4..7630d1ab2 100644
--- a/Tests/RuntimeTests/RuntimeTests.csproj
+++ b/Tests/RuntimeTests/RuntimeTests.csproj
@@ -80,6 +80,7 @@
     
     
     
+    
     
     
     
@@ -172,22 +173,10 @@
     
   
   
-    
-      {453D8014-B6CD-4E86-80A8-D59F59092334}
-      TechTalk.SpecFlow.Generator
-    
-    
-      {7CCEF6D6-FC17-422E-9BED-EDD752B6496F}
-      TechTalk.SpecFlow.Parser
-    
     
       {413EE28C-4F89-4C6F-BA1E-2CDEE4CD43B4}
       TechTalk.SpecFlow
     
-    
-      {C0AF4A43-0C3B-47C7-86DE-79FB632B1453}
-      TechTalk.SpecFlow.Utils
-    
   
   
     
diff --git a/Tests/RuntimeTests/StepExecutionTestsBase.cs b/Tests/RuntimeTests/StepExecutionTestsBase.cs
index 68f8dd1e6..90de3c7ec 100644
--- a/Tests/RuntimeTests/StepExecutionTestsBase.cs
+++ b/Tests/RuntimeTests/StepExecutionTestsBase.cs
@@ -10,7 +10,6 @@
 using TechTalk.SpecFlow.Bindings.Reflection;
 using TechTalk.SpecFlow.Infrastructure;
 using TechTalk.SpecFlow.Tracing;
-using TechTalk.SpecFlow.Utils;
 using MockRepository = Rhino.Mocks.MockRepository;
 using TestStatus = TechTalk.SpecFlow.Infrastructure.TestStatus;
 

From c5e3f6a8a90059f41e39ab05cb185ee6652f9c51 Mon Sep 17 00:00:00 2001
From: unknown 
Date: Sun, 12 Oct 2014 22:29:10 -0500
Subject: [PATCH 25/26] Adding back missing files

---
 Runtime/Assist/MemberExtensionMethods.cs      | 53 +++++++++++++++++++
 .../InstanceComparisonTestObjectWithFields.cs | 13 +++++
 2 files changed, 66 insertions(+)
 create mode 100644 Runtime/Assist/MemberExtensionMethods.cs
 create mode 100644 Tests/RuntimeTests/AssistTests/TestInfrastructure/InstanceComparisonTestObjectWithFields.cs

diff --git a/Runtime/Assist/MemberExtensionMethods.cs b/Runtime/Assist/MemberExtensionMethods.cs
new file mode 100644
index 000000000..d43772a50
--- /dev/null
+++ b/Runtime/Assist/MemberExtensionMethods.cs
@@ -0,0 +1,53 @@
+using System.Linq;
+using System.Reflection;
+
+namespace TechTalk.SpecFlow.Assist
+{
+    internal static class MemberExtensionMethods
+    {
+        public static object GetMemberValue(this object @object, string memberName)
+        {
+            var property = GetThePropertyOnThisObject(@object, memberName);
+
+            if (property != null)
+            {
+                return property.GetValue(@object, null);
+            }
+
+            var field = GetTheFieldOnThisObject(@object, memberName);
+            return field.GetValue(@object);
+            
+        }        
+
+        public static void SetMemberValue(this object @object, string propertyName, object value)
+        {
+            var property = GetThePropertyOnThisObject(@object, propertyName);
+
+            if (property != null)
+            {
+                property.SetValue(@object, value, null);
+                return;
+            }
+
+            var field = GetTheFieldOnThisObject(@object, propertyName);
+            field.SetValue(@object, value);
+            
+        }
+
+
+        private static FieldInfo GetTheFieldOnThisObject(object @object, string fieldName)
+        {
+            var type = @object.GetType();
+            return type.GetFields()
+               .FirstOrDefault(x => TEHelpers.IsMemberMatchingToColumnName(x, fieldName));
+
+        }
+
+        private static PropertyInfo GetThePropertyOnThisObject(object @object, string propertyName)
+        {
+            var type = @object.GetType();
+            return type.GetProperties()
+                .FirstOrDefault(x => TEHelpers.IsMemberMatchingToColumnName(x, propertyName));
+        }
+    }
+}
\ No newline at end of file
diff --git a/Tests/RuntimeTests/AssistTests/TestInfrastructure/InstanceComparisonTestObjectWithFields.cs b/Tests/RuntimeTests/AssistTests/TestInfrastructure/InstanceComparisonTestObjectWithFields.cs
new file mode 100644
index 000000000..1abf33b35
--- /dev/null
+++ b/Tests/RuntimeTests/AssistTests/TestInfrastructure/InstanceComparisonTestObjectWithFields.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace TechTalk.SpecFlow.RuntimeTests.AssistTests.TestInfrastructure
+{
+    public class InstanceComparisonTestObjectWithFields
+    {
+        public string StringField;
+        public int IntField;
+    }
+}

From c9d25c420b9ce5d0b8aa7a1494dae2b1d35ad607 Mon Sep 17 00:00:00 2001
From: unknown 
Date: Wed, 15 Oct 2014 09:19:27 -0500
Subject: [PATCH 26/26] Updating TEHelpers EnumValueRetriever to identify
 matching properties as well as fields

---
 Runtime/Assist/TEHelpers.cs | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

diff --git a/Runtime/Assist/TEHelpers.cs b/Runtime/Assist/TEHelpers.cs
index 34b913eb8..3724da4e1 100644
--- a/Runtime/Assist/TEHelpers.cs
+++ b/Runtime/Assist/TEHelpers.cs
@@ -151,10 +151,23 @@ internal static Dictionary> GetTypeHandlersForField
                            {typeof (DateTime?), (TableRow row) => new NullableDateTimeValueRetriever(v => new DateTimeValueRetriever().GetValue(v)).GetValue(row[1])},
                            {typeof (Guid), (TableRow row) => new GuidValueRetriever().GetValue(row[1])},
                            {typeof (Guid?), (TableRow row) => new NullableGuidValueRetriever(v => new GuidValueRetriever().GetValue(v)).GetValue(row[1])},
-                           {typeof (Enum), (TableRow row) => new EnumValueRetriever().GetValue(row[1], type.GetProperties().First(x => x.Name.MatchesThisColumnName(row[0])).PropertyType)},
+                           {typeof (Enum), (TableRow row) => new EnumValueRetriever().GetValue(row[1], GetMemberType(type, row))},
                        };
         }
 
+        private static Type GetMemberType(Type type, TableRow row)
+        {
+            // search for property matching on name
+            if (type.GetProperties().Any(x => x.Name.MatchesThisColumnName(row[0])))
+            {
+                return type.GetProperties().First(x => x.Name.MatchesThisColumnName(row[0])).PropertyType;
+            }
+
+            //search for matching field
+            return type.GetFields().First(x => x.Name.MatchesThisColumnName(row[0])).FieldType;
+
+        }
+
         internal class MemberHandler
         {
             public TableRow Row { get; set; }