From 851e6e0a9a21c18d934acc4e1e462021d580f8c7 Mon Sep 17 00:00:00 2001 From: Gaspar Nagy Date: Mon, 3 May 2010 16:46:04 +0200 Subject: [PATCH] Binding methods are executed using the culture of the feature file. --- Runtime/Bindings/StepArgumentTypeConverter.cs | 2 +- Runtime/Bindings/StepMethodBinding.cs | 46 ++++++++++++++ Runtime/Bindings/StepTransformationBinding.cs | 7 ++- Runtime/CultureInfoScope.cs | 31 ++++++++++ Runtime/ObjectContainer.cs | 8 +++ Runtime/TechTalk.SpecFlow.csproj | 1 + Runtime/TestRunner.cs | 33 +--------- Tests/RuntimeTests/RuntimeTests.csproj | 1 + Tests/RuntimeTests/StepExecutionTestsBase.cs | 11 +++- ...TestsWithConversionsInNonEnglishCulture.cs | 60 +++++++++++++++++++ changelog.txt | 5 +- 11 files changed, 168 insertions(+), 37 deletions(-) create mode 100644 Runtime/CultureInfoScope.cs create mode 100644 Tests/RuntimeTests/StepExecutionTestsWithConversionsInNonEnglishCulture.cs diff --git a/Runtime/Bindings/StepArgumentTypeConverter.cs b/Runtime/Bindings/StepArgumentTypeConverter.cs index e38fec47b..5c9ab8135 100644 --- a/Runtime/Bindings/StepArgumentTypeConverter.cs +++ b/Runtime/Bindings/StepArgumentTypeConverter.cs @@ -33,7 +33,7 @@ public object Convert(string value, Type typeToConvertTo, CultureInfo cultureInf var stepTransformations = StepTransformations.Where(t => t.ReturnType == typeToConvertTo && t.Regex.IsMatch(value)).ToArray(); Debug.Assert(stepTransformations.Length <= 1, "You may not call Convert if CanConvert returns false"); if (stepTransformations.Length > 0) - return stepTransformations[0].Transform(value); + return stepTransformations[0].Transform(value, testTracer); return ConvertSimple(typeToConvertTo, value, cultureInfo); } diff --git a/Runtime/Bindings/StepMethodBinding.cs b/Runtime/Bindings/StepMethodBinding.cs index b71aece67..7a6d3ce62 100644 --- a/Runtime/Bindings/StepMethodBinding.cs +++ b/Runtime/Bindings/StepMethodBinding.cs @@ -1,9 +1,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; +using TechTalk.SpecFlow.Configuration; using TechTalk.SpecFlow.ErrorHandling; +using TechTalk.SpecFlow.Tracing; namespace TechTalk.SpecFlow.Bindings { @@ -106,6 +109,49 @@ private Delegate CreateFuncDelegate(MethodInfo method) return lambda.Compile(); } + public object InvokeAction(object[] arguments, ITestTracer testTracer) + { + TimeSpan duration; + return InvokeAction(arguments, testTracer, out duration); + } + + public object InvokeAction(object[] arguments, ITestTracer testTracer, out TimeSpan duration) + { + try + { + object result; + Stopwatch stopwatch = new Stopwatch(); + using (new CultureInfoScope(FeatureContext.Current.FeatureInfo.Language)) + { + stopwatch.Start(); + result = BindingAction.DynamicInvoke(arguments); + stopwatch.Stop(); + } + + if (RuntimeConfiguration.Current.TraceTimings && stopwatch.Elapsed >= RuntimeConfiguration.Current.MinTracedDuration) + { + testTracer.TraceDuration(stopwatch.Elapsed, MethodInfo, arguments); + } + + duration = stopwatch.Elapsed; + return result; + } + catch (ArgumentException ex) + { + throw errorProvider.GetCallError(MethodInfo, ex); + } + catch (TargetInvocationException invEx) + { + var ex = invEx.InnerException; + PreserveStackTrace(ex); + throw ex; + } + } + + internal void PreserveStackTrace(Exception ex) + { + typeof(Exception).GetMethod("InternalPreserveStackTrace", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(ex, new object[0]); + } #region extended action types static readonly Type[] actionTypes = new Type[] { typeof(Action), typeof(Action<>), typeof(Action<,>), typeof(Action<,,>), typeof(Action<,,,>), diff --git a/Runtime/Bindings/StepTransformationBinding.cs b/Runtime/Bindings/StepTransformationBinding.cs index aaac57231..7ffaa88e3 100644 --- a/Runtime/Bindings/StepTransformationBinding.cs +++ b/Runtime/Bindings/StepTransformationBinding.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Reflection; using System.Text.RegularExpressions; +using TechTalk.SpecFlow.Tracing; namespace TechTalk.SpecFlow.Bindings { @@ -16,17 +17,17 @@ public StepTransformationBinding(string regexString, MethodInfo methodInfo) Regex = regex; } - public string[] GetStepTransformationArguments(string stepSnippet) + private object[] GetStepTransformationArguments(string stepSnippet) { var match = Regex.Match(stepSnippet); var argumentStrings = match.Groups.Cast().Skip(1).Select(g => g.Value).ToArray(); return argumentStrings; } - public object Transform(string value) + public object Transform(string value, ITestTracer testTracer) { var arguments = GetStepTransformationArguments(value); - return BindingAction.DynamicInvoke(arguments); + return InvokeAction(arguments, testTracer); } } } \ No newline at end of file diff --git a/Runtime/CultureInfoScope.cs b/Runtime/CultureInfoScope.cs new file mode 100644 index 000000000..bebeacb3f --- /dev/null +++ b/Runtime/CultureInfoScope.cs @@ -0,0 +1,31 @@ +using System; +using System.Globalization; +using System.Threading; +using TechTalk.SpecFlow.Tracing; + +namespace TechTalk.SpecFlow +{ + public class CultureInfoScope : IDisposable + { + private readonly CultureInfo originalCultureInfo; + + public CultureInfoScope(CultureInfo cultureInfo) + { + if (cultureInfo != null && !cultureInfo.Equals(Thread.CurrentThread.CurrentCulture)) + { + if (cultureInfo.IsNeutralCulture) + { + cultureInfo = LanguageHelper.GetSpecificCultureInfo(cultureInfo); + } + originalCultureInfo = Thread.CurrentThread.CurrentCulture; + Thread.CurrentThread.CurrentCulture = cultureInfo; + } + } + + public void Dispose() + { + if (originalCultureInfo != null) + Thread.CurrentThread.CurrentCulture = originalCultureInfo; + } + } +} \ No newline at end of file diff --git a/Runtime/ObjectContainer.cs b/Runtime/ObjectContainer.cs index 18e8d7845..35c8b243f 100644 --- a/Runtime/ObjectContainer.cs +++ b/Runtime/ObjectContainer.cs @@ -270,5 +270,13 @@ private static TInterface GetOrCreate(ref TInterface storage, Func + diff --git a/Runtime/TestRunner.cs b/Runtime/TestRunner.cs index b2e9dd1a8..88a832e3d 100644 --- a/Runtime/TestRunner.cs +++ b/Runtime/TestRunner.cs @@ -195,7 +195,7 @@ private void FireEvents(BindingEvent bindingEvent, IEnumerable tags) { if (IsTagNeeded(eventBinding.Tags, tags)) { - InvokeAction(eventBinding.BindingAction, null, eventBinding.MethodInfo); + eventBinding.InvokeAction(null, testTracer); } } } @@ -402,7 +402,8 @@ internal void PreserveStackTrace(Exception ex) private TimeSpan ExecuteStepMatch(BindingMatch match, object[] arguments) { OnStepStart(); - TimeSpan duration = InvokeAction(match.StepBinding.BindingAction, arguments, match.StepBinding.MethodInfo); + TimeSpan duration; + match.StepBinding.InvokeAction(arguments, testTracer, out duration); OnStepEnd(); return duration; @@ -422,34 +423,6 @@ private void HandleBlockSwitch(ScenarioBlock block) } } - private TimeSpan InvokeAction(Delegate action, object[] arguments, MethodInfo methodInfo) - { - try - { - Stopwatch stopwatch = new Stopwatch(); - stopwatch.Start(); - action.DynamicInvoke(arguments); - stopwatch.Stop(); - - if (RuntimeConfiguration.Current.TraceTimings && stopwatch.Elapsed >= RuntimeConfiguration.Current.MinTracedDuration) - { - testTracer.TraceDuration(stopwatch.Elapsed, methodInfo, arguments); - } - - return stopwatch.Elapsed; - } - catch (ArgumentException ex) - { - throw errorProvider.GetCallError(methodInfo, ex); - } - catch (TargetInvocationException invEx) - { - var ex = invEx.InnerException; - PreserveStackTrace(ex); - throw ex; - } - } - private object[] GetExecuteArguments(BindingMatch match) { List arguments = new List(); diff --git a/Tests/RuntimeTests/RuntimeTests.csproj b/Tests/RuntimeTests/RuntimeTests.csproj index cae081f96..5c45a0456 100644 --- a/Tests/RuntimeTests/RuntimeTests.csproj +++ b/Tests/RuntimeTests/RuntimeTests.csproj @@ -68,6 +68,7 @@ + diff --git a/Tests/RuntimeTests/StepExecutionTestsBase.cs b/Tests/RuntimeTests/StepExecutionTestsBase.cs index 0be80951b..87d92f2f9 100644 --- a/Tests/RuntimeTests/StepExecutionTestsBase.cs +++ b/Tests/RuntimeTests/StepExecutionTestsBase.cs @@ -67,13 +67,20 @@ public void TraceDuration(TimeSpan elapsed, string text) } #endregion + protected virtual CultureInfo GetLanguage() + { + return new CultureInfo("en-US"); + } + [SetUp] public void SetUp() { + ObjectContainer.Reset(); + MockRepository = new MockRepository(); // FeatureContext and ScenarioContext is needed, because the [Binding]-instances live there - Language = new CultureInfo("en-US"); + Language = GetLanguage(); ObjectContainer.FeatureContext = new FeatureContext(new FeatureInfo(Language, "test feature", null)); ObjectContainer.ScenarioContext = new ScenarioContext(new ScenarioInfo("test scenario")); @@ -82,7 +89,7 @@ public void SetUp() ObjectContainer.TestTracer = new DummyTestTracer(); } - private TestRunner GetTestRunnerFor(params Type[] bindingTypes) + protected TestRunner GetTestRunnerFor(params Type[] bindingTypes) { bindingRegistry = new BindingRegistry(); foreach (var bindingType in bindingTypes) diff --git a/Tests/RuntimeTests/StepExecutionTestsWithConversionsInNonEnglishCulture.cs b/Tests/RuntimeTests/StepExecutionTestsWithConversionsInNonEnglishCulture.cs new file mode 100644 index 000000000..36abbe2d6 --- /dev/null +++ b/Tests/RuntimeTests/StepExecutionTestsWithConversionsInNonEnglishCulture.cs @@ -0,0 +1,60 @@ +using System; +using System.Globalization; +using System.Threading; +using NUnit.Framework; +using Rhino.Mocks; + +namespace TechTalk.SpecFlow.RuntimeTests +{ + [Binding] + public class StepExecutionTestsBindingsForArgumentConvertInNonEnglishCulture + { + [Given("argument (.*) should be able to convert to (.*)")] + public virtual void InBindingConversion(string doubleParam, double expectedValue) + { + double value = Convert.ToDouble(doubleParam); + Assert.AreEqual(expectedValue, value); + + Assert.AreEqual("de-DE", Thread.CurrentThread.CurrentCulture.Name); + } + } + + + [TestFixture] + public class StepExecutionTestsWithConversionsInNonEnglishCulture : StepExecutionTestsBase + { + protected override CultureInfo GetLanguage() + { + return new CultureInfo("de-DE"); + } + + [Test] + public void SholdCallBindingWithSimpleConvertParam() + { + StepExecutionTestsBindings bindingInstance; + TestRunner testRunner = GetTestRunnerFor(out bindingInstance); + + bindingInstance.Expect(b => b.BindingWithSimpleConvertParam(1.23)); + + MockRepository.ReplayAll(); + + testRunner.Given("sample step with simple convert param: 1,23"); // German uses , as decimal separator + + Assert.AreEqual(TestStatus.OK, ObjectContainer.ScenarioContext.TestStatus); + MockRepository.VerifyAll(); + } + + [Test] + public void ShouldExecuteBindingWithTheProperCulture() + { + TestRunner testRunner = GetTestRunnerFor(typeof(StepExecutionTestsBindingsForArgumentConvertInNonEnglishCulture)); + + MockRepository.ReplayAll(); + + testRunner.Given("argument 1,23 should be able to convert to 1,23"); // German uses , as decimal separator + + Assert.AreEqual(TestStatus.OK, ObjectContainer.ScenarioContext.TestStatus); + MockRepository.VerifyAll(); + } + } +} \ No newline at end of file diff --git a/changelog.txt b/changelog.txt index 8c4bd245b..c067c600d 100644 --- a/changelog.txt +++ b/changelog.txt @@ -13,10 +13,13 @@ New features: + SpecFlow feature files can be added also to VB.NET projects + Support for xUnit + Single installer for Visual Studio 2008 and 2010 (Issue 6, 10, 11) -+ SpecFlow Reporting doesn't work with Firefox (Issue 31) + Place GeneratedCodeAttribute and 'Designer generated code' region on generated code to avoid having this code parsed by code analysis. (Issue 33) +Fixed issues: ++ SpecFlow Reporting doesn't work with Firefox (Issue 31) ++ Binding methods are executed using the culture of the feature file. + 1.2.0 - 2009/11/25 New features: