diff --git a/Plugins/SpecFlow.ExternalData/sample/ExternalDataSample/AssemblyHooks.cs b/Plugins/SpecFlow.ExternalData/sample/ExternalDataSample/AssemblyHooks.cs index 90a48fee6..77d900b72 100644 --- a/Plugins/SpecFlow.ExternalData/sample/ExternalDataSample/AssemblyHooks.cs +++ b/Plugins/SpecFlow.ExternalData/sample/ExternalDataSample/AssemblyHooks.cs @@ -1,4 +1,5 @@ -using NUnit.Framework; +using System.Threading.Tasks; +using NUnit.Framework; using TechTalk.SpecFlow; namespace Specs @@ -9,17 +10,17 @@ namespace Specs public class NUnitAssemblyHooks { [OneTimeSetUp] - public void AssemblyInitialize() + public async Task AssemblyInitialize() { var currentAssembly = typeof(NUnitAssemblyHooks).Assembly; - TestRunnerManager.OnTestRunStart(currentAssembly); + await TestRunnerManager.OnTestRunStartAsync(currentAssembly); } [OneTimeTearDown] - public void AssemblyCleanup() + public async Task AssemblyCleanup() { var currentAssembly = typeof(NUnitAssemblyHooks).Assembly; - TestRunnerManager.OnTestRunEnd(currentAssembly); + await TestRunnerManager.OnTestRunEndAsync(currentAssembly); } } } diff --git a/Plugins/TechTalk.SpecFlow.MSTest.Generator.SpecFlowPlugin/build/MSTest.AssemblyHooks.template.cs b/Plugins/TechTalk.SpecFlow.MSTest.Generator.SpecFlowPlugin/build/MSTest.AssemblyHooks.template.cs index 46f65649a..9428470ab 100644 --- a/Plugins/TechTalk.SpecFlow.MSTest.Generator.SpecFlowPlugin/build/MSTest.AssemblyHooks.template.cs +++ b/Plugins/TechTalk.SpecFlow.MSTest.Generator.SpecFlowPlugin/build/MSTest.AssemblyHooks.template.cs @@ -7,6 +7,7 @@ using global::TechTalk.SpecFlow; using global::TechTalk.SpecFlow.MSTest.SpecFlowPlugin; using global::System.Runtime.CompilerServices; +using System.Threading.Tasks; [GeneratedCode("SpecFlow", "SPECFLOW_VERSION")] [TestClass] @@ -14,21 +15,20 @@ public class PROJECT_ROOT_NAMESPACE_MSTestAssemblyHooks { [AssemblyInitialize] [MethodImpl(MethodImplOptions.NoInlining)] - public static void AssemblyInitialize(TestContext testContext) + public static async Task AssemblyInitializeAsync(TestContext testContext) { var currentAssembly = typeof(PROJECT_ROOT_NAMESPACE_MSTestAssemblyHooks).Assembly; var containerBuilder = new MsTestContainerBuilder(testContext); - TestRunnerManager.OnTestRunStart(currentAssembly, containerBuilder); + await global::TechTalk.SpecFlow.TestRunnerManager.OnTestRunStartAsync(currentAssembly, containerBuilder: containerBuilder); } [AssemblyCleanup] [MethodImpl(MethodImplOptions.NoInlining)] - public static void AssemblyCleanup() + public static async Task AssemblyCleanupAsync() { var currentAssembly = typeof(PROJECT_ROOT_NAMESPACE_MSTestAssemblyHooks).Assembly; - - TestRunnerManager.OnTestRunEnd(currentAssembly); + await global::TechTalk.SpecFlow.TestRunnerManager.OnTestRunEndAsync(currentAssembly); } } #pragma warning restore diff --git a/Plugins/TechTalk.SpecFlow.MSTest.Generator.SpecFlowPlugin/build/MSTest.AssemblyHooks.template.vb b/Plugins/TechTalk.SpecFlow.MSTest.Generator.SpecFlowPlugin/build/MSTest.AssemblyHooks.template.vb index 8cd1e3617..f13d58fd6 100644 --- a/Plugins/TechTalk.SpecFlow.MSTest.Generator.SpecFlowPlugin/build/MSTest.AssemblyHooks.template.vb +++ b/Plugins/TechTalk.SpecFlow.MSTest.Generator.SpecFlowPlugin/build/MSTest.AssemblyHooks.template.vb @@ -13,21 +13,17 @@ Imports System.Runtime.CompilerServices Public NotInheritable Class PROJECT_ROOT_NAMESPACE_MSTestAssemblyHooks - Public Shared Sub AssemblyInitialize(testContext As TestContext) - + Public Shared Async Function AssemblyInitializeAsync(testContext As TestContext) As Task Dim currentAssembly As Assembly = GetType(PROJECT_ROOT_NAMESPACE_MSTestAssemblyHooks).Assembly Dim containerBuilder As New MsTestContainerBuilder(testContext) - - TestRunnerManager.OnTestRunStart(currentAssembly, containerBuilder) - End Sub + Await Global.TechTalk.SpecFlow.TestRunnerManager.OnTestRunStartAsync(currentAssembly, Nothing, containerBuilder) + End Function - Public Shared Sub AssemblyCleanup() - + Public Shared Async Function AssemblyCleanupAsync() As Task Dim currentAssembly As Assembly = GetType(PROJECT_ROOT_NAMESPACE_MSTestAssemblyHooks).Assembly - - TestRunnerManager.OnTestRunEnd(currentAssembly) - End Sub + Await Global.TechTalk.SpecFlow.TestRunnerManager.OnTestRunEndAsync(currentAssembly) + End Function End Class diff --git a/Plugins/TechTalk.SpecFlow.NUnit.Generator.SpecFlowPlugin/build/NUnit.AssemblyHooks.template.cs b/Plugins/TechTalk.SpecFlow.NUnit.Generator.SpecFlowPlugin/build/NUnit.AssemblyHooks.template.cs index a11941818..3810187f2 100644 --- a/Plugins/TechTalk.SpecFlow.NUnit.Generator.SpecFlowPlugin/build/NUnit.AssemblyHooks.template.cs +++ b/Plugins/TechTalk.SpecFlow.NUnit.Generator.SpecFlowPlugin/build/NUnit.AssemblyHooks.template.cs @@ -3,30 +3,27 @@ using System.CodeDom.Compiler; using System.Diagnostics; -using global::NUnit.Framework; -using global::TechTalk.SpecFlow; using global::System.Runtime.CompilerServices; +using System.Threading.Tasks; [GeneratedCode("SpecFlow", "SPECFLOW_VERSION")] -[SetUpFixture] +[global::NUnit.Framework.SetUpFixture] public class PROJECT_ROOT_NAMESPACE_NUnitAssemblyHooks { - [OneTimeSetUp] + [global::NUnit.Framework.OneTimeSetUp] [MethodImpl(MethodImplOptions.NoInlining)] - public void AssemblyInitialize() + public async Task AssemblyInitializeAsync() { var currentAssembly = typeof(PROJECT_ROOT_NAMESPACE_NUnitAssemblyHooks).Assembly; - - TestRunnerManager.OnTestRunStart(currentAssembly); + await global::TechTalk.SpecFlow.TestRunnerManager.OnTestRunStartAsync(currentAssembly); } - [OneTimeTearDown] + [global::NUnit.Framework.OneTimeTearDown] [MethodImpl(MethodImplOptions.NoInlining)] - public void AssemblyCleanup() + public async ValueTask AssemblyCleanupAsync() { var currentAssembly = typeof(PROJECT_ROOT_NAMESPACE_NUnitAssemblyHooks).Assembly; - - TestRunnerManager.OnTestRunEnd(currentAssembly); + await global::TechTalk.SpecFlow.TestRunnerManager.OnTestRunEndAsync(currentAssembly); } } #pragma warning restore diff --git a/Plugins/TechTalk.SpecFlow.NUnit.Generator.SpecFlowPlugin/build/NUnit.AssemblyHooks.template.vb b/Plugins/TechTalk.SpecFlow.NUnit.Generator.SpecFlowPlugin/build/NUnit.AssemblyHooks.template.vb index c18e52ff0..add03c66b 100644 --- a/Plugins/TechTalk.SpecFlow.NUnit.Generator.SpecFlowPlugin/build/NUnit.AssemblyHooks.template.vb +++ b/Plugins/TechTalk.SpecFlow.NUnit.Generator.SpecFlowPlugin/build/NUnit.AssemblyHooks.template.vb @@ -12,18 +12,16 @@ Imports System.Runtime.CompilerServices Public NotInheritable Class PROJECT_ROOT_NAMESPACE_NUnitAssemblyHooks - Public Shared Sub AssemblyInitialize() + Public Async Function AssemblyInitializeAsync() As Task Dim currentAssembly As Assembly = GetType(PROJECT_ROOT_NAMESPACE_NUnitAssemblyHooks).Assembly - - TestRunnerManager.OnTestRunStart(currentAssembly) - End Sub + Await Global.TechTalk.SpecFlow.TestRunnerManager.OnTestRunStartAsync(currentAssembly) + End Function - Public Shared Sub AssemblyCleanup() + Public Async Function AssemblyCleanupAsync() As Task Dim currentAssembly As Assembly = GetType(PROJECT_ROOT_NAMESPACE_NUnitAssemblyHooks).Assembly - - TestRunnerManager.OnTestRunEnd(currentAssembly) - End Sub + Await Global.TechTalk.SpecFlow.TestRunnerManager.OnTestRunEndAsync(currentAssembly) + End Function End Class diff --git a/Plugins/TechTalk.SpecFlow.xUnit.Generator.SpecFlowPlugin/SpecFlow.xUnit.nuspec b/Plugins/TechTalk.SpecFlow.xUnit.Generator.SpecFlowPlugin/SpecFlow.xUnit.nuspec index 13a12f907..942e8fab5 100644 --- a/Plugins/TechTalk.SpecFlow.xUnit.Generator.SpecFlowPlugin/SpecFlow.xUnit.nuspec +++ b/Plugins/TechTalk.SpecFlow.xUnit.Generator.SpecFlowPlugin/SpecFlow.xUnit.nuspec @@ -21,6 +21,7 @@ + diff --git a/Plugins/TechTalk.SpecFlow.xUnit.Generator.SpecFlowPlugin/build/xUnit.AssemblyHooks.template.cs b/Plugins/TechTalk.SpecFlow.xUnit.Generator.SpecFlowPlugin/build/xUnit.AssemblyHooks.template.cs index 2936f8cdb..f611cc958 100644 --- a/Plugins/TechTalk.SpecFlow.xUnit.Generator.SpecFlowPlugin/build/xUnit.AssemblyHooks.template.cs +++ b/Plugins/TechTalk.SpecFlow.xUnit.Generator.SpecFlowPlugin/build/xUnit.AssemblyHooks.template.cs @@ -3,26 +3,26 @@ using System.CodeDom.Compiler; using global::System.Runtime.CompilerServices; +using System.Threading.Tasks; [assembly: global::Xunit.TestFramework("TechTalk.SpecFlow.xUnit.SpecFlowPlugin.XunitTestFrameworkWithAssemblyFixture", "TechTalk.SpecFlow.xUnit.SpecFlowPlugin")] [assembly: global::TechTalk.SpecFlow.xUnit.SpecFlowPlugin.AssemblyFixture(typeof(global::PROJECT_ROOT_NAMESPACE_XUnitAssemblyFixture))] [GeneratedCode("SpecFlow", "SPECFLOW_VERSION")] -public class PROJECT_ROOT_NAMESPACE_XUnitAssemblyFixture : global::System.IDisposable +public class PROJECT_ROOT_NAMESPACE_XUnitAssemblyFixture : global::Xunit.IAsyncLifetime { - private readonly global::System.Reflection.Assembly _currentAssembly; - [MethodImpl(MethodImplOptions.NoInlining)] - public PROJECT_ROOT_NAMESPACE_XUnitAssemblyFixture() + public async Task InitializeAsync() { - _currentAssembly = typeof(PROJECT_ROOT_NAMESPACE_XUnitAssemblyFixture).Assembly; - global::TechTalk.SpecFlow.TestRunnerManager.OnTestRunStart(_currentAssembly); + var currentAssembly = typeof(PROJECT_ROOT_NAMESPACE_XUnitAssemblyFixture).Assembly; + await global::TechTalk.SpecFlow.TestRunnerManager.OnTestRunStartAsync(currentAssembly); } [MethodImpl(MethodImplOptions.NoInlining)] - public void Dispose() + public async Task DisposeAsync() { - global::TechTalk.SpecFlow.TestRunnerManager.OnTestRunEnd(_currentAssembly); + var currentAssembly = typeof(PROJECT_ROOT_NAMESPACE_XUnitAssemblyFixture).Assembly; + await global::TechTalk.SpecFlow.TestRunnerManager.OnTestRunEndAsync(currentAssembly); } } diff --git a/Plugins/TechTalk.SpecFlow.xUnit.Generator.SpecFlowPlugin/build/xUnit.AssemblyHooks.template.vb b/Plugins/TechTalk.SpecFlow.xUnit.Generator.SpecFlowPlugin/build/xUnit.AssemblyHooks.template.vb index 07dc770da..958f8d3b0 100644 --- a/Plugins/TechTalk.SpecFlow.xUnit.Generator.SpecFlowPlugin/build/xUnit.AssemblyHooks.template.vb +++ b/Plugins/TechTalk.SpecFlow.xUnit.Generator.SpecFlowPlugin/build/xUnit.AssemblyHooks.template.vb @@ -2,26 +2,27 @@ Imports System.CodeDom.Compiler Imports System.Runtime.CompilerServices +Imports System.Threading.Tasks +Imports System.Reflection Public Class PROJECT_ROOT_NAMESPACE_XUnitAssemblyFixture - Implements Global.System.IDisposable - - Private ReadOnly _currentAssembly As Global.System.Reflection.Assembly + Implements Global.Xunit.IAsyncLifetime - Public Sub New() - _currentAssembly = GetType(PROJECT_ROOT_NAMESPACE_XUnitAssemblyFixture).Assembly - Global.TechTalk.SpecFlow.TestRunnerManager.OnTestRunStart(_currentAssembly) - End Sub + Public Async Function InitializeAsync() As Task Implements Global.Xunit.IAsyncLifetime.InitializeAsync + Dim currentAssembly As Assembly = GetType(PROJECT_ROOT_NAMESPACE_XUnitAssemblyFixture).Assembly + Await Global.TechTalk.SpecFlow.TestRunnerManager.OnTestRunStartAsync(currentAssembly) + End Function - Public Sub Dispose() Implements Global.System.IDisposable.Dispose - Global.TechTalk.SpecFlow.TestRunnerManager.OnTestRunEnd(_currentAssembly) - End Sub + Private Async Function DisposeAsync() As Task Implements Global.Xunit.IAsyncLifetime.DisposeAsync + Dim currentAssembly As Assembly = GetType(PROJECT_ROOT_NAMESPACE_XUnitAssemblyFixture).Assembly + Await Global.TechTalk.SpecFlow.TestRunnerManager.OnTestRunEndAsync(currentAssembly) + End Function End Class diff --git a/Plugins/TechTalk.SpecFlow.xUnit.SpecFlowPlugin/XUnitParallelWorkerTracker.cs b/Plugins/TechTalk.SpecFlow.xUnit.SpecFlowPlugin/XUnitParallelWorkerTracker.cs new file mode 100644 index 000000000..2374008ec --- /dev/null +++ b/Plugins/TechTalk.SpecFlow.xUnit.SpecFlowPlugin/XUnitParallelWorkerTracker.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Concurrent; +using System.Threading; + +namespace TechTalk.SpecFlow.xUnit.SpecFlowPlugin; + +public class XUnitParallelWorkerTracker +{ + public static readonly XUnitParallelWorkerTracker Instance = new(); + + private readonly ConcurrentBag _availableWorkers = new(); + private int _workerCount = 0; + + private XUnitParallelWorkerTracker() { } + + public string GetWorkerId() + { + if (!_availableWorkers.TryTake(out var workerId)) + { + workerId = $"XW{Interlocked.Increment(ref _workerCount):D3}"; + } + return workerId; + } + + public void ReleaseWorker(string workerId) + { + _availableWorkers.Add(workerId); + } +} \ No newline at end of file diff --git a/Plugins/TechTalk.SpecFlow.xUnit.SpecFlowPlugin/XunitTestAssemblyRunnerWithAssemblyFixture.cs b/Plugins/TechTalk.SpecFlow.xUnit.SpecFlowPlugin/XunitTestAssemblyRunnerWithAssemblyFixture.cs index 2f1455fea..c0153eae1 100644 --- a/Plugins/TechTalk.SpecFlow.xUnit.SpecFlowPlugin/XunitTestAssemblyRunnerWithAssemblyFixture.cs +++ b/Plugins/TechTalk.SpecFlow.xUnit.SpecFlowPlugin/XunitTestAssemblyRunnerWithAssemblyFixture.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Xunit; using Xunit.Abstractions; using Xunit.Sdk; @@ -36,27 +37,46 @@ protected override async Task AfterTestAssemblyStartingAsync() await base.AfterTestAssemblyStartingAsync(); // Go find all the AssemblyFixtureAttributes adorned on the test assembly - Aggregator.Run(() => - { - var reflectionAssemblyInfo = (IReflectionAssemblyInfo)TestAssembly.Assembly; - var fixturesAttrs = reflectionAssemblyInfo.Assembly - .GetCustomAttributes(typeof(AssemblyFixtureAttribute), false) - .Cast() - .ToList(); - - // Instantiate all the fixtures - foreach (var fixtureAttr in fixturesAttrs) + await Aggregator.RunAsync( + async () => { - _assemblyFixtureMappings[fixtureAttr.FixtureType] = Activator.CreateInstance(fixtureAttr.FixtureType); - } - }); + var reflectionAssemblyInfo = (IReflectionAssemblyInfo)TestAssembly.Assembly; + var fixturesAttrs = reflectionAssemblyInfo.Assembly + .GetCustomAttributes(typeof(AssemblyFixtureAttribute), false) + .Cast() + .ToList(); + + // Instantiate all the fixtures + foreach (var fixtureAttr in fixturesAttrs) + { + object assemblyFixture = Activator.CreateInstance(fixtureAttr.FixtureType); + if (assemblyFixture is IAsyncLifetime assemblyFixtureLifetime) + await assemblyFixtureLifetime.InitializeAsync(); + _assemblyFixtureMappings[fixtureAttr.FixtureType] = assemblyFixture; + } + }); } protected override Task BeforeTestAssemblyFinishedAsync() { // Make sure we clean up everybody who is disposable, and use Aggregator.Run to isolate Dispose failures - foreach (var disposable in _assemblyFixtureMappings.Values.OfType()) - Aggregator.Run(disposable.Dispose); + foreach (var potentialDisposable in _assemblyFixtureMappings.Values) + { + if (potentialDisposable is IDisposable disposable) + { + Aggregator.Run(disposable.Dispose); + } +#if NET // IAsyncDisposable supported natively in .NET 5, .NET 6 + else if (potentialDisposable is IAsyncDisposable asyncDisposable) + { + Aggregator.RunAsync(async () => await asyncDisposable.DisposeAsync()); + } +#endif + else if (potentialDisposable is IAsyncLifetime asyncLifetime) + { + Aggregator.RunAsync(async () => await asyncLifetime.DisposeAsync()); + } + } return base.BeforeTestAssemblyFinishedAsync(); } diff --git a/SpecFlow.Tools.MsBuild.Generation/MSBuildTaskAnalyticsTransmitter.cs b/SpecFlow.Tools.MsBuild.Generation/MSBuildTaskAnalyticsTransmitter.cs index 334a3c8e7..2bf9a7f4e 100644 --- a/SpecFlow.Tools.MsBuild.Generation/MSBuildTaskAnalyticsTransmitter.cs +++ b/SpecFlow.Tools.MsBuild.Generation/MSBuildTaskAnalyticsTransmitter.cs @@ -54,7 +54,7 @@ public async Task TransmitProjectCompilingEventAsync() _specFlowProjectInfo.TargetFrameworks, _specFlowProjectInfo.CurrentTargetFramework, _specFlowProjectInfo.ProjectGuid); - return await _analyticsTransmitter.TransmitSpecFlowProjectCompilingEvent(projectCompilingEvent); + return await _analyticsTransmitter.TransmitSpecFlowProjectCompilingEventAsync(projectCompilingEvent); } } } diff --git a/TechTalk.SpecFlow.Generator/CodeDom/CodeDomHelper.cs b/TechTalk.SpecFlow.Generator/CodeDom/CodeDomHelper.cs index 316977324..f898d3ec7 100644 --- a/TechTalk.SpecFlow.Generator/CodeDom/CodeDomHelper.cs +++ b/TechTalk.SpecFlow.Generator/CodeDom/CodeDomHelper.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; +using System.Threading.Tasks; using Microsoft.CSharp; using Microsoft.VisualBasic; @@ -59,7 +60,6 @@ private NotImplementedException TargetLanguageNotSupportedException() return new NotImplementedException($"{TargetLanguage} is not supported"); } - public void BindTypeToSourceFile(CodeTypeDeclaration typeDeclaration, string fileName) { switch (TargetLanguage) @@ -76,7 +76,6 @@ public void BindTypeToSourceFile(CodeTypeDeclaration typeDeclaration, string fil } } - public CodeStatement GetStartRegionStatement(string regionText) { switch (TargetLanguage) @@ -101,7 +100,6 @@ public CodeStatement GetEndRegionStatement() return new CodeCommentStatement("#endregion"); } - public CodeStatement GetDisableWarningsPragma() { switch (TargetLanguage) @@ -232,6 +230,63 @@ public IEnumerable CreateSourceLinePragmaStatement(string filenam break; } } + + public void MarkCodeMemberMethodAsAsync(CodeMemberMethod method) + { + if (IsVoid(method.ReturnType)) + { + method.ReturnType = new CodeTypeReference($"async {typeof(Task).FullName}"); + return; + } + + var returnTypeArgumentReferences = method.ReturnType.TypeArguments.OfType().ToArray(); + + var asyncReturnType = new CodeTypeReference($"async {method.ReturnType.BaseType}", returnTypeArgumentReferences); + method.ReturnType = asyncReturnType; + } + + public bool IsVoid(CodeTypeReference codeTypeReference) + { + return typeof(void).FullName!.Equals(codeTypeReference.BaseType); + } + + public void MarkCodeMethodInvokeExpressionAsAwait(CodeMethodInvokeExpression expression) + { + if (expression.Method.TargetObject is CodeVariableReferenceExpression variableExpression) + { + expression.Method.TargetObject = new CodeVariableReferenceExpression($"await {variableExpression.VariableName}"); + } + else if (expression.Method.TargetObject is CodeFieldReferenceExpression fieldExpression + && fieldExpression.TargetObject is CodeThisReferenceExpression thisFieldExpression) + { + expression.Method.TargetObject = new CodeFieldReferenceExpression(GetAwaitedMethodThisTargetObject(thisFieldExpression), fieldExpression.FieldName); + } + else if (expression.Method.TargetObject is CodeTypeReferenceExpression typeExpression) + { + string baseType = typeExpression.Type.BaseType; + if (typeExpression.Type.Options.HasFlag(CodeTypeReferenceOptions.GlobalReference)) + { + typeExpression.Type.Options &= ~CodeTypeReferenceOptions.GlobalReference; + if (TargetLanguage == CodeDomProviderLanguage.CSharp) + baseType = $"global::{baseType}"; // For VB.NET, the BaseType already contains the "Global." prefix + } + expression.Method.TargetObject = new CodeTypeReferenceExpression(new CodeTypeReference($"await {baseType}", typeExpression.Type.Options)); + } + else if (expression.Method.TargetObject is CodeThisReferenceExpression thisExpression) + { + expression.Method.TargetObject = GetAwaitedMethodThisTargetObject(expression.Method.TargetObject); + } + } + + private CodeExpression GetAwaitedMethodThisTargetObject(CodeExpression thisExpression) + { + return TargetLanguage switch + { + CodeDomProviderLanguage.VB => new CodeVariableReferenceExpression("await Me"), + CodeDomProviderLanguage.CSharp => new CodeVariableReferenceExpression("await this"), + _ => thisExpression + }; + } } } diff --git a/TechTalk.SpecFlow.Generator/Generation/GeneratorConstants.cs b/TechTalk.SpecFlow.Generator/Generation/GeneratorConstants.cs index 9a6067be7..490456098 100644 --- a/TechTalk.SpecFlow.Generator/Generation/GeneratorConstants.cs +++ b/TechTalk.SpecFlow.Generator/Generation/GeneratorConstants.cs @@ -5,13 +5,13 @@ public class GeneratorConstants public const string DEFAULT_NAMESPACE = "SpecFlowTests"; public const string TEST_NAME_FORMAT = "{0}"; public const string SCENARIO_INITIALIZE_NAME = "ScenarioInitialize"; - public const string SCENARIO_START_NAME = "ScenarioStart"; - public const string SCENARIO_CLEANUP_NAME = "ScenarioCleanup"; - public const string TEST_INITIALIZE_NAME = "TestInitialize"; - public const string TEST_CLEANUP_NAME = "TestTearDown"; - public const string TESTCLASS_INITIALIZE_NAME = "FeatureSetup"; - public const string TESTCLASS_CLEANUP_NAME = "FeatureTearDown"; - public const string BACKGROUND_NAME = "FeatureBackground"; + public const string SCENARIO_START_NAME = "ScenarioStartAsync"; + public const string SCENARIO_CLEANUP_NAME = "ScenarioCleanupAsync"; + public const string TEST_INITIALIZE_NAME = "TestInitializeAsync"; + public const string TEST_CLEANUP_NAME = "TestTearDownAsync"; + public const string TESTCLASS_INITIALIZE_NAME = "FeatureSetupAsync"; + public const string TESTCLASS_CLEANUP_NAME = "FeatureTearDownAsync"; + public const string BACKGROUND_NAME = "FeatureBackgroundAsync"; public const string TESTRUNNER_FIELD = "testRunner"; public const string SPECFLOW_NAMESPACE = "TechTalk.SpecFlow"; public const string SCENARIO_OUTLINE_EXAMPLE_TAGS_PARAMETER = "exampleTags"; diff --git a/TechTalk.SpecFlow.Generator/Generation/ScenarioPartHelper.cs b/TechTalk.SpecFlow.Generator/Generation/ScenarioPartHelper.cs index df521073a..41ef85535 100644 --- a/TechTalk.SpecFlow.Generator/Generation/ScenarioPartHelper.cs +++ b/TechTalk.SpecFlow.Generator/Generation/ScenarioPartHelper.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +using System.Threading.Tasks; using Gherkin.Ast; using TechTalk.SpecFlow.Configuration; using TechTalk.SpecFlow.Generator.CodeDom; @@ -15,7 +16,6 @@ public class ScenarioPartHelper private readonly CodeDomHelper _codeDomHelper; private int _tableCounter; - public ScenarioPartHelper(SpecFlowConfiguration specFlowConfiguration, CodeDomHelper codeDomHelper) { _specFlowConfiguration = specFlowConfiguration; @@ -36,7 +36,8 @@ public void SetupFeatureBackground(TestClassGenerationContext generationContext) backgroundMethod.Attributes = MemberAttributes.Public; backgroundMethod.Name = GeneratorConstants.BACKGROUND_NAME; - + _codeDomHelper.MarkCodeMemberMethodAsAsync(backgroundMethod); + var statements = new List(); using (new SourceLineScope(_specFlowConfiguration, _codeDomHelper, statements, generationContext.Document.SourceFilePath, background.Location)) { @@ -63,18 +64,18 @@ public void GenerateStep(TestClassGenerationContext generationContext, List tags) diff --git a/TechTalk.SpecFlow.Generator/Generation/UnitTestFeatureGenerator.cs b/TechTalk.SpecFlow.Generator/Generation/UnitTestFeatureGenerator.cs index af8226f58..63109e31a 100644 --- a/TechTalk.SpecFlow.Generator/Generation/UnitTestFeatureGenerator.cs +++ b/TechTalk.SpecFlow.Generator/Generation/UnitTestFeatureGenerator.cs @@ -117,16 +117,21 @@ private void SetupScenarioCleanupMethod(TestClassGenerationContext generationCon var scenarioCleanupMethod = generationContext.ScenarioCleanupMethod; scenarioCleanupMethod.Attributes = MemberAttributes.Public | MemberAttributes.Final; - scenarioCleanupMethod.Name = GeneratorConstants.SCENARIO_CLEANUP_NAME; + scenarioCleanupMethod.Name = GeneratorConstants.SCENARIO_CLEANUP_NAME; + + _codeDomHelper.MarkCodeMemberMethodAsAsync(scenarioCleanupMethod); // call collect errors var testRunnerField = _scenarioPartHelper.GetTestRunnerExpression(); - //testRunner.CollectScenarioErrors(); - scenarioCleanupMethod.Statements.Add( - new CodeMethodInvokeExpression( - testRunnerField, - "CollectScenarioErrors")); + //await testRunner.CollectScenarioErrorsAsync(); + var expression = new CodeMethodInvokeExpression( + testRunnerField, + nameof(TestRunner.CollectScenarioErrorsAsync)); + + _codeDomHelper.MarkCodeMethodInvokeExpressionAsAwait(expression); + + scenarioCleanupMethod.Statements.Add(expression); } private void SetupTestClass(TestClassGenerationContext generationContext) @@ -159,7 +164,6 @@ private CodeMemberField DeclareTestRunnerMember(CodeTypeDeclaration type) return testRunnerField; } - private void SetupTestClassInitializeMethod(TestClassGenerationContext generationContext) { var testClassInitializeMethod = generationContext.TestClassInitializeMethod; @@ -167,22 +171,27 @@ private void SetupTestClassInitializeMethod(TestClassGenerationContext generatio testClassInitializeMethod.Attributes = MemberAttributes.Public; testClassInitializeMethod.Name = GeneratorConstants.TESTCLASS_INITIALIZE_NAME; + _codeDomHelper.MarkCodeMemberMethodAsAsync(testClassInitializeMethod); + _testGeneratorProvider.SetTestClassInitializeMethod(generationContext); - //testRunner = TestRunnerManager.GetTestRunner(); if UnitTestGeneratorTraits.ParallelExecution - //testRunner = TestRunnerManager.GetTestRunner(null, 0); if not UnitTestGeneratorTraits.ParallelExecution + //testRunner = TestRunnerManager.GetTestRunnerForAssembly(null, [test_worker_id]); var testRunnerField = _scenarioPartHelper.GetTestRunnerExpression(); - var testRunnerParameters = _testGeneratorProvider.GetTraits().HasFlag(UnitTestGeneratorTraits.ParallelExecution) - ? new CodeExpression[] { } - : new[] {new CodePrimitiveExpression(null), new CodePrimitiveExpression(0)}; + var testRunnerParameters = new[] + { + new CodePrimitiveExpression(null), + _testGeneratorProvider.GetTestWorkerIdExpression() + }; + + var getTestRunnerExpression = new CodeMethodInvokeExpression( + new CodeTypeReferenceExpression(typeof(TestRunnerManager)), + nameof(TestRunnerManager.GetTestRunnerForAssembly), testRunnerParameters); testClassInitializeMethod.Statements.Add( new CodeAssignStatement( testRunnerField, - new CodeMethodInvokeExpression( - new CodeTypeReferenceExpression(typeof(TestRunnerManager)), - "GetTestRunner", testRunnerParameters))); + getTestRunnerExpression)); //FeatureInfo featureInfo = new FeatureInfo("xxxx"); testClassInitializeMethod.Statements.Add( @@ -198,14 +207,16 @@ private void SetupTestClassInitializeMethod(TestClassGenerationContext generatio _codeDomHelper.TargetLanguage.ToString()), new CodeFieldReferenceExpression(null, GeneratorConstants.FEATURE_TAGS_VARIABLE_NAME)))); - //testRunner.OnFeatureStart(featureInfo); - testClassInitializeMethod.Statements.Add( - new CodeMethodInvokeExpression( - testRunnerField, - "OnFeatureStart", - new CodeVariableReferenceExpression("featureInfo"))); - } + //await testRunner.OnFeatureStartAsync(featureInfo); + var onFeatureStartExpression = new CodeMethodInvokeExpression( + testRunnerField, + nameof(ITestRunner.OnFeatureStartAsync), + new CodeVariableReferenceExpression("featureInfo")); + + _codeDomHelper.MarkCodeMethodInvokeExpressionAsAwait(onFeatureStartExpression); + testClassInitializeMethod.Statements.Add(onFeatureStartExpression); + } private void SetupTestClassCleanupMethod(TestClassGenerationContext generationContext) { @@ -214,15 +225,22 @@ private void SetupTestClassCleanupMethod(TestClassGenerationContext generationCo testClassCleanupMethod.Attributes = MemberAttributes.Public; testClassCleanupMethod.Name = GeneratorConstants.TESTCLASS_CLEANUP_NAME; + _codeDomHelper.MarkCodeMemberMethodAsAsync(testClassCleanupMethod); + _testGeneratorProvider.SetTestClassCleanupMethod(generationContext); var testRunnerField = _scenarioPartHelper.GetTestRunnerExpression(); - // testRunner.OnFeatureEnd(); - testClassCleanupMethod.Statements.Add( - new CodeMethodInvokeExpression( - testRunnerField, - "OnFeatureEnd")); - // testRunner = null; + + // await testRunner.OnFeatureEndAsync(); + var expression = new CodeMethodInvokeExpression( + testRunnerField, + nameof(ITestRunner.OnFeatureEndAsync)); + + _codeDomHelper.MarkCodeMethodInvokeExpressionAsAwait(expression); + + testClassCleanupMethod.Statements.Add(expression); + + // testRunner = null; testClassCleanupMethod.Statements.Add( new CodeAssignStatement( testRunnerField, @@ -236,6 +254,8 @@ private void SetupTestInitializeMethod(TestClassGenerationContext generationCont testInitializeMethod.Attributes = MemberAttributes.Public | MemberAttributes.Final; testInitializeMethod.Name = GeneratorConstants.TEST_INITIALIZE_NAME; + _codeDomHelper.MarkCodeMemberMethodAsAsync(testInitializeMethod); + _testGeneratorProvider.SetTestInitializeMethod(generationContext); } @@ -246,14 +266,20 @@ private void SetupTestCleanupMethod(TestClassGenerationContext generationContext testCleanupMethod.Attributes = MemberAttributes.Public | MemberAttributes.Final; testCleanupMethod.Name = GeneratorConstants.TEST_CLEANUP_NAME; + _codeDomHelper.MarkCodeMemberMethodAsAsync(testCleanupMethod); + _testGeneratorProvider.SetTestCleanupMethod(generationContext); var testRunnerField = _scenarioPartHelper.GetTestRunnerExpression(); - //testRunner.OnScenarioEnd(); - testCleanupMethod.Statements.Add( - new CodeMethodInvokeExpression( - testRunnerField, - "OnScenarioEnd")); + + //await testRunner.OnScenarioEndAsync(); + var expression = new CodeMethodInvokeExpression( + testRunnerField, + nameof(ITestRunner.OnScenarioEndAsync)); + + _codeDomHelper.MarkCodeMethodInvokeExpressionAsAwait(expression); + + testCleanupMethod.Statements.Add(expression); } private void SetupScenarioInitializeMethod(TestClassGenerationContext generationContext) @@ -280,13 +306,18 @@ private void SetupScenarioStartMethod(TestClassGenerationContext generationConte scenarioStartMethod.Attributes = MemberAttributes.Public | MemberAttributes.Final; scenarioStartMethod.Name = GeneratorConstants.SCENARIO_START_NAME; + + _codeDomHelper.MarkCodeMemberMethodAsAsync(scenarioStartMethod); - //testRunner.OnScenarioStart(); + //await testRunner.OnScenarioStartAsync(); var testRunnerField = _scenarioPartHelper.GetTestRunnerExpression(); - scenarioStartMethod.Statements.Add( - new CodeMethodInvokeExpression( - testRunnerField, - nameof(ITestExecutionEngine.OnScenarioStart))); + var expression = new CodeMethodInvokeExpression( + testRunnerField, + nameof(ITestExecutionEngine.OnScenarioStartAsync)); + + _codeDomHelper.MarkCodeMethodInvokeExpressionAsAwait(expression); + + scenarioStartMethod.Statements.Add(expression); } } } \ No newline at end of file diff --git a/TechTalk.SpecFlow.Generator/Generation/UnitTestMethodGenerator.cs b/TechTalk.SpecFlow.Generator/Generation/UnitTestMethodGenerator.cs index 10b45abfd..fc37f3131 100644 --- a/TechTalk.SpecFlow.Generator/Generation/UnitTestMethodGenerator.cs +++ b/TechTalk.SpecFlow.Generator/Generation/UnitTestMethodGenerator.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; +using System.Threading.Tasks; using Gherkin.Ast; using TechTalk.SpecFlow.Configuration; using TechTalk.SpecFlow.Generator.CodeDom; @@ -63,7 +64,7 @@ private void GenerateScenarioOutlineTest(TestClassGenerationContext generationCo var paramToIdentifier = CreateParamToIdentifierMapping(scenarioOutline); - var scenarioOutlineTestMethod = CreateScenatioOutlineTestMethod(generationContext, scenarioOutline, paramToIdentifier); + var scenarioOutlineTestMethod = CreateScenarioOutlineTestMethod(generationContext, scenarioOutline, paramToIdentifier); var exampleTagsParam = new CodeVariableReferenceExpression(GeneratorConstants.SCENARIO_OUTLINE_EXAMPLE_TAGS_PARAMETER); if (generationContext.GenerateRowTests) { @@ -213,12 +214,14 @@ private void AddVariableForArguments(CodeMemberMethod testMethod, ParameterSubst internal void GenerateTestMethodBody(TestClassGenerationContext generationContext, StepsContainer scenario, CodeMemberMethod testMethod, ParameterSubstitution paramToIdentifier, SpecFlowFeature feature) { var statementsWhenScenarioIsIgnored = new CodeStatement[] { new CodeExpressionStatement(CreateTestRunnerSkipScenarioCall()) }; + + var callScenarioStartMethodExpression = new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), generationContext.ScenarioStartMethod.Name); + + _codeDomHelper.MarkCodeMethodInvokeExpressionAsAwait(callScenarioStartMethodExpression); + var statementsWhenScenarioIsExecuted = new List { - new CodeExpressionStatement( - new CodeMethodInvokeExpression( - new CodeThisReferenceExpression(), - generationContext.ScenarioStartMethod.Name)) + new CodeExpressionStatement(callScenarioStartMethodExpression) }; @@ -277,13 +280,15 @@ internal void GenerateScenarioInitializeCall(TestClassGenerationContext generati internal void GenerateScenarioCleanupMethodCall(TestClassGenerationContext generationContext, CodeMemberMethod testMethod) { // call scenario cleanup - testMethod.Statements.Add( - new CodeMethodInvokeExpression( - new CodeThisReferenceExpression(), - generationContext.ScenarioCleanupMethod.Name)); - } + var expression = new CodeMethodInvokeExpression( + new CodeThisReferenceExpression(), + generationContext.ScenarioCleanupMethod.Name); + _codeDomHelper.MarkCodeMethodInvokeExpressionAsAwait(expression); + testMethod.Statements.Add(expression); + } + private CodeMethodInvokeExpression CreateTestRunnerSkipScenarioCall() { return new CodeMethodInvokeExpression( @@ -386,13 +391,15 @@ private bool CanUseFirstColumnAsName(IEnumerable tableBody return tableBody.Select(r => r.Cells.First().Value.ToIdentifier()).Distinct().Count() == tableBody.Count(); } - private CodeMemberMethod CreateScenatioOutlineTestMethod(TestClassGenerationContext generationContext, ScenarioOutline scenarioOutline, ParameterSubstitution paramToIdentifier) + private CodeMemberMethod CreateScenarioOutlineTestMethod(TestClassGenerationContext generationContext, ScenarioOutline scenarioOutline, ParameterSubstitution paramToIdentifier) { var testMethod = _codeDomHelper.CreateMethod(generationContext.TestClass); testMethod.Attributes = MemberAttributes.Public; testMethod.Name = string.Format(GeneratorConstants.TEST_NAME_FORMAT, scenarioOutline.Name.ToIdentifier()); + _codeDomHelper.MarkCodeMemberMethodAsAsync(testMethod); + foreach (var pair in paramToIdentifier) { testMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), pair.Value)); @@ -414,26 +421,26 @@ private CodeMemberMethod CreateScenatioOutlineTestMethod(TestClassGenerationCont string variantName) { var testMethod = CreateTestMethod(generationContext, scenarioOutline, exampleSetTags, variantName, exampleSetIdentifier); - - + //call test implementation with the params var argumentExpressions = row.Cells.Select(paramCell => new CodePrimitiveExpression(paramCell.Value)).Cast().ToList(); argumentExpressions.Add(_scenarioPartHelper.GetStringArrayExpression(exampleSetTags)); - - + var statements = new List(); using (new SourceLineScope(_specFlowConfiguration, _codeDomHelper, statements, generationContext.Document.SourceFilePath, scenarioOutline.Location)) { - statements.Add(new CodeExpressionStatement( - new CodeMethodInvokeExpression( - new CodeThisReferenceExpression(), - scenatioOutlineTestMethod.Name, - argumentExpressions.ToArray()))); - } + var callTestMethodExpression = new CodeMethodInvokeExpression( + new CodeThisReferenceExpression(), + scenatioOutlineTestMethod.Name, + argumentExpressions.ToArray()); + _codeDomHelper.MarkCodeMethodInvokeExpressionAsAwait(callTestMethodExpression); + statements.Add(new CodeExpressionStatement(callTestMethodExpression)); + } + testMethod.Statements.AddRange(statements.ToArray()); //_linePragmaHandler.AddLineDirectiveHidden(testMethod.Statements); @@ -443,9 +450,9 @@ private CodeMemberMethod CreateScenatioOutlineTestMethod(TestClassGenerationCont // Otherwise, use the title of the example set provided by the user in the feature file. string exampleSetName = string.IsNullOrEmpty(exampleSetIdentifier) ? exampleSetTitle : exampleSetIdentifier; _unitTestGeneratorProvider.SetTestMethodAsRow(generationContext, testMethod, scenarioOutline.Name, exampleSetName, variantName, arguments); - } + } - private CodeMemberMethod CreateTestMethod( + private CodeMemberMethod CreateTestMethod( TestClassGenerationContext generationContext, StepsContainer scenario, IEnumerable additionalTags, @@ -453,13 +460,13 @@ private CodeMemberMethod CreateScenatioOutlineTestMethod(TestClassGenerationCont string exampleSetIdentifier = null) { var testMethod = _codeDomHelper.CreateMethod(generationContext.TestClass); + _codeDomHelper.MarkCodeMemberMethodAsAsync(testMethod); SetupTestMethod(generationContext, testMethod, scenario, additionalTags, variantName, exampleSetIdentifier); return testMethod; } - private void SetupTestMethod( TestClassGenerationContext generationContext, CodeMemberMethod testMethod, diff --git a/TechTalk.SpecFlow.Generator/TestGenerator.cs b/TechTalk.SpecFlow.Generator/TestGenerator.cs index 034a4a1be..0ead76aa2 100644 --- a/TechTalk.SpecFlow.Generator/TestGenerator.cs +++ b/TechTalk.SpecFlow.Generator/TestGenerator.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Reflection; using System.Text; +using System.Text.RegularExpressions; using TechTalk.SpecFlow.Configuration; using TechTalk.SpecFlow.Generator.CodeDom; using TechTalk.SpecFlow.Generator.Configuration; @@ -19,7 +20,7 @@ namespace TechTalk.SpecFlow.Generator { public class TestGenerator : ErrorHandlingTestGenerator, ITestGenerator { - protected readonly SpecFlow.Configuration.SpecFlowConfiguration specFlowConfiguration; + protected readonly SpecFlowConfiguration specFlowConfiguration; protected readonly ProjectSettings projectSettings; protected readonly ITestHeaderWriter testHeaderWriter; protected readonly ITestUpToDateChecker testUpToDateChecker; @@ -28,7 +29,7 @@ public class TestGenerator : ErrorHandlingTestGenerator, ITestGenerator private readonly IGherkinParserFactory gherkinParserFactory; - public TestGenerator(SpecFlow.Configuration.SpecFlowConfiguration specFlowConfiguration, + public TestGenerator(SpecFlowConfiguration specFlowConfiguration, ProjectSettings projectSettings, ITestHeaderWriter testHeaderWriter, ITestUpToDateChecker testUpToDateChecker, @@ -105,17 +106,36 @@ protected string GetGeneratedTestCode(FeatureFileInput featureFileInput) outputWriter.Flush(); var generatedTestCode = outputWriter.ToString(); - return FixVBNetGlobalNamespace(generatedTestCode); + return FixVb(generatedTestCode); } } + private string FixVb(string generatedTestCode) + { + return FixVBNetAsyncMethodDeclarations(FixVBNetGlobalNamespace(generatedTestCode)); + } + private string FixVBNetGlobalNamespace(string generatedTestCode) { return generatedTestCode .Replace("Global.GlobalVBNetNamespace", "Global") .Replace("GlobalVBNetNamespace", "Global"); } + + /// + /// This is a workaround to allow async/await calls in VB.NET. Works in cooperation with CodeDomHelper.MarkCodeMemberMethodAsAsync() method + /// + private string FixVBNetAsyncMethodDeclarations(string generatedTestCode) + { + var functionRegex = new Regex(@"^([^\n]*)Function[ ]*([^(\n]*)(\([^\n]*\)[ ]*As) async([^\n]*)$", RegexOptions.Multiline); + var subRegex = new Regex(@"^([^\n]*)Sub[ ]*([^(\n]*)(\([^\n]*\)[ ]*As) async([^\n]*)$", RegexOptions.Multiline); + + var result = functionRegex.Replace(generatedTestCode, "$1 Async Function $2$3$4"); + result = subRegex.Replace(result, "$1 Async Sub $2$3$4"); + return result; + } + private CodeNamespace GenerateTestFileCode(FeatureFileInput featureFileInput) { string targetNamespace = GetTargetNamespace(featureFileInput) ?? "SpecFlow.GeneratedTests"; diff --git a/TechTalk.SpecFlow.Generator/UnitTestProvider/IUnitTestGeneratorProvider.cs b/TechTalk.SpecFlow.Generator/UnitTestProvider/IUnitTestGeneratorProvider.cs index 2b1889a40..d38defdcc 100644 --- a/TechTalk.SpecFlow.Generator/UnitTestProvider/IUnitTestGeneratorProvider.cs +++ b/TechTalk.SpecFlow.Generator/UnitTestProvider/IUnitTestGeneratorProvider.cs @@ -26,5 +26,9 @@ public interface IUnitTestGeneratorProvider void SetRowTest(TestClassGenerationContext generationContext, CodeMemberMethod testMethod, string scenarioTitle); void SetRow(TestClassGenerationContext generationContext, CodeMemberMethod testMethod, IEnumerable arguments, IEnumerable tags, bool isIgnored); void SetTestMethodAsRow(TestClassGenerationContext generationContext, CodeMemberMethod testMethod, string scenarioTitle, string exampleSetName, string variantName, IEnumerable> arguments); + + void MarkCodeMethodInvokeExpressionAsAwait(CodeMethodInvokeExpression expression); + + CodeExpression GetTestWorkerIdExpression(); } } diff --git a/TechTalk.SpecFlow.Generator/UnitTestProvider/MsTestGeneratorProvider.cs b/TechTalk.SpecFlow.Generator/UnitTestProvider/MsTestGeneratorProvider.cs index 8b38b262a..bb72377ab 100644 --- a/TechTalk.SpecFlow.Generator/UnitTestProvider/MsTestGeneratorProvider.cs +++ b/TechTalk.SpecFlow.Generator/UnitTestProvider/MsTestGeneratorProvider.cs @@ -132,6 +132,17 @@ protected virtual void FixTestRunOrderingIssue(TestClassGenerationContext genera var featureContextExpression = new CodePropertyReferenceExpression( new CodeFieldReferenceExpression(null, generationContext.TestRunnerField.Name), "FeatureContext"); + + var callTestClassInitializeMethodExpression = new CodeMethodInvokeExpression( + new CodeTypeReferenceExpression( + new CodeTypeReference( + generationContext.Namespace.Name + "." + generationContext.TestClass.Name, + CodeTypeReferenceOptions.GlobalReference)), + generationContext.TestClassInitializeMethod.Name, + new CodePrimitiveExpression(null)); + + CodeDomHelper.MarkCodeMethodInvokeExpressionAsAwait(callTestClassInitializeMethodExpression); + generationContext.TestInitializeMethod.Statements.Add( new CodeConditionStatement( new CodeBinaryOperatorExpression( @@ -149,13 +160,7 @@ protected virtual void FixTestRunOrderingIssue(TestClassGenerationContext genera CodeBinaryOperatorType.IdentityInequality, new CodePrimitiveExpression(generationContext.Feature.Name))), new CodeExpressionStatement( - new CodeMethodInvokeExpression( - new CodeTypeReferenceExpression( - new CodeTypeReference( - generationContext.Namespace.Name + "." + generationContext.TestClass.Name, - CodeTypeReferenceOptions.GlobalReference)), - generationContext.TestClassInitializeMethod.Name, - new CodePrimitiveExpression(null))))); + callTestClassInitializeMethodExpression))); } public void SetTestCleanupMethod(TestClassGenerationContext generationContext) @@ -214,5 +219,19 @@ public virtual void SetTestMethodAsRow(TestClassGenerationContext generationCont SetProperty(testMethod, "Parameter:" + pair.Key, pair.Value); } } + + public void MarkCodeMethodInvokeExpressionAsAwait(CodeMethodInvokeExpression expression) + { + CodeDomHelper.MarkCodeMethodInvokeExpressionAsAwait(expression); + } + + public CodeExpression GetTestWorkerIdExpression() + { + // System.Threading.Thread.CurrentThread.ManagedThreadId.ToString() + return new CodeMethodInvokeExpression( + new CodeVariableReferenceExpression("System.Threading.Thread.CurrentThread.ManagedThreadId"), + nameof(ToString) + ); + } } } diff --git a/TechTalk.SpecFlow.Generator/UnitTestProvider/NUnit3TestGeneratorProvider.cs b/TechTalk.SpecFlow.Generator/UnitTestProvider/NUnit3TestGeneratorProvider.cs index 4bad82392..a386f57e0 100644 --- a/TechTalk.SpecFlow.Generator/UnitTestProvider/NUnit3TestGeneratorProvider.cs +++ b/TechTalk.SpecFlow.Generator/UnitTestProvider/NUnit3TestGeneratorProvider.cs @@ -1,6 +1,7 @@ using System.CodeDom; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using BoDi; using TechTalk.SpecFlow.Generator.CodeDom; @@ -21,6 +22,7 @@ public class NUnit3TestGeneratorProvider : IUnitTestGeneratorProvider protected internal const string DESCRIPTION_ATTR = "NUnit.Framework.DescriptionAttribute"; protected internal const string TESTCONTEXT_TYPE = "NUnit.Framework.TestContext"; protected internal const string TESTCONTEXT_INSTANCE = "NUnit.Framework.TestContext.CurrentContext"; + protected internal const string TESTCONTEXT_WORKERID_PROPERTY = "WorkerId"; public NUnit3TestGeneratorProvider(CodeDomHelper codeDomHelper) { @@ -85,9 +87,11 @@ public virtual void FinalizeTestClass(TestClassGenerationContext generationConte nameof(ScenarioContext.ScenarioContainer)), nameof(IObjectContainer.RegisterInstanceAs), new CodeTypeReference(TESTCONTEXT_TYPE)), - new CodeVariableReferenceExpression(TESTCONTEXT_INSTANCE))); + GetTestContextExpression())); } + private CodeExpression GetTestContextExpression() => new CodeVariableReferenceExpression(TESTCONTEXT_INSTANCE); + public void SetTestInitializeMethod(TestClassGenerationContext generationContext) { CodeDomHelper.AddAttribute(generationContext.TestInitializeMethod, TESTSETUP_ATTR); @@ -145,5 +149,13 @@ public void SetTestMethodAsRow(TestClassGenerationContext generationContext, Cod { // doing nothing since we support RowTest } + + public void MarkCodeMethodInvokeExpressionAsAwait(CodeMethodInvokeExpression expression) + { + CodeDomHelper.MarkCodeMethodInvokeExpressionAsAwait(expression); + } + + public CodeExpression GetTestWorkerIdExpression() + => new CodePropertyReferenceExpression(GetTestContextExpression(), TESTCONTEXT_WORKERID_PROPERTY); } } diff --git a/TechTalk.SpecFlow.Generator/UnitTestProvider/XUnit2TestGeneratorProvider.cs b/TechTalk.SpecFlow.Generator/UnitTestProvider/XUnit2TestGeneratorProvider.cs index 1829c82d8..74854b017 100644 --- a/TechTalk.SpecFlow.Generator/UnitTestProvider/XUnit2TestGeneratorProvider.cs +++ b/TechTalk.SpecFlow.Generator/UnitTestProvider/XUnit2TestGeneratorProvider.cs @@ -5,13 +5,11 @@ using TechTalk.SpecFlow.Generator.CodeDom; using BoDi; using System.Text.RegularExpressions; -using TechTalk.SpecFlow.Generator.Interfaces; namespace TechTalk.SpecFlow.Generator.UnitTestProvider { public class XUnit2TestGeneratorProvider : IUnitTestGeneratorProvider { - private readonly ProjectSettings _projectSettings; private CodeTypeDeclaration _currentFixtureDataTypeDeclaration = null; private readonly CodeTypeReference _objectCodeTypeReference = new CodeTypeReference(typeof(object)); protected internal const string THEORY_ATTRIBUTE = "Xunit.SkippableTheoryAttribute"; @@ -34,11 +32,12 @@ public class XUnit2TestGeneratorProvider : IUnitTestGeneratorProvider protected internal const string CATEGORY_PROPERTY_NAME = "Category"; protected internal const string IGNORE_TEST_CLASS = "IgnoreTestClass"; protected internal const string NONPARALLELIZABLE_COLLECTION_NAME = "SpecFlowNonParallelizableFeatures"; + protected internal const string IASYNCLIFETIME_INTERFACE = "Xunit.IAsyncLifetime"; + protected internal const string XUNITPARALLELWORKERTRACKER_INSTANCE = "TechTalk.SpecFlow.xUnit.SpecFlowPlugin.XUnitParallelWorkerTracker.Instance"; - public XUnit2TestGeneratorProvider(CodeDomHelper codeDomHelper, ProjectSettings projectSettings) + public XUnit2TestGeneratorProvider(CodeDomHelper codeDomHelper) { CodeDomHelper = codeDomHelper; - _projectSettings = projectSettings; } public virtual void SetTestClass(TestClassGenerationContext generationContext, string featureTitle, string featureDescription) @@ -92,29 +91,28 @@ public virtual void SetRow(TestClassGenerationContext generationContext, CodeMem CodeDomHelper.AddAttribute(testMethod, INLINEDATA_ATTRIBUTE, args.ToArray()); } - protected virtual void SetTestConstructor(TestClassGenerationContext generationContext, CodeConstructor ctorMethod) + protected virtual void SetTestConstructor(TestClassGenerationContext generationContext, CodeConstructor constructor) { - var typeName = $"{_projectSettings.DefaultNamespace.Replace('.', '_')}_XUnitAssemblyFixture"; - ctorMethod.Parameters.Add( + constructor.Parameters.Add( new CodeParameterDeclarationExpression((CodeTypeReference)generationContext.CustomData[FIXTUREDATA_PARAMETER_NAME], FIXTUREDATA_PARAMETER_NAME)); - ctorMethod.Parameters.Add( - new CodeParameterDeclarationExpression(typeName, "assemblyFixture")); - ctorMethod.Parameters.Add( + constructor.Parameters.Add( new CodeParameterDeclarationExpression(OUTPUT_INTERFACE, OUTPUT_INTERFACE_PARAMETER_NAME)); - ctorMethod.Statements.Add( + constructor.Statements.Add( new CodeAssignStatement( new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), OUTPUT_INTERFACE_FIELD_NAME), new CodeVariableReferenceExpression(OUTPUT_INTERFACE_PARAMETER_NAME))); + } - //ctorMethod.Statements.Add( - // new CodeVariableDeclarationStatement(new CodeTypeReference(typeName), "assemblyFixture", - // new CodeObjectCreateExpression(new CodeTypeReference(typeName)))); + protected virtual void SetTestInitializeMethod(TestClassGenerationContext generationContext, CodeMemberMethod method) + { + var callTestInitializeMethodExpression = new CodeMethodInvokeExpression( + new CodeThisReferenceExpression(), + generationContext.TestInitializeMethod.Name); - ctorMethod.Statements.Add( - new CodeMethodInvokeExpression( - new CodeThisReferenceExpression(), - generationContext.TestInitializeMethod.Name)); + CodeDomHelper.MarkCodeMethodInvokeExpressionAsAwait(callTestInitializeMethodExpression); + + method.Statements.Add(callTestInitializeMethodExpression); } public virtual void SetTestMethodIgnore(TestClassGenerationContext generationContext, CodeMemberMethod testMethod) @@ -133,6 +131,7 @@ public virtual void SetTestMethodIgnore(TestClassGenerationContext generationCon // set [TheoryAttribute(Skip="reason")] theoryAttr?.Arguments.Add(new CodeAttributeArgument(THEORY_ATTRIBUTE_SKIP_PROPERTY_NAME, new CodePrimitiveExpression(SKIP_REASON))); } + public virtual void SetTestClassCategories(TestClassGenerationContext generationContext, IEnumerable featureCategories) { IEnumerable collection = featureCategories.Where(f => f.StartsWith(COLLECTION_TAG, StringComparison.InvariantCultureIgnoreCase)).ToList(); @@ -158,7 +157,6 @@ public void SetTestClassCollection(TestClassGenerationContext generationContext, CodeDomHelper.AddAttribute(generationContext.TestClass, COLLECTION_DEF, description); } - public virtual void SetTestClassNonParallelizable(TestClassGenerationContext generationContext) { CodeDomHelper.AddAttribute(generationContext.TestClass, COLLECTION_ATTRIBUTE, new CodeAttributeArgument(new CodePrimitiveExpression(NONPARALLELIZABLE_COLLECTION_NAME))); @@ -180,11 +178,24 @@ public virtual void FinalizeTestClass(TestClassGenerationContext generationConte nameof(IObjectContainer.RegisterInstanceAs), new CodeTypeReference(OUTPUT_INTERFACE)), new CodeVariableReferenceExpression(OUTPUT_INTERFACE_FIELD_NAME))); + + // Wrap FeatureTearDown: + // var testWorkerId = .TestWorkerId; + // + // XUnitParallelWorkerTracker.Instance.ReleaseWorker(testWorkerId); + generationContext.TestClassCleanupMethod.Statements.Insert(0, + new CodeVariableDeclarationStatement(typeof(string), "testWorkerId", + new CodePropertyReferenceExpression(new CodeVariableReferenceExpression(generationContext.TestRunnerField.Name), "TestWorkerId"))); + generationContext.TestClassCleanupMethod.Statements.Add( + new CodeMethodInvokeExpression( + new CodeVariableReferenceExpression(XUNITPARALLELWORKERTRACKER_INSTANCE), + "ReleaseWorker", + new CodeVariableReferenceExpression("testWorkerId"))); } protected virtual void IgnoreFeature(TestClassGenerationContext generationContext) { - var featureHasIgnoreTag = generationContext.CustomData.TryGetValue(IGNORE_TEST_CLASS, out var featureHasIgnoreTagValue) && (bool)featureHasIgnoreTagValue == true; + var featureHasIgnoreTag = generationContext.CustomData.TryGetValue(IGNORE_TEST_CLASS, out var featureHasIgnoreTagValue) && (bool)featureHasIgnoreTagValue; if (featureHasIgnoreTag) { @@ -232,13 +243,22 @@ public void SetTestClassInitializeMethod(TestClassGenerationContext generationCo generationContext.TestClass.BaseTypes.Add(_objectCodeTypeReference); generationContext.TestClass.BaseTypes.Add(useFixtureType); - // public <_currentFixtureTypeDeclaration>() { (); } - var ctorMethod = new CodeConstructor { Attributes = MemberAttributes.Public }; - _currentFixtureDataTypeDeclaration.Members.Add(ctorMethod); - ctorMethod.Statements.Add( - new CodeMethodInvokeExpression( - new CodeTypeReferenceExpression(new CodeTypeReference(generationContext.TestClass.Name)), - generationContext.TestClassInitializeMethod.Name)); + // Task IAsyncLifetime.InitializeAsync() { (); } + var initializeMethod = new CodeMemberMethod(); + initializeMethod.PrivateImplementationType = new CodeTypeReference(IASYNCLIFETIME_INTERFACE); + initializeMethod.Name = "InitializeAsync"; + + CodeDomHelper.MarkCodeMemberMethodAsAsync(initializeMethod); + + _currentFixtureDataTypeDeclaration.Members.Add(initializeMethod); + + var expression = new CodeMethodInvokeExpression( + new CodeTypeReferenceExpression(new CodeTypeReference(generationContext.TestClass.Name)), + generationContext.TestClassInitializeMethod.Name); + + CodeDomHelper.MarkCodeMethodInvokeExpressionAsAwait(expression); + + initializeMethod.Statements.Add(expression); } public void SetTestClassCleanupMethod(TestClassGenerationContext generationContext) @@ -247,19 +267,29 @@ public void SetTestClassCleanupMethod(TestClassGenerationContext generationConte generationContext.TestClassCleanupMethod.Attributes |= MemberAttributes.Static; - _currentFixtureDataTypeDeclaration.BaseTypes.Add(typeof(IDisposable)); + var iasyncLifetimeInterface = new CodeTypeReference(IASYNCLIFETIME_INTERFACE); - // void IDisposable.Dispose() { (); } + // have to add the explicit object base class because of VB.NET + _currentFixtureDataTypeDeclaration.BaseTypes.Add(typeof(object)); + _currentFixtureDataTypeDeclaration.BaseTypes.Add(iasyncLifetimeInterface); + // Task IAsyncLifetime.DisposeAsync() { (); } var disposeMethod = new CodeMemberMethod(); - disposeMethod.PrivateImplementationType = new CodeTypeReference(typeof(IDisposable)); - disposeMethod.Name = "Dispose"; + disposeMethod.PrivateImplementationType = iasyncLifetimeInterface; + disposeMethod.Name = "DisposeAsync"; + disposeMethod.ImplementationTypes.Add(iasyncLifetimeInterface); + + CodeDomHelper.MarkCodeMemberMethodAsAsync(disposeMethod); + _currentFixtureDataTypeDeclaration.Members.Add(disposeMethod); - disposeMethod.Statements.Add( - new CodeMethodInvokeExpression( - new CodeTypeReferenceExpression(new CodeTypeReference(generationContext.TestClass.Name)), - generationContext.TestClassCleanupMethod.Name)); + var expression = new CodeMethodInvokeExpression( + new CodeTypeReferenceExpression(new CodeTypeReference(generationContext.TestClass.Name)), + generationContext.TestClassCleanupMethod.Name); + + CodeDomHelper.MarkCodeMethodInvokeExpressionAsAwait(expression); + + disposeMethod.Statements.Add(expression); } public virtual void SetTestMethod(TestClassGenerationContext generationContext, CodeMemberMethod testMethod, string friendlyTestName) @@ -280,7 +310,7 @@ public virtual void SetTestMethodCategories(TestClassGenerationContext generatio public void SetTestInitializeMethod(TestClassGenerationContext generationContext) { - // xUnit uses a parameterless constructor + // xUnit uses a parameterless constructor & IAsyncLifetime.InitializeAsync // public <_currentTestTypeDeclaration>() { (); } @@ -289,25 +319,46 @@ public void SetTestInitializeMethod(TestClassGenerationContext generationContext generationContext.TestClass.Members.Add(ctorMethod); SetTestConstructor(generationContext, ctorMethod); + + // Task IAsyncLifetime.InitializeAsync() { (); } + + var initializeMethod = new CodeMemberMethod(); + initializeMethod.Attributes = MemberAttributes.Public; + initializeMethod.PrivateImplementationType = new CodeTypeReference(IASYNCLIFETIME_INTERFACE); + initializeMethod.Name = "InitializeAsync"; + + CodeDomHelper.MarkCodeMemberMethodAsAsync(initializeMethod); + + generationContext.TestClass.Members.Add(initializeMethod); + + SetTestInitializeMethod(generationContext, initializeMethod); } public void SetTestCleanupMethod(TestClassGenerationContext generationContext) { - // xUnit supports test tear down through the IDisposable interface + // xUnit supports test tear down through the IAsyncLifetime interface + + var iasyncLifetimeInterface = new CodeTypeReference(IASYNCLIFETIME_INTERFACE); - generationContext.TestClass.BaseTypes.Add(typeof(IDisposable)); + generationContext.TestClass.BaseTypes.Add(iasyncLifetimeInterface); - // void IDisposable.Dispose() { (); } + // Task IAsyncLifetime.DisposeAsync() { (); } var disposeMethod = new CodeMemberMethod(); - disposeMethod.PrivateImplementationType = new CodeTypeReference(typeof(IDisposable)); - disposeMethod.Name = "Dispose"; + disposeMethod.PrivateImplementationType = iasyncLifetimeInterface; + disposeMethod.Name = "DisposeAsync"; + + CodeDomHelper.MarkCodeMemberMethodAsAsync(disposeMethod); + generationContext.TestClass.Members.Add(disposeMethod); - disposeMethod.Statements.Add( - new CodeMethodInvokeExpression( - new CodeThisReferenceExpression(), - generationContext.TestCleanupMethod.Name)); + var expression = new CodeMethodInvokeExpression( + new CodeThisReferenceExpression(), + generationContext.TestCleanupMethod.Name); + + CodeDomHelper.MarkCodeMethodInvokeExpressionAsAwait(expression); + + disposeMethod.Statements.Add(expression); } public void SetTestClassIgnore(TestClassGenerationContext generationContext) @@ -357,5 +408,18 @@ public void SetTestMethodAsRow(TestClassGenerationContext generationContext, Cod { // doing nothing since we support RowTest } + + public void MarkCodeMethodInvokeExpressionAsAwait(CodeMethodInvokeExpression expression) + { + CodeDomHelper.MarkCodeMethodInvokeExpressionAsAwait(expression); + } + + public CodeExpression GetTestWorkerIdExpression() + { + // XUnitParallelWorkerTracker.Instance.GetWorkerId() + return new CodeMethodInvokeExpression( + new CodeVariableReferenceExpression(XUNITPARALLELWORKERTRACKER_INSTANCE), + "GetWorkerId"); + } } } \ No newline at end of file diff --git a/TechTalk.SpecFlow.Parser/TechTalk.SpecFlow.Parser.csproj b/TechTalk.SpecFlow.Parser/TechTalk.SpecFlow.Parser.csproj index fd48d0156..83ef920b8 100644 --- a/TechTalk.SpecFlow.Parser/TechTalk.SpecFlow.Parser.csproj +++ b/TechTalk.SpecFlow.Parser/TechTalk.SpecFlow.Parser.csproj @@ -1,4 +1,4 @@ - + $(SpecFlow_Generator_TFM) TechTalk.SpecFlow.Parser diff --git a/TechTalk.SpecFlow/Analytics/AnalyticsTransmitter.cs b/TechTalk.SpecFlow/Analytics/AnalyticsTransmitter.cs index e44b4aafa..450c7d118 100644 --- a/TechTalk.SpecFlow/Analytics/AnalyticsTransmitter.cs +++ b/TechTalk.SpecFlow/Analytics/AnalyticsTransmitter.cs @@ -16,24 +16,24 @@ public AnalyticsTransmitter(IAnalyticsTransmitterSink analyticsTransmitterSink, _environmentSpecFlowTelemetryChecker = environmentSpecFlowTelemetryChecker; } - public Task TransmitSpecFlowProjectCompilingEvent(SpecFlowProjectCompilingEvent projectCompilingEvent) + public async Task TransmitSpecFlowProjectCompilingEventAsync(SpecFlowProjectCompilingEvent projectCompilingEvent) { - return TransmitEvent(projectCompilingEvent); + return await TransmitEventAsync(projectCompilingEvent); } - public Task TransmitSpecFlowProjectRunningEvent(SpecFlowProjectRunningEvent projectRunningEvent) + public async Task TransmitSpecFlowProjectRunningEventAsync(SpecFlowProjectRunningEvent projectRunningEvent) { - return TransmitEvent(projectRunningEvent); + return await TransmitEventAsync(projectRunningEvent); } - public Task TransmitEvent(IAnalyticsEvent analyticsEvent) + public async Task TransmitEventAsync(IAnalyticsEvent analyticsEvent) { if (!IsEnabled) { - return Task.FromResult(Result.Success()); + return Result.Success(); } - return _analyticsTransmitterSink.TransmitEvent(analyticsEvent); + return await _analyticsTransmitterSink.TransmitEventAsync(analyticsEvent); } } } diff --git a/TechTalk.SpecFlow/Analytics/HttpClientAnalyticsTransmitterSink.cs b/TechTalk.SpecFlow/Analytics/HttpClientAnalyticsTransmitterSink.cs index 7183e54f9..3cb963cc8 100644 --- a/TechTalk.SpecFlow/Analytics/HttpClientAnalyticsTransmitterSink.cs +++ b/TechTalk.SpecFlow/Analytics/HttpClientAnalyticsTransmitterSink.cs @@ -20,11 +20,11 @@ public HttpClientAnalyticsTransmitterSink(IAppInsightsEventSerializer appInsight _httpClientWrapper = httpClientWrapper; } - public async Task TransmitEvent(IAnalyticsEvent analyticsEvent, string instrumentationKey) + public async Task TransmitEventAsync(IAnalyticsEvent analyticsEvent, string instrumentationKey) { try { - await TransmitEventAsync(analyticsEvent, instrumentationKey); + await TransmitEventInternalAsync(analyticsEvent, instrumentationKey); return Result.Success(); } catch (Exception e) @@ -35,7 +35,7 @@ public async Task TransmitEvent(IAnalyticsEvent analyticsEvent, string } } - public async Task TransmitEventAsync(IAnalyticsEvent analyticsEvent, string instrumentationKey) + private async Task TransmitEventInternalAsync(IAnalyticsEvent analyticsEvent, string instrumentationKey) { var serializedEventTelemetry = _appInsightsEventSerializer.SerializeAnalyticsEvent(analyticsEvent, instrumentationKey); diff --git a/TechTalk.SpecFlow/Analytics/IAnalyticsTransmitter.cs b/TechTalk.SpecFlow/Analytics/IAnalyticsTransmitter.cs index 5dc135f6a..1e85640d5 100644 --- a/TechTalk.SpecFlow/Analytics/IAnalyticsTransmitter.cs +++ b/TechTalk.SpecFlow/Analytics/IAnalyticsTransmitter.cs @@ -7,7 +7,7 @@ public interface IAnalyticsTransmitter { bool IsEnabled { get; } - Task TransmitSpecFlowProjectCompilingEvent(SpecFlowProjectCompilingEvent projectCompilingEvent); - Task TransmitSpecFlowProjectRunningEvent(SpecFlowProjectRunningEvent projectRunningEvent); + Task TransmitSpecFlowProjectCompilingEventAsync(SpecFlowProjectCompilingEvent projectCompilingEvent); + Task TransmitSpecFlowProjectRunningEventAsync(SpecFlowProjectRunningEvent projectRunningEvent); } } diff --git a/TechTalk.SpecFlow/Analytics/IAnalyticsTransmitterSink.cs b/TechTalk.SpecFlow/Analytics/IAnalyticsTransmitterSink.cs index a312d81d2..975201dfe 100644 --- a/TechTalk.SpecFlow/Analytics/IAnalyticsTransmitterSink.cs +++ b/TechTalk.SpecFlow/Analytics/IAnalyticsTransmitterSink.cs @@ -6,6 +6,6 @@ namespace TechTalk.SpecFlow.Analytics { public interface IAnalyticsTransmitterSink { - Task TransmitEvent(IAnalyticsEvent analyticsEvent, string instrumentationKey = AppInsightsInstrumentationKey.Key); + Task TransmitEventAsync(IAnalyticsEvent analyticsEvent, string instrumentationKey = AppInsightsInstrumentationKey.Key); } } diff --git a/TechTalk.SpecFlow/Assist/InstanceComparisonExtensionMethods.cs b/TechTalk.SpecFlow/Assist/InstanceComparisonExtensionMethods.cs index 00025a32e..c08bd58cf 100644 --- a/TechTalk.SpecFlow/Assist/InstanceComparisonExtensionMethods.cs +++ b/TechTalk.SpecFlow/Assist/InstanceComparisonExtensionMethods.cs @@ -131,7 +131,7 @@ public PropertyDoesNotExist(string propertyName) } public override string Description => - $"{this.propertyName}: Property does not exist"; + $"{propertyName}: Property does not exist"; } private class PropertyDiffers : Difference diff --git a/TechTalk.SpecFlow/Bindings/AsyncHelpers.cs b/TechTalk.SpecFlow/Bindings/AsyncHelpers.cs deleted file mode 100644 index 41f111ada..000000000 --- a/TechTalk.SpecFlow/Bindings/AsyncHelpers.cs +++ /dev/null @@ -1,151 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.ExceptionServices; -using System.Threading; -using System.Threading.Tasks; - -namespace TechTalk.SpecFlow.Bindings -{ - /// - /// https://stackoverflow.com/questions/5095183/how-would-i-run-an-async-taskt-method-synchronously#answer-5097066 - /// - public static class AsyncHelpers - { - /// - /// Execute's an async method which has a void return value synchronously - /// - /// method to execute - public static void RunSync(Func task) - { - var synch = new ExclusiveSynchronizationContext(); - using (new SetSynchronizationContext(synch)) - { - synch.Post(async _ => - { - try - { - await task(); - } - catch (Exception e) - { - synch.InnerExceptionDispatchInfo = ExceptionDispatchInfo.Capture(e); - throw; - } - finally - { - synch.EndMessageLoop(); - } - }, null); - synch.BeginMessageLoop(); - } - } - - /// - /// Execute's an async method which has a T return type synchronously - /// - /// Return Type - /// method to execute - /// - public static T RunSync(Func> task) - { - var synch = new ExclusiveSynchronizationContext(); - using (new SetSynchronizationContext(synch)) - { - T ret = default(T); - synch.Post(async _ => - { - try - { - ret = await task(); - } - catch (Exception e) - { - synch.InnerExceptionDispatchInfo = ExceptionDispatchInfo.Capture(e); - throw; - } - finally - { - synch.EndMessageLoop(); - } - }, null); - synch.BeginMessageLoop(); - return ret; - } - } - - private class ExclusiveSynchronizationContext : SynchronizationContext - { - private bool done; - public ExceptionDispatchInfo InnerExceptionDispatchInfo { get; set; } - private readonly AutoResetEvent workItemsWaiting = new(false); - private readonly Queue> items = new(); - - public override void Send(SendOrPostCallback d, object state) - { - throw new NotSupportedException("We cannot send to our same thread"); - } - - public override void Post(SendOrPostCallback d, object state) - { - lock (items) - { - items.Enqueue(Tuple.Create(d, state)); - } - workItemsWaiting.Set(); - } - - public void EndMessageLoop() - { - Post(_ => done = true, null); - } - - public void BeginMessageLoop() - { - while (!done) - { - Tuple task = null; - lock (items) - { - if (items.Count > 0) - { - task = items.Dequeue(); - } - } - if (task != null) - { - task.Item1(task.Item2); - if (InnerExceptionDispatchInfo != null) // the method threw an exeption - { - InnerExceptionDispatchInfo.Throw(); - } - } - else - { - workItemsWaiting.WaitOne(); - } - } - } - - public override SynchronizationContext CreateCopy() - { - return this; - } - } - - private sealed class SetSynchronizationContext: IDisposable - { - private readonly SynchronizationContext _oldContext; - - public SetSynchronizationContext(SynchronizationContext synchronizationContext) - { - _oldContext = SynchronizationContext.Current; - SynchronizationContext.SetSynchronizationContext(synchronizationContext); - } - - public void Dispose() - { - SynchronizationContext.SetSynchronizationContext(_oldContext); - } - } - } -} diff --git a/TechTalk.SpecFlow/Bindings/BindingDelegateInvoker.cs b/TechTalk.SpecFlow/Bindings/BindingDelegateInvoker.cs new file mode 100644 index 000000000..cbd822d0a --- /dev/null +++ b/TechTalk.SpecFlow/Bindings/BindingDelegateInvoker.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading.Tasks; + +namespace TechTalk.SpecFlow.Bindings +{ + public class BindingDelegateInvoker : IBindingDelegateInvoker + { + public virtual async Task InvokeDelegateAsync(Delegate bindingDelegate, object[] invokeArgs) + { + if (typeof(Task).IsAssignableFrom(bindingDelegate.Method.ReturnType)) + return await InvokeBindingDelegateAsync(bindingDelegate, invokeArgs); + return InvokeBindingDelegateSync(bindingDelegate, invokeArgs); + } + + protected virtual object InvokeBindingDelegateSync(Delegate bindingDelegate, object[] invokeArgs) + { + return bindingDelegate.DynamicInvoke(invokeArgs); + } + + protected virtual async Task InvokeBindingDelegateAsync(Delegate bindingDelegate, object[] invokeArgs) + { + var r = bindingDelegate.DynamicInvoke(invokeArgs); + if (r is Task t) + await t; + return r; + } + } +} diff --git a/TechTalk.SpecFlow/Bindings/BindingInvoker.cs b/TechTalk.SpecFlow/Bindings/BindingInvoker.cs index 557ffdd07..81b83fcc1 100644 --- a/TechTalk.SpecFlow/Bindings/BindingInvoker.cs +++ b/TechTalk.SpecFlow/Bindings/BindingInvoker.cs @@ -4,28 +4,41 @@ 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; using TechTalk.SpecFlow.ErrorHandling; using TechTalk.SpecFlow.Infrastructure; using TechTalk.SpecFlow.Tracing; namespace TechTalk.SpecFlow.Bindings { - public class BindingInvoker : IBindingInvoker +#pragma warning disable CS0618 + public class BindingInvoker : IBindingInvoker, IAsyncBindingInvoker +#pragma warning restore CS0618 { - protected readonly Configuration.SpecFlowConfiguration specFlowConfiguration; + protected readonly SpecFlowConfiguration specFlowConfiguration; protected readonly IErrorProvider errorProvider; - protected readonly ISynchronousBindingDelegateInvoker synchronousBindingDelegateInvoker; + protected readonly IBindingDelegateInvoker bindingDelegateInvoker; - public BindingInvoker(Configuration.SpecFlowConfiguration specFlowConfiguration, IErrorProvider errorProvider, ISynchronousBindingDelegateInvoker synchronousBindingDelegateInvoker) + public BindingInvoker(SpecFlowConfiguration specFlowConfiguration, IErrorProvider errorProvider, IBindingDelegateInvoker bindingDelegateInvoker) { this.specFlowConfiguration = specFlowConfiguration; this.errorProvider = errorProvider; - this.synchronousBindingDelegateInvoker = synchronousBindingDelegateInvoker; + this.bindingDelegateInvoker = bindingDelegateInvoker; } + [Obsolete("Use async version of the method instead")] public virtual object InvokeBinding(IBinding binding, IContextManager contextManager, object[] arguments, ITestTracer testTracer, out TimeSpan duration) + { + var durationHolder = new DurationHolder(); + var result = InvokeBindingAsync(binding, contextManager, arguments, testTracer, durationHolder).ConfigureAwait(false).GetAwaiter().GetResult(); + duration = durationHolder.Duration; + return result; + } + + public virtual async Task InvokeBindingAsync(IBinding binding, IContextManager contextManager, object[] arguments, ITestTracer testTracer, DurationHolder durationHolder) { EnsureReflectionInfo(binding, out _, out var bindingAction); Stopwatch stopwatch = new Stopwatch(); @@ -40,10 +53,10 @@ public virtual object InvokeBinding(IBinding binding, IContextManager contextMan Array.Copy(arguments, 0, invokeArgs, 1, arguments.Length); invokeArgs[0] = contextManager; - result = synchronousBindingDelegateInvoker - .InvokeDelegateSynchronously(bindingAction, invokeArgs); + result = await bindingDelegateInvoker.InvokeDelegateAsync(bindingAction, invokeArgs); stopwatch.Stop(); + durationHolder.Duration = stopwatch.Elapsed; } if (specFlowConfiguration.TraceTimings && stopwatch.Elapsed >= specFlowConfiguration.MinTracedDuration) @@ -51,13 +64,12 @@ public virtual object InvokeBinding(IBinding binding, IContextManager contextMan testTracer.TraceDuration(stopwatch.Elapsed, binding.Method, arguments); } - duration = stopwatch.Elapsed; return result; } catch (ArgumentException ex) { stopwatch.Stop(); - duration = stopwatch.Elapsed; + durationHolder.Duration = stopwatch.Elapsed; throw errorProvider.GetCallError(binding.Method, ex); } catch (TargetInvocationException invEx) @@ -65,7 +77,7 @@ public virtual object InvokeBinding(IBinding binding, IContextManager contextMan var ex = invEx.InnerException; ex = ex.PreserveStackTrace(errorProvider.GetMethodText(binding.Method)); stopwatch.Stop(); - duration = stopwatch.Elapsed; + durationHolder.Duration = stopwatch.Elapsed; throw ex; } catch (AggregateException aggregateEx) @@ -73,13 +85,13 @@ public virtual object InvokeBinding(IBinding binding, IContextManager contextMan var ex = aggregateEx.InnerExceptions.First(); ex = ex.PreserveStackTrace(errorProvider.GetMethodText(binding.Method)); stopwatch.Stop(); - duration = stopwatch.Elapsed; + durationHolder.Duration = stopwatch.Elapsed; throw ex; } catch (Exception) { stopwatch.Stop(); - duration = stopwatch.Elapsed; + durationHolder.Duration = stopwatch.Elapsed; throw; } } diff --git a/TechTalk.SpecFlow/Bindings/BindingRegistry.cs b/TechTalk.SpecFlow/Bindings/BindingRegistry.cs index 78147e6b7..7afd03287 100644 --- a/TechTalk.SpecFlow/Bindings/BindingRegistry.cs +++ b/TechTalk.SpecFlow/Bindings/BindingRegistry.cs @@ -3,21 +3,6 @@ namespace TechTalk.SpecFlow.Bindings { - public interface IBindingRegistry - { - bool Ready { get; set; } - - IEnumerable GetStepDefinitions(); - IEnumerable GetHooks(); - IEnumerable GetConsideredStepDefinitions(StepDefinitionType stepDefinitionType, string stepText = null); - IEnumerable GetHooks(HookType bindingEvent); - IEnumerable GetStepTransformations(); - - void RegisterStepDefinitionBinding(IStepDefinitionBinding stepDefinitionBinding); - void RegisterHookBinding(IHookBinding hookBinding); - void RegisterStepArgumentTransformationBinding(IStepArgumentTransformationBinding stepArgumentTransformationBinding); - } - public class BindingRegistry : IBindingRegistry { private readonly List stepDefinitions = new List(); diff --git a/TechTalk.SpecFlow/Bindings/DurationHolder.cs b/TechTalk.SpecFlow/Bindings/DurationHolder.cs new file mode 100644 index 000000000..bd44f1087 --- /dev/null +++ b/TechTalk.SpecFlow/Bindings/DurationHolder.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TechTalk.SpecFlow.Bindings +{ + public class DurationHolder + { + public TimeSpan Duration { get; set; } + } +} diff --git a/TechTalk.SpecFlow/Bindings/IAsyncBindingInvoker.cs b/TechTalk.SpecFlow/Bindings/IAsyncBindingInvoker.cs new file mode 100644 index 000000000..3ccb412c7 --- /dev/null +++ b/TechTalk.SpecFlow/Bindings/IAsyncBindingInvoker.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using TechTalk.SpecFlow.Infrastructure; +using TechTalk.SpecFlow.Tracing; + +namespace TechTalk.SpecFlow.Bindings; + +public interface IAsyncBindingInvoker +{ + Task InvokeBindingAsync(IBinding binding, IContextManager contextManager, object[] arguments, ITestTracer testTracer, DurationHolder durationHolder); +} diff --git a/TechTalk.SpecFlow/Bindings/IBindingDelegateInvoker.cs b/TechTalk.SpecFlow/Bindings/IBindingDelegateInvoker.cs new file mode 100644 index 000000000..bbc4b6fd7 --- /dev/null +++ b/TechTalk.SpecFlow/Bindings/IBindingDelegateInvoker.cs @@ -0,0 +1,10 @@ +using System; +using System.Threading.Tasks; + +namespace TechTalk.SpecFlow.Bindings +{ + public interface IBindingDelegateInvoker + { + Task InvokeDelegateAsync(Delegate bindingDelegate, object[] invokeArgs); + } +} \ No newline at end of file diff --git a/TechTalk.SpecFlow/Bindings/IBindingInvoker.cs b/TechTalk.SpecFlow/Bindings/IBindingInvoker.cs index 83e353578..f4134a0d8 100644 --- a/TechTalk.SpecFlow/Bindings/IBindingInvoker.cs +++ b/TechTalk.SpecFlow/Bindings/IBindingInvoker.cs @@ -4,8 +4,10 @@ namespace TechTalk.SpecFlow.Bindings { + [Obsolete("Use async version of the interface (IAsyncBindingInvoker) whenever you can")] public interface IBindingInvoker { + [Obsolete("Use async version of the method of IAsyncBindingInvoker instead")] object InvokeBinding(IBinding binding, IContextManager contextManager, object[] arguments, ITestTracer testTracer, out TimeSpan duration); } } \ No newline at end of file diff --git a/TechTalk.SpecFlow/Bindings/IBindingRegistry.cs b/TechTalk.SpecFlow/Bindings/IBindingRegistry.cs new file mode 100644 index 000000000..106c3eee1 --- /dev/null +++ b/TechTalk.SpecFlow/Bindings/IBindingRegistry.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; + +namespace TechTalk.SpecFlow.Bindings +{ + public interface IBindingRegistry + { + bool Ready { get; set; } + + IEnumerable GetStepDefinitions(); + IEnumerable GetHooks(); + IEnumerable GetConsideredStepDefinitions(StepDefinitionType stepDefinitionType, string stepText = null); + IEnumerable GetHooks(HookType bindingEvent); + IEnumerable GetStepTransformations(); + + void RegisterStepDefinitionBinding(IStepDefinitionBinding stepDefinitionBinding); + void RegisterHookBinding(IHookBinding hookBinding); + void RegisterStepArgumentTransformationBinding(IStepArgumentTransformationBinding stepArgumentTransformationBinding); + } +} diff --git a/TechTalk.SpecFlow/Bindings/IStepArgumentTypeConverter.cs b/TechTalk.SpecFlow/Bindings/IStepArgumentTypeConverter.cs new file mode 100644 index 000000000..fa5caadf6 --- /dev/null +++ b/TechTalk.SpecFlow/Bindings/IStepArgumentTypeConverter.cs @@ -0,0 +1,12 @@ +using System.Globalization; +using System.Threading.Tasks; +using TechTalk.SpecFlow.Bindings.Reflection; + +namespace TechTalk.SpecFlow.Bindings +{ + public interface IStepArgumentTypeConverter + { + Task ConvertAsync(object value, IBindingType typeToConvertTo, CultureInfo cultureInfo); + bool CanConvert(object value, IBindingType typeToConvertTo, CultureInfo cultureInfo); + } +} diff --git a/TechTalk.SpecFlow/Bindings/StepArgumentTypeConverter.cs b/TechTalk.SpecFlow/Bindings/StepArgumentTypeConverter.cs index a122be843..e349f5a77 100644 --- a/TechTalk.SpecFlow/Bindings/StepArgumentTypeConverter.cs +++ b/TechTalk.SpecFlow/Bindings/StepArgumentTypeConverter.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Linq; using System.Text.RegularExpressions; +using System.Threading.Tasks; using TechTalk.SpecFlow.Assist.ValueRetrievers; using TechTalk.SpecFlow.Bindings.Reflection; using TechTalk.SpecFlow.Infrastructure; @@ -10,20 +11,14 @@ namespace TechTalk.SpecFlow.Bindings { - public interface IStepArgumentTypeConverter - { - object Convert(object value, IBindingType typeToConvertTo, CultureInfo cultureInfo); - bool CanConvert(object value, IBindingType typeToConvertTo, CultureInfo cultureInfo); - } - public class StepArgumentTypeConverter : IStepArgumentTypeConverter { private readonly ITestTracer testTracer; private readonly IBindingRegistry bindingRegistry; private readonly IContextManager contextManager; - private readonly IBindingInvoker bindingInvoker; + private readonly IAsyncBindingInvoker bindingInvoker; - public StepArgumentTypeConverter(ITestTracer testTracer, IBindingRegistry bindingRegistry, IContextManager contextManager, IBindingInvoker bindingInvoker) + public StepArgumentTypeConverter(ITestTracer testTracer, IBindingRegistry bindingRegistry, IContextManager contextManager, IAsyncBindingInvoker bindingInvoker) { this.testTracer = testTracer; this.bindingRegistry = bindingRegistry; @@ -42,13 +37,13 @@ protected virtual IStepArgumentTransformationBinding GetMatchingStepTransformati return stepTransformations.Length > 0 ? stepTransformations[0] : null; } - public object Convert(object value, IBindingType typeToConvertTo, CultureInfo cultureInfo) + public async Task ConvertAsync(object value, IBindingType typeToConvertTo, CultureInfo cultureInfo) { if (value == null) throw new ArgumentNullException(nameof(value)); var stepTransformation = GetMatchingStepTransformation(value, typeToConvertTo, true); if (stepTransformation != null) - return DoTransform(stepTransformation, value, cultureInfo); + return await DoTransformAsync(stepTransformation, value, cultureInfo); if (typeToConvertTo is RuntimeBindingType convertToType && convertToType.Type.IsInstanceOfType(value)) return value; @@ -56,25 +51,33 @@ public object Convert(object value, IBindingType typeToConvertTo, CultureInfo cu return ConvertSimple(typeToConvertTo, value, cultureInfo); } - private object DoTransform(IStepArgumentTransformationBinding stepTransformation, object value, CultureInfo cultureInfo) + private async Task DoTransformAsync(IStepArgumentTransformationBinding stepTransformation, object value, CultureInfo cultureInfo) { object[] arguments; if (stepTransformation.Regex != null && value is string stringValue) - arguments = GetStepTransformationArgumentsFromRegex(stepTransformation, stringValue, cultureInfo); + arguments = await GetStepTransformationArgumentsFromRegexAsync(stepTransformation, stringValue, cultureInfo); else arguments = new[] {value}; - return bindingInvoker.InvokeBinding(stepTransformation, contextManager, arguments, testTracer, out _); + var result = await bindingInvoker.InvokeBindingAsync(stepTransformation, contextManager, arguments, testTracer, new DurationHolder()); + + return result; } - private object[] GetStepTransformationArgumentsFromRegex(IStepArgumentTransformationBinding stepTransformation, string stepSnippet, CultureInfo cultureInfo) + private async Task GetStepTransformationArgumentsFromRegexAsync(IStepArgumentTransformationBinding stepTransformation, string stepSnippet, CultureInfo cultureInfo) { var match = stepTransformation.Regex.Match(stepSnippet); - var argumentStrings = match.Groups.Cast().Skip(1).Select(g => g.Value); + var argumentStrings = match.Groups.Cast().Skip(1).Select(g => g.Value).ToList(); var bindingParameters = stepTransformation.Method.Parameters.ToArray(); - return argumentStrings - .Select((arg, argIndex) => this.Convert(arg, bindingParameters[argIndex].Type, cultureInfo)) - .ToArray(); + + var result = new object[argumentStrings.Count]; + + for (int i = 0; i < argumentStrings.Count; i++) + { + result[i] = await ConvertAsync(argumentStrings[i], bindingParameters[i].Type, cultureInfo); + } + + return result; } public bool CanConvert(object value, IBindingType typeToConvertTo, CultureInfo cultureInfo) diff --git a/TechTalk.SpecFlow/Bindings/SynchronousBindingDelegateInvoker.cs b/TechTalk.SpecFlow/Bindings/SynchronousBindingDelegateInvoker.cs deleted file mode 100644 index 681a4ed78..000000000 --- a/TechTalk.SpecFlow/Bindings/SynchronousBindingDelegateInvoker.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace TechTalk.SpecFlow.Bindings -{ - public interface ISynchronousBindingDelegateInvoker - { - object InvokeDelegateSynchronously(Delegate bindingDelegate, object[] invokeArgs); - } - - public class SynchronousBindingDelegateInvoker : ISynchronousBindingDelegateInvoker - { - public virtual object InvokeDelegateSynchronously(Delegate bindingDelegate, object[] invokeArgs) - { - if (typeof(Task).IsAssignableFrom(bindingDelegate.Method.ReturnType)) - return InvokeBindingDelegateAsync(bindingDelegate, invokeArgs); - return InvokeBindingDelegateSync(bindingDelegate, invokeArgs); - } - - protected virtual object InvokeBindingDelegateSync(Delegate bindingDelegate, object[] invokeArgs) - { - return bindingDelegate.DynamicInvoke(invokeArgs); - } - - protected virtual object InvokeBindingDelegateAsync(Delegate bindingDelegate, object[] invokeArgs) - { - return AsyncHelpers.RunSync(async () => - { - var r = bindingDelegate.DynamicInvoke(invokeArgs); - if (r is Task t) - await t; - return r; - }); - } - - } -} diff --git a/TechTalk.SpecFlow/ISyncTestRunner.cs b/TechTalk.SpecFlow/ISyncTestRunner.cs new file mode 100644 index 000000000..9baa6b2c3 --- /dev/null +++ b/TechTalk.SpecFlow/ISyncTestRunner.cs @@ -0,0 +1,37 @@ +using System; + +namespace TechTalk.SpecFlow +{ + public interface ISyncTestRunner + { + /// + /// The ID of the parallel test worker processing the current scenario. How the worker ID is obtained is dependent on the test execution framework. + /// + string TestWorkerId { get; } + + FeatureContext FeatureContext { get; } + ScenarioContext ScenarioContext { get; } + + void OnTestRunStart(); + void OnTestRunEnd(); + + void OnFeatureStart(FeatureInfo featureInfo); + void OnFeatureEnd(); + + void OnScenarioInitialize(ScenarioInfo scenarioInfo); + void OnScenarioStart(); + + void CollectScenarioErrors(); + void OnScenarioEnd(); + + void SkipScenario(); + + void Given(string text, string multilineTextArg, Table tableArg, string keyword = null); + void When(string text, string multilineTextArg, Table tableArg, string keyword = null); + void Then(string text, string multilineTextArg, Table tableArg, string keyword = null); + void And(string text, string multilineTextArg, Table tableArg, string keyword = null); + void But(string text, string multilineTextArg, Table tableArg, string keyword = null); + + void Pending(); + } +} diff --git a/TechTalk.SpecFlow/ITestRunner.cs b/TechTalk.SpecFlow/ITestRunner.cs index 956cb85c0..76eb71e20 100644 --- a/TechTalk.SpecFlow/ITestRunner.cs +++ b/TechTalk.SpecFlow/ITestRunner.cs @@ -1,61 +1,38 @@ +using System.Threading.Tasks; + namespace TechTalk.SpecFlow { public interface ITestRunner { - int ThreadId { get; } + /// + /// The ID of the parallel test worker processing the current scenario. How the worker ID is obtained is dependent on the test execution framework. + /// + string TestWorkerId { get; } FeatureContext FeatureContext { get; } ScenarioContext ScenarioContext { get; } - void InitializeTestRunner(int threadId); + void InitializeTestRunner(string testWorkerId); - void OnTestRunStart(); - void OnTestRunEnd(); + Task OnTestRunStartAsync(); + Task OnTestRunEndAsync(); - void OnFeatureStart(FeatureInfo featureInfo); - void OnFeatureEnd(); + Task OnFeatureStartAsync(FeatureInfo featureInfo); + Task OnFeatureEndAsync(); void OnScenarioInitialize(ScenarioInfo scenarioInfo); - void OnScenarioStart(); + Task OnScenarioStartAsync(); - void CollectScenarioErrors(); - void OnScenarioEnd(); + Task CollectScenarioErrorsAsync(); + Task OnScenarioEndAsync(); void SkipScenario(); - void Given(string text, string multilineTextArg, Table tableArg, string keyword = null); - void When(string text, string multilineTextArg, Table tableArg, string keyword = null); - void Then(string text, string multilineTextArg, Table tableArg, string keyword = null); - void And(string text, string multilineTextArg, Table tableArg, string keyword = null); - void But(string text, string multilineTextArg, Table tableArg, string keyword = null); + Task GivenAsync(string text, string multilineTextArg, Table tableArg, string keyword = null); + Task WhenAsync(string text, string multilineTextArg, Table tableArg, string keyword = null); + Task ThenAsync(string text, string multilineTextArg, Table tableArg, string keyword = null); + Task AndAsync(string text, string multilineTextArg, Table tableArg, string keyword = null); + Task ButAsync(string text, string multilineTextArg, Table tableArg, string keyword = null); void Pending(); } - - public static class TestRunnerDefaultArguments - { - public static void Given(this ITestRunner testRunner, string text) - { - testRunner.Given(text, null, null, null); - } - - public static void When(this ITestRunner testRunner, string text) - { - testRunner.When(text, null, null, null); - } - - public static void Then(this ITestRunner testRunner, string text) - { - testRunner.Then(text, null, null, null); - } - - public static void And(this ITestRunner testRunner, string text) - { - testRunner.And(text, null, null, null); - } - - public static void But(this ITestRunner testRunner, string text) - { - testRunner.But(text, null, null, null); - } - } } diff --git a/TechTalk.SpecFlow/ITestRunnerManager.cs b/TechTalk.SpecFlow/ITestRunnerManager.cs new file mode 100644 index 000000000..bed54d935 --- /dev/null +++ b/TechTalk.SpecFlow/ITestRunnerManager.cs @@ -0,0 +1,19 @@ +using System; +using System.Reflection; +using System.Threading.Tasks; + +namespace TechTalk.SpecFlow +{ + // This class does not implement IAsyncDisposable, because that is only supported properly in .NET Standard 2.1, so won't work with .NET 4.6.1. The 'Microsoft.Bcl.AsyncInterfaces' package that overcomes this causes other issues, see https://github.com/simpleinjector/SimpleInjector/issues/867 + public interface ITestRunnerManager + { + Assembly TestAssembly { get; } + Assembly[] BindingAssemblies { get; } + bool IsMultiThreaded { get; } + ITestRunner GetTestRunner(string workerId); + void Initialize(Assembly testAssembly); + Task FireTestRunEndAsync(); + Task FireTestRunStartAsync(); + Task DisposeAsync(); + } +} diff --git a/TechTalk.SpecFlow/Infrastructure/BlockingSyncTestRunner.cs b/TechTalk.SpecFlow/Infrastructure/BlockingSyncTestRunner.cs new file mode 100644 index 000000000..de5f189c5 --- /dev/null +++ b/TechTalk.SpecFlow/Infrastructure/BlockingSyncTestRunner.cs @@ -0,0 +1,97 @@ +using System; +using System.Threading.Tasks; + +namespace TechTalk.SpecFlow.Infrastructure; + +public class BlockingSyncTestRunner : ISyncTestRunner +{ + private readonly ITestRunner _testRunner; + + public BlockingSyncTestRunner(ITestRunner testRunner) + { + _testRunner = testRunner; + } + private void SyncWrapper(Func asyncCall) + { + asyncCall().GetAwaiter().GetResult(); + } + + public string TestWorkerId => _testRunner.TestWorkerId; + public FeatureContext FeatureContext => _testRunner.FeatureContext; + public ScenarioContext ScenarioContext => _testRunner.ScenarioContext; + + public void OnScenarioInitialize(ScenarioInfo scenarioInfo) + { + _testRunner.OnScenarioInitialize(scenarioInfo); + } + + public void SkipScenario() + { + _testRunner.SkipScenario(); + } + + public void Pending() + { + _testRunner.Pending(); + } + + public void OnTestRunStart() + { + SyncWrapper(() => _testRunner.OnTestRunStartAsync()); + } + + public void OnTestRunEnd() + { + SyncWrapper(() => _testRunner.OnTestRunEndAsync()); + } + + public void OnFeatureStart(FeatureInfo featureInfo) + { + SyncWrapper(() => _testRunner.OnFeatureStartAsync(featureInfo)); + } + + public void OnFeatureEnd() + { + SyncWrapper(() => _testRunner.OnFeatureEndAsync()); + } + + public void OnScenarioStart() + { + SyncWrapper(() => _testRunner.OnScenarioStartAsync()); + } + + public void CollectScenarioErrors() + { + SyncWrapper(() => _testRunner.CollectScenarioErrorsAsync()); + } + + public void OnScenarioEnd() + { + SyncWrapper(() => _testRunner.OnScenarioEndAsync()); + } + + public void Given(string text, string multilineTextArg, Table tableArg, string keyword = null) + { + SyncWrapper(() => _testRunner.GivenAsync(text, multilineTextArg, tableArg, keyword)); + } + + public void When(string text, string multilineTextArg, Table tableArg, string keyword = null) + { + SyncWrapper(() => _testRunner.WhenAsync(text, multilineTextArg, tableArg, keyword)); + } + + public void Then(string text, string multilineTextArg, Table tableArg, string keyword = null) + { + SyncWrapper(() => _testRunner.ThenAsync(text, multilineTextArg, tableArg, keyword)); + } + + public void And(string text, string multilineTextArg, Table tableArg, string keyword = null) + { + SyncWrapper(() => _testRunner.AndAsync(text, multilineTextArg, tableArg, keyword)); + } + + public void But(string text, string multilineTextArg, Table tableArg, string keyword = null) + { + SyncWrapper(() => _testRunner.ButAsync(text, multilineTextArg, tableArg, keyword)); + } +} diff --git a/TechTalk.SpecFlow/Infrastructure/ContextManager.cs b/TechTalk.SpecFlow/Infrastructure/ContextManager.cs index 246e88db5..5d5a4fdf5 100644 --- a/TechTalk.SpecFlow/Infrastructure/ContextManager.cs +++ b/TechTalk.SpecFlow/Infrastructure/ContextManager.cs @@ -123,7 +123,7 @@ public void Reset() private readonly IContainerBuilder containerBuilder; /// - /// Holds the StepDefinitionType of the last step that was executed from the actual featrure file, excluding the types of the steps that were executed during the calling of a step + /// Holds the StepDefinitionType of the last step that was executed from the actual feature file, excluding the types of the steps that were executed during the calling of a step /// public StepDefinitionType? CurrentTopLevelStepDefinitionType { get; private set; } @@ -159,7 +159,7 @@ private void InitializeTestThreadContext() { // Since both TestThreadContext and ContextManager are in the same container (test thread container) // their lifetime is the same, so we do not need the swop infrastructure like for the other contexts. - // We just neet to initliaze it during contstructuion time. + // We just need to initialize it during construction time. var testThreadContext = testThreadContainer.Resolve(); this.TestThreadContext = testThreadContext; } diff --git a/TechTalk.SpecFlow/Infrastructure/DefaultDependencyProvider.cs b/TechTalk.SpecFlow/Infrastructure/DefaultDependencyProvider.cs index 231485f33..4ef51dff2 100644 --- a/TechTalk.SpecFlow/Infrastructure/DefaultDependencyProvider.cs +++ b/TechTalk.SpecFlow/Infrastructure/DefaultDependencyProvider.cs @@ -40,8 +40,11 @@ public virtual void RegisterGlobalContainerDefaults(ObjectContainer container) container.RegisterTypeAs(); container.RegisterTypeAs(); container.RegisterTypeAs(); +#pragma warning disable CS0618 container.RegisterTypeAs(); - container.RegisterTypeAs(); +#pragma warning restore CS0618 + container.RegisterTypeAs(); + container.RegisterTypeAs(); container.RegisterTypeAs(); container.RegisterTypeAs(); @@ -91,6 +94,7 @@ public virtual void RegisterGlobalContainerDefaults(ObjectContainer container) public virtual void RegisterTestThreadContainerDefaults(ObjectContainer testThreadContainer) { testThreadContainer.RegisterTypeAs(); + testThreadContainer.RegisterTypeAs(); testThreadContainer.RegisterTypeAs(); testThreadContainer.RegisterTypeAs(); diff --git a/TechTalk.SpecFlow/Infrastructure/ITestExecutionEngine.cs b/TechTalk.SpecFlow/Infrastructure/ITestExecutionEngine.cs index ba9b8c8f6..0d3f7b3f2 100644 --- a/TechTalk.SpecFlow/Infrastructure/ITestExecutionEngine.cs +++ b/TechTalk.SpecFlow/Infrastructure/ITestExecutionEngine.cs @@ -1,4 +1,5 @@ -using TechTalk.SpecFlow.Bindings; +using System.Threading.Tasks; +using TechTalk.SpecFlow.Bindings; namespace TechTalk.SpecFlow.Infrastructure { @@ -7,20 +8,20 @@ public interface ITestExecutionEngine FeatureContext FeatureContext { get; } ScenarioContext ScenarioContext { get; } - void OnTestRunStart(); - void OnTestRunEnd(); + Task OnTestRunStartAsync(); + Task OnTestRunEndAsync(); - void OnFeatureStart(FeatureInfo featureInfo); - void OnFeatureEnd(); + Task OnFeatureStartAsync(FeatureInfo featureInfo); + Task OnFeatureEndAsync(); void OnScenarioInitialize(ScenarioInfo scenarioInfo); - void OnScenarioStart(); - void OnAfterLastStep(); - void OnScenarioEnd(); + Task OnScenarioStartAsync(); + Task OnAfterLastStepAsync(); + Task OnScenarioEndAsync(); void OnScenarioSkipped(); - void Step(StepDefinitionKeyword stepDefinitionKeyword, string keyword, string text, string multilineTextArg, Table tableArg); + Task StepAsync(StepDefinitionKeyword stepDefinitionKeyword, string keyword, string text, string multilineTextArg, Table tableArg); void Pending(); } diff --git a/TechTalk.SpecFlow/Infrastructure/TestExecutionEngine.cs b/TechTalk.SpecFlow/Infrastructure/TestExecutionEngine.cs index 79c4f6d28..f83c41129 100644 --- a/TechTalk.SpecFlow/Infrastructure/TestExecutionEngine.cs +++ b/TechTalk.SpecFlow/Infrastructure/TestExecutionEngine.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Linq; +using System.Threading.Tasks; using BoDi; using TechTalk.SpecFlow.Analytics; using TechTalk.SpecFlow.Bindings; @@ -17,7 +18,7 @@ namespace TechTalk.SpecFlow.Infrastructure { public class TestExecutionEngine : ITestExecutionEngine { - private readonly IBindingInvoker _bindingInvoker; + private readonly IAsyncBindingInvoker _bindingInvoker; private readonly IBindingRegistry _bindingRegistry; private readonly IContextManager _contextManager; private readonly IErrorProvider _errorProvider; @@ -51,7 +52,7 @@ public class TestExecutionEngine : ITestExecutionEngine IUnitTestRuntimeProvider unitTestRuntimeProvider, IContextManager contextManager, IStepDefinitionMatchService stepDefinitionMatchService, - IBindingInvoker bindingInvoker, + IAsyncBindingInvoker bindingInvoker, IObsoleteStepHandler obsoleteStepHandler, IAnalyticsEventProvider analyticsEventProvider, IAnalyticsTransmitter analyticsTransmitter, @@ -89,7 +90,7 @@ public class TestExecutionEngine : ITestExecutionEngine public ScenarioContext ScenarioContext => _contextManager.ScenarioContext; - public virtual void OnTestRunStart() + public virtual async Task OnTestRunStartAsync() { if (_testRunnerStartExecuted) { @@ -102,7 +103,7 @@ public virtual void OnTestRunStart() { var testAssemblyName = _testRunnerManager.TestAssembly.GetName().Name; var projectRunningEvent = _analyticsEventProvider.CreateProjectRunningEvent(testAssemblyName); - _analyticsTransmitter.TransmitSpecFlowProjectRunningEvent(projectRunningEvent); + await _analyticsTransmitter.TransmitSpecFlowProjectRunningEventAsync(projectRunningEvent); } catch (Exception) { @@ -114,10 +115,10 @@ public virtual void OnTestRunStart() _testThreadExecutionEventPublisher.PublishEvent(new TestRunStartedEvent()); - FireEvents(HookType.BeforeTestRun); + await FireEventsAsync(HookType.BeforeTestRun); } - public virtual void OnTestRunEnd() + public virtual async Task OnTestRunEndAsync() { lock (_testRunnerEndExecutedLock) { @@ -129,19 +130,19 @@ public virtual void OnTestRunEnd() _testRunnerEndExecuted = true; } - FireEvents(HookType.AfterTestRun); - + await FireEventsAsync(HookType.AfterTestRun); + _testThreadExecutionEventPublisher.PublishEvent(new TestRunFinishedEvent()); } - public virtual void OnFeatureStart(FeatureInfo featureInfo) + public virtual async Task OnFeatureStartAsync(FeatureInfo featureInfo) { // if the unit test provider would execute the fixture teardown code // only delayed (at the end of the execution), we automatically close // the current feature if necessary if (_unitTestRuntimeProvider.DelayedFixtureTearDown && FeatureContext != null) { - OnFeatureEnd(); + await OnFeatureEndAsync(); } @@ -149,10 +150,10 @@ public virtual void OnFeatureStart(FeatureInfo featureInfo) _testThreadExecutionEventPublisher.PublishEvent(new FeatureStartedEvent(FeatureContext)); - FireEvents(HookType.BeforeFeature); + await FireEventsAsync(HookType.BeforeFeature); } - public virtual void OnFeatureEnd() + public virtual async Task OnFeatureEndAsync() { // if the unit test provider would execute the fixture teardown code // only delayed (at the end of the execution), we ignore the @@ -161,7 +162,7 @@ public virtual void OnFeatureEnd() FeatureContext == null) return; - FireEvents(HookType.AfterFeature); + await FireEventsAsync(HookType.AfterFeature); if (_specFlowConfiguration.TraceTimings) { @@ -180,13 +181,13 @@ public virtual void OnScenarioInitialize(ScenarioInfo scenarioInfo) _contextManager.InitializeScenarioContext(scenarioInfo); } - public virtual void OnScenarioStart() + public virtual async Task OnScenarioStartAsync() { _testThreadExecutionEventPublisher.PublishEvent(new ScenarioStartedEvent(FeatureContext, ScenarioContext)); try { - FireScenarioEvents(HookType.BeforeScenario); + await FireScenarioEventsAsync(HookType.BeforeScenario); } catch (Exception ex) { @@ -198,9 +199,9 @@ public virtual void OnScenarioStart() } } - public virtual void OnAfterLastStep() + public virtual async Task OnAfterLastStepAsync() { - HandleBlockSwitch(ScenarioBlock.None); + await HandleBlockSwitchAsync(ScenarioBlock.None); if (_specFlowConfiguration.TraceTimings) { @@ -234,13 +235,13 @@ public virtual void OnAfterLastStep() throw _contextManager.ScenarioContext.TestError; } - public virtual void OnScenarioEnd() + public virtual async Task OnScenarioEndAsync() { try { if (_contextManager.ScenarioContext.ScenarioExecutionStatus != ScenarioExecutionStatus.Skipped) { - FireScenarioEvents(HookType.AfterScenario); + await FireScenarioEventsAsync(HookType.AfterScenario); } _testThreadExecutionEventPublisher.PublishEvent(new ScenarioFinishedEvent(FeatureContext, ScenarioContext)); } @@ -265,31 +266,32 @@ public virtual void Pending() throw _errorProvider.GetPendingStepDefinitionError(); } - protected virtual void OnBlockStart(ScenarioBlock block) + protected virtual async Task OnBlockStartAsync(ScenarioBlock block) { if (block == ScenarioBlock.None) return; - FireScenarioEvents(HookType.BeforeScenarioBlock); + await FireScenarioEventsAsync(HookType.BeforeScenarioBlock); } - protected virtual void OnBlockEnd(ScenarioBlock block) + protected virtual async Task OnBlockEndAsync(ScenarioBlock block) { if (block == ScenarioBlock.None) return; - FireScenarioEvents(HookType.AfterScenarioBlock); + await FireScenarioEventsAsync(HookType.AfterScenarioBlock); } - protected virtual void OnStepStart() + protected virtual async Task OnStepStartAsync() { - FireScenarioEvents(HookType.BeforeStep); + await FireScenarioEventsAsync(HookType.BeforeStep); } - protected virtual void OnStepEnd() + protected virtual async Task OnStepEndAsync() { - FireScenarioEvents(HookType.AfterStep); + await FireScenarioEventsAsync(HookType.AfterStep); } + protected virtual void OnSkipStep() { _contextManager.StepContext.Status = ScenarioExecutionStatus.Skipped; @@ -306,12 +308,12 @@ protected virtual void OnSkipStep() #region Step/event execution - protected virtual void FireScenarioEvents(HookType bindingEvent) + protected virtual async Task FireScenarioEventsAsync(HookType bindingEvent) { - FireEvents(bindingEvent); + await FireEventsAsync(bindingEvent); } - private void FireEvents(HookType hookType) + private async Task FireEventsAsync(HookType hookType) { _testThreadExecutionEventPublisher.PublishEvent(new HookStartedEvent(hookType, FeatureContext, ScenarioContext, _contextManager.StepContext)); var stepContext = _contextManager.GetStepContext(); @@ -332,9 +334,9 @@ private void FireEvents(HookType hookType) //Note: if a (user-)hook throws an exception the subsequent hooks of the same type are not executed foreach (var hookBinding in uniqueMatchingHooks.OrderBy(x => x.HookOrder)) { - InvokeHook(_bindingInvoker, hookBinding, hookType); + await InvokeHookAsync(_bindingInvoker, hookBinding, hookType); } - } + } catch (Exception hookExceptionCaught) { hookException = hookExceptionCaught; @@ -360,22 +362,21 @@ private void FireRuntimePluginTestExecutionLifecycleEvents(HookType hookType) protected IObjectContainer TestThreadContainer { get; } - public virtual void InvokeHook(IBindingInvoker invoker, IHookBinding hookBinding, HookType hookType) + public virtual async Task InvokeHookAsync(IAsyncBindingInvoker invoker, IHookBinding hookBinding, HookType hookType) { var currentContainer = GetHookContainer(hookType); var arguments = ResolveArguments(hookBinding, currentContainer); _testThreadExecutionEventPublisher.PublishEvent(new HookBindingStartedEvent(hookBinding)); - TimeSpan duration = default; + var durationHolder = new DurationHolder(); try { - - invoker.InvokeBinding(hookBinding, _contextManager, arguments, _testTracer, out duration); + await invoker.InvokeBindingAsync(hookBinding, _contextManager, arguments, _testTracer, durationHolder); } finally { - _testThreadExecutionEventPublisher.PublishEvent(new HookBindingFinishedEvent(hookBinding, duration)); + _testThreadExecutionEventPublisher.PublishEvent(new HookBindingFinishedEvent(hookBinding, durationHolder.Duration)); } } @@ -448,9 +449,9 @@ private object ResolveArgument(IObjectContainer container, IBindingParameter par } - private void ExecuteStep(IContextManager contextManager, StepInstance stepInstance) + private async Task ExecuteStepAsync(IContextManager contextManager, StepInstance stepInstance) { - HandleBlockSwitch(stepInstance.StepDefinitionType.ToScenarioBlock()); + await HandleBlockSwitchAsync(stepInstance.StepDefinitionType.ToScenarioBlock()); _testTracer.TraceStep(stepInstance, true); @@ -459,7 +460,7 @@ private void ExecuteStep(IContextManager contextManager, StepInstance stepInstan BindingMatch match = null; object[] arguments = null; - TimeSpan duration = TimeSpan.Zero; + var durationHolder = new DurationHolder(); try { match = GetStepMatch(stepInstance); @@ -472,14 +473,14 @@ private void ExecuteStep(IContextManager contextManager, StepInstance stepInstan } else { - arguments = GetExecuteArguments(match); + arguments = await GetExecuteArgumentsAsync(match); _obsoleteStepHandler.Handle(match); onStepStartExecuted = true; - OnStepStart(); - ExecuteStepMatch(match, arguments, out duration); + await OnStepStartAsync(); + await ExecuteStepMatchAsync(match, arguments, durationHolder); if (_specFlowConfiguration.TraceSuccessfulSteps) - _testTracer.TraceStepDone(match, arguments, duration); + _testTracer.TraceStepDone(match, arguments, durationHolder.Duration); } } catch (PendingStepException) @@ -505,7 +506,7 @@ private void ExecuteStep(IContextManager contextManager, StepInstance stepInstan } catch (Exception ex) { - _testTracer.TraceError(ex, duration); + _testTracer.TraceError(ex, durationHolder.Duration); UpdateStatusOnStepFailure(ScenarioExecutionStatus.TestError, ex); @@ -516,7 +517,7 @@ private void ExecuteStep(IContextManager contextManager, StepInstance stepInstan { if (onStepStartExecuted) { - OnStepEnd(); + await OnStepEndAsync(); } } } @@ -557,22 +558,21 @@ protected virtual BindingMatch GetStepMatch(StepInstance stepInstance) throw _errorProvider.GetMissingStepDefinitionError(); } - protected virtual void ExecuteStepMatch(BindingMatch match, object[] arguments, out TimeSpan duration) + protected virtual async Task ExecuteStepMatchAsync(BindingMatch match, object[] arguments, DurationHolder durationHolder) { _testThreadExecutionEventPublisher.PublishEvent(new StepBindingStartedEvent(match.StepBinding)); - duration = default; - + try { - _bindingInvoker.InvokeBinding(match.StepBinding, _contextManager, arguments, _testTracer, out duration); + await _bindingInvoker.InvokeBindingAsync(match.StepBinding, _contextManager, arguments, _testTracer, durationHolder); } finally { - _testThreadExecutionEventPublisher.PublishEvent(new StepBindingFinishedEvent(match.StepBinding, duration)); + _testThreadExecutionEventPublisher.PublishEvent(new StepBindingFinishedEvent(match.StepBinding, durationHolder.Duration)); } } - private void HandleBlockSwitch(ScenarioBlock block) + private async Task HandleBlockSwitchAsync(ScenarioBlock block) { if (_contextManager == null) { @@ -587,41 +587,44 @@ private void HandleBlockSwitch(ScenarioBlock block) if (_contextManager.ScenarioContext.CurrentScenarioBlock != block) { if (_contextManager.ScenarioContext.ScenarioExecutionStatus == ScenarioExecutionStatus.OK) - OnBlockEnd(_contextManager.ScenarioContext.CurrentScenarioBlock); + await OnBlockEndAsync(_contextManager.ScenarioContext.CurrentScenarioBlock); _contextManager.ScenarioContext.CurrentScenarioBlock = block; if (_contextManager.ScenarioContext.ScenarioExecutionStatus == ScenarioExecutionStatus.OK) - OnBlockStart(_contextManager.ScenarioContext.CurrentScenarioBlock); + await OnBlockStartAsync(_contextManager.ScenarioContext.CurrentScenarioBlock); } } - private object[] GetExecuteArguments(BindingMatch match) + private async Task GetExecuteArgumentsAsync(BindingMatch match) { var bindingParameters = match.StepBinding.Method.Parameters.ToArray(); if (match.Arguments.Length != bindingParameters.Length) throw _errorProvider.GetParameterCountError(match, match.Arguments.Length); - var arguments = match.Arguments.Select( - (arg, argIndex) => ConvertArg(arg, bindingParameters[argIndex].Type)) - .ToArray(); + var arguments = new object[match.Arguments.Length]; + + for (var i = 0; i < match.Arguments.Length; i++) + { + arguments[i] = await ConvertArg(match.Arguments[i], bindingParameters[i].Type); + } return arguments; } - private object ConvertArg(object value, IBindingType typeToConvertTo) + private async Task ConvertArg(object value, IBindingType typeToConvertTo) { Debug.Assert(value != null); Debug.Assert(typeToConvertTo != null); - return _stepArgumentTypeConverter.Convert(value, typeToConvertTo, FeatureContext.BindingCulture); + return await _stepArgumentTypeConverter.ConvertAsync(value, typeToConvertTo, FeatureContext.BindingCulture); } #endregion #region Given-When-Then - public virtual void Step(StepDefinitionKeyword stepDefinitionKeyword, string keyword, string text, string multilineTextArg, Table tableArg) + public virtual async Task StepAsync(StepDefinitionKeyword stepDefinitionKeyword, string keyword, string text, string multilineTextArg, Table tableArg) { StepDefinitionType stepDefinitionType = stepDefinitionKeyword == StepDefinitionKeyword.And || stepDefinitionKeyword == StepDefinitionKeyword.But ? GetCurrentBindingType() @@ -632,7 +635,7 @@ public virtual void Step(StepDefinitionKeyword stepDefinitionKeyword, string key try { var stepInstance = new StepInstance(stepDefinitionType, stepDefinitionKeyword, keyword, text, multilineTextArg, tableArg, _contextManager.GetStepContext()); - ExecuteStep(_contextManager, stepInstance); + await ExecuteStepAsync(_contextManager, stepInstance); } finally { diff --git a/TechTalk.SpecFlow/Steps.cs b/TechTalk.SpecFlow/Steps.cs index 703e993d6..f3e3b5f5e 100644 --- a/TechTalk.SpecFlow/Steps.cs +++ b/TechTalk.SpecFlow/Steps.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using System.Threading.Tasks; using BoDi; using TechTalk.SpecFlow.Infrastructure; @@ -7,11 +8,11 @@ namespace TechTalk.SpecFlow { public abstract class Steps : IContainerDependentObject { - private const string GivenObsoleteMessage = nameof(Steps) + "." + nameof(Given) + " is obsolete and will be removed with SpecFlow 4.0. Details: https://github.com/techtalk/SpecFlow/issues/1733"; - private const string WhenObsoleteMessage = nameof(Steps) + "." + nameof(When) + " is obsolete and will be removed with SpecFlow 4.0. Details: https://github.com/techtalk/SpecFlow/issues/1733"; - private const string ThenObsoleteMessage = nameof(Steps) + "." + nameof(Then) + " is obsolete and will be removed with SpecFlow 4.0. Details: https://github.com/techtalk/SpecFlow/issues/1733"; - private const string ButObsoleteMessage = nameof(Steps) + "." + nameof(But) + " is obsolete and will be removed with SpecFlow 4.0. Details: https://github.com/techtalk/SpecFlow/issues/1733"; - private const string AndObsoleteMessage = nameof(Steps) + "." + nameof(And) + " is obsolete and will be removed with SpecFlow 4.0. Details: https://github.com/techtalk/SpecFlow/issues/1733"; + private const string GivenObsoleteMessage = $"{nameof(Steps)}.{nameof(Given)} and {nameof(Steps)}.{nameof(GivenAsync)} are obsolete and will be removed with SpecFlow 4.0. Details: https://github.com/techtalk/SpecFlow/issues/1733"; + private const string WhenObsoleteMessage = $"{nameof(Steps)}.{nameof(When)} and {nameof(Steps)}.{nameof(WhenAsync)} are obsolete and will be removed with SpecFlow 4.0. Details: https://github.com/techtalk/SpecFlow/issues/1733"; + private const string ThenObsoleteMessage = $"{nameof(Steps)}.{nameof(Then)} and {nameof(Steps)}.{nameof(ThenAsync)} are obsolete and will be removed with SpecFlow 4.0. Details: https://github.com/techtalk/SpecFlow/issues/1733"; + private const string ButObsoleteMessage = $"{nameof(Steps)}.{nameof(And)} and {nameof(Steps)}.{nameof(AndAsync)} are obsolete and will be removed with SpecFlow 4.0. Details: https://github.com/techtalk/SpecFlow/issues/1733"; + private const string AndObsoleteMessage = $"{nameof(Steps)}.{nameof(But)} and {nameof(Steps)}.{nameof(ButAsync)} are obsolete and will be removed with SpecFlow 4.0. Details: https://github.com/techtalk/SpecFlow/issues/1733"; private IObjectContainer objectContainer; void IContainerDependentObject.SetObjectContainer(IObjectContainer container) @@ -31,6 +32,15 @@ protected ITestRunner TestRunner } } + protected ISyncTestRunner SyncTestRunner + { + get + { + AssertInitialized(); + return objectContainer.Resolve(); + } + } + public ScenarioContext ScenarioContext { get @@ -74,6 +84,156 @@ protected void AssertInitialized() throw new SpecFlowException("Container of the steps class has not been initialized!"); } + #region GivenAsync + [Obsolete(GivenObsoleteMessage)] + [EditorBrowsable(EditorBrowsableState.Never)] + public async Task GivenAsync(string step) + { + await GivenAsync(step, null, null); + } + + [Obsolete(GivenObsoleteMessage)] + [EditorBrowsable(EditorBrowsableState.Never)] + public async Task GivenAsync(string step, Table tableArg) + { + await GivenAsync(step, null, tableArg); + } + + [Obsolete(GivenObsoleteMessage)] + [EditorBrowsable(EditorBrowsableState.Never)] + public async Task GivenAsync(string step, string multilineTextArg) + { + await GivenAsync(step, multilineTextArg, null); + } + + [Obsolete(GivenObsoleteMessage)] + [EditorBrowsable(EditorBrowsableState.Never)] + public async Task GivenAsync(string step, string multilineTextArg, Table tableArg) + { + await TestRunner.GivenAsync(step, multilineTextArg, tableArg); + } + #endregion + + #region WhenAsync + [Obsolete(WhenObsoleteMessage)] + [EditorBrowsable(EditorBrowsableState.Never)] + public async Task WhenAsync(string step) + { + await WhenAsync(step, null, null); + } + + [Obsolete(WhenObsoleteMessage)] + [EditorBrowsable(EditorBrowsableState.Never)] + public async Task WhenAsync(string step, Table tableArg) + { + await WhenAsync(step, null, tableArg); + } + + [Obsolete(WhenObsoleteMessage)] + [EditorBrowsable(EditorBrowsableState.Never)] + public async Task WhenAsync(string step, string multilineTextArg) + { + await WhenAsync(step, multilineTextArg, null); + } + + [Obsolete(WhenObsoleteMessage)] + [EditorBrowsable(EditorBrowsableState.Never)] + public async Task WhenAsync(string step, string multilineTextArg, Table tableArg) + { + await TestRunner.WhenAsync(step, multilineTextArg, tableArg); + } + #endregion + + #region ThenAsync + [Obsolete(ThenObsoleteMessage)] + [EditorBrowsable(EditorBrowsableState.Never)] + public async Task ThenAsync(string step) + { + await ThenAsync(step, null, null); + } + + [Obsolete(ThenObsoleteMessage)] + [EditorBrowsable(EditorBrowsableState.Never)] + public async Task ThenAsync(string step, Table tableArg) + { + await ThenAsync(step, null, tableArg); + } + + [Obsolete(ThenObsoleteMessage)] + [EditorBrowsable(EditorBrowsableState.Never)] + public async Task ThenAsync(string step, string multilineTextArg) + { + await ThenAsync(step, multilineTextArg, null); + } + + [Obsolete(ThenObsoleteMessage)] + [EditorBrowsable(EditorBrowsableState.Never)] + public async Task ThenAsync(string step, string multilineTextArg, Table tableArg) + { + await TestRunner.ThenAsync(step, multilineTextArg, tableArg); + } + #endregion + + #region ButAsync + [Obsolete(ButObsoleteMessage)] + [EditorBrowsable(EditorBrowsableState.Never)] + public async Task ButAsync(string step) + { + await ButAsync(step, null, null); + } + + [Obsolete(ButObsoleteMessage)] + [EditorBrowsable(EditorBrowsableState.Never)] + public async Task ButAsync(string step, Table tableArg) + { + await ButAsync(step, null, tableArg); + } + + [Obsolete(ButObsoleteMessage)] + [EditorBrowsable(EditorBrowsableState.Never)] + public async Task ButAsync(string step, string multilineTextArg) + { + await ButAsync(step, multilineTextArg, null); + } + + [Obsolete(ButObsoleteMessage)] + [EditorBrowsable(EditorBrowsableState.Never)] + public async Task ButAsync(string step, string multilineTextArg, Table tableArg) + { + await TestRunner.ButAsync(step, multilineTextArg, tableArg); + } + #endregion + + #region AndAsync + [Obsolete(AndObsoleteMessage)] + [EditorBrowsable(EditorBrowsableState.Never)] + public async Task AndAsync(string step) + { + await AndAsync(step, null, null); + } + + [Obsolete(AndObsoleteMessage)] + [EditorBrowsable(EditorBrowsableState.Never)] + public async Task AndAsync(string step, Table tableArg) + { + await AndAsync(step, null, tableArg); + } + + [Obsolete(AndObsoleteMessage)] + [EditorBrowsable(EditorBrowsableState.Never)] + public async Task AndAsync(string step, string multilineTextArg) + { + await AndAsync(step, multilineTextArg, null); + } + + [Obsolete(AndObsoleteMessage)] + [EditorBrowsable(EditorBrowsableState.Never)] + public async Task AndAsync(string step, string multilineTextArg, Table tableArg) + { + await TestRunner.AndAsync(step, multilineTextArg, tableArg); + } + #endregion + #region Given [Obsolete(GivenObsoleteMessage)] [EditorBrowsable(EditorBrowsableState.Never)] @@ -100,7 +260,7 @@ public void Given(string step, string multilineTextArg) [EditorBrowsable(EditorBrowsableState.Never)] public void Given(string step, string multilineTextArg, Table tableArg) { - TestRunner.Given(step, multilineTextArg, tableArg, null); + SyncTestRunner.Given(step, multilineTextArg, tableArg); } #endregion @@ -130,7 +290,7 @@ public void When(string step, string multilineTextArg) [EditorBrowsable(EditorBrowsableState.Never)] public void When(string step, string multilineTextArg, Table tableArg) { - TestRunner.When(step, multilineTextArg, tableArg, null); + SyncTestRunner.When(step, multilineTextArg, tableArg); } #endregion @@ -160,7 +320,7 @@ public void Then(string step, string multilineTextArg) [EditorBrowsable(EditorBrowsableState.Never)] public void Then(string step, string multilineTextArg, Table tableArg) { - TestRunner.Then(step, multilineTextArg, tableArg, null); + SyncTestRunner.Then(step, multilineTextArg, tableArg); } #endregion @@ -190,7 +350,7 @@ public void But(string step, string multilineTextArg) [EditorBrowsable(EditorBrowsableState.Never)] public void But(string step, string multilineTextArg, Table tableArg) { - TestRunner.But(step, multilineTextArg, tableArg, null); + SyncTestRunner.But(step, multilineTextArg, tableArg); } #endregion @@ -220,7 +380,7 @@ public void And(string step, string multilineTextArg) [EditorBrowsable(EditorBrowsableState.Never)] public void And(string step, string multilineTextArg, Table tableArg) { - TestRunner.And(step, multilineTextArg, tableArg, null); + SyncTestRunner.And(step, multilineTextArg, tableArg); } #endregion } diff --git a/TechTalk.SpecFlow/TestRunner.cs b/TechTalk.SpecFlow/TestRunner.cs index a9e249851..76fa979bf 100644 --- a/TechTalk.SpecFlow/TestRunner.cs +++ b/TechTalk.SpecFlow/TestRunner.cs @@ -1,4 +1,5 @@ -using TechTalk.SpecFlow.Bindings; +using System.Threading.Tasks; +using TechTalk.SpecFlow.Bindings; using TechTalk.SpecFlow.Infrastructure; namespace TechTalk.SpecFlow @@ -7,7 +8,7 @@ public class TestRunner : ITestRunner { private readonly ITestExecutionEngine _executionEngine; - public int ThreadId { get; private set; } + public string TestWorkerId { get; private set; } public TestRunner(ITestExecutionEngine executionEngine) { @@ -24,24 +25,24 @@ public ScenarioContext ScenarioContext get { return _executionEngine.ScenarioContext; } } - public void OnTestRunStart() + public async Task OnTestRunStartAsync() { - _executionEngine.OnTestRunStart(); + await _executionEngine.OnTestRunStartAsync(); } - public void InitializeTestRunner(int threadId) + public void InitializeTestRunner(string testWorkerId) { - ThreadId = threadId; + TestWorkerId = testWorkerId; } - public void OnFeatureStart(FeatureInfo featureInfo) + public async Task OnFeatureStartAsync(FeatureInfo featureInfo) { - _executionEngine.OnFeatureStart(featureInfo); + await _executionEngine.OnFeatureStartAsync(featureInfo); } - public void OnFeatureEnd() + public async Task OnFeatureEndAsync() { - _executionEngine.OnFeatureEnd(); + await _executionEngine.OnFeatureEndAsync(); } public void OnScenarioInitialize(ScenarioInfo scenarioInfo) @@ -49,19 +50,19 @@ public void OnScenarioInitialize(ScenarioInfo scenarioInfo) _executionEngine.OnScenarioInitialize(scenarioInfo); } - public void OnScenarioStart() + public async Task OnScenarioStartAsync() { - _executionEngine.OnScenarioStart(); + await _executionEngine.OnScenarioStartAsync(); } - public void CollectScenarioErrors() + public async Task CollectScenarioErrorsAsync() { - _executionEngine.OnAfterLastStep(); + await _executionEngine.OnAfterLastStepAsync(); } - public void OnScenarioEnd() + public async Task OnScenarioEndAsync() { - _executionEngine.OnScenarioEnd(); + await _executionEngine.OnScenarioEndAsync(); } public void SkipScenario() @@ -69,34 +70,34 @@ public void SkipScenario() _executionEngine.OnScenarioSkipped(); } - public void OnTestRunEnd() + public async Task OnTestRunEndAsync() { - _executionEngine.OnTestRunEnd(); + await _executionEngine.OnTestRunEndAsync(); } - public void Given(string text, string multilineTextArg, Table tableArg, string keyword = null) + public async Task GivenAsync(string text, string multilineTextArg, Table tableArg, string keyword = null) { - _executionEngine.Step(StepDefinitionKeyword.Given, keyword, text, multilineTextArg, tableArg); + await _executionEngine.StepAsync(StepDefinitionKeyword.Given, keyword, text, multilineTextArg, tableArg); } - public void When(string text, string multilineTextArg, Table tableArg, string keyword = null) + public async Task WhenAsync(string text, string multilineTextArg, Table tableArg, string keyword = null) { - _executionEngine.Step(StepDefinitionKeyword.When, keyword, text, multilineTextArg, tableArg); + await _executionEngine.StepAsync(StepDefinitionKeyword.When, keyword, text, multilineTextArg, tableArg); } - public void Then(string text, string multilineTextArg, Table tableArg, string keyword = null) + public async Task ThenAsync(string text, string multilineTextArg, Table tableArg, string keyword = null) { - _executionEngine.Step(StepDefinitionKeyword.Then, keyword, text, multilineTextArg, tableArg); + await _executionEngine.StepAsync(StepDefinitionKeyword.Then, keyword, text, multilineTextArg, tableArg); } - public void And(string text, string multilineTextArg, Table tableArg, string keyword = null) + public async Task AndAsync(string text, string multilineTextArg, Table tableArg, string keyword = null) { - _executionEngine.Step(StepDefinitionKeyword.And, keyword, text, multilineTextArg, tableArg); + await _executionEngine.StepAsync(StepDefinitionKeyword.And, keyword, text, multilineTextArg, tableArg); } - public void But(string text, string multilineTextArg, Table tableArg, string keyword = null) + public async Task ButAsync(string text, string multilineTextArg, Table tableArg, string keyword = null) { - _executionEngine.Step(StepDefinitionKeyword.But, keyword, text, multilineTextArg, tableArg); + await _executionEngine.StepAsync(StepDefinitionKeyword.But, keyword, text, multilineTextArg, tableArg); } public void Pending() diff --git a/TechTalk.SpecFlow/TestRunnerDefaultArguments.cs b/TechTalk.SpecFlow/TestRunnerDefaultArguments.cs new file mode 100644 index 000000000..abee7e31f --- /dev/null +++ b/TechTalk.SpecFlow/TestRunnerDefaultArguments.cs @@ -0,0 +1,32 @@ +using System.Threading.Tasks; + +namespace TechTalk.SpecFlow +{ + public static class TestRunnerDefaultArguments + { + public static async Task GivenAsync(this ITestRunner testRunner, string text) + { + await testRunner.GivenAsync(text, null, null, null); + } + + public static async Task WhenAsync(this ITestRunner testRunner, string text) + { + await testRunner.WhenAsync(text, null, null, null); + } + + public static async Task ThenAsync(this ITestRunner testRunner, string text) + { + await testRunner.ThenAsync(text, null, null, null); + } + + public static async Task AndAsync(this ITestRunner testRunner, string text) + { + await testRunner.AndAsync(text, null, null, null); + } + + public static async Task ButAsync(this ITestRunner testRunner, string text) + { + await testRunner.ButAsync(text, null, null, null); + } + } +} diff --git a/TechTalk.SpecFlow/TestRunnerManager.cs b/TechTalk.SpecFlow/TestRunnerManager.cs index 20c65906c..0b7c420bb 100644 --- a/TechTalk.SpecFlow/TestRunnerManager.cs +++ b/TechTalk.SpecFlow/TestRunnerManager.cs @@ -1,65 +1,71 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using System.Threading; +using System.Threading.Tasks; using BoDi; using TechTalk.SpecFlow.Bindings.Discovery; +using TechTalk.SpecFlow.Configuration; using TechTalk.SpecFlow.Infrastructure; using TechTalk.SpecFlow.Tracing; namespace TechTalk.SpecFlow { - public interface ITestRunnerManager : IDisposable - { - Assembly TestAssembly { get; } - Assembly[] BindingAssemblies { get; } - bool IsMultiThreaded { get; } - ITestRunner GetTestRunner(int threadId); - void Initialize(Assembly testAssembly); - void FireTestRunEnd(); - void FireTestRunStart(); - } - public class TestRunnerManager : ITestRunnerManager { - protected readonly IObjectContainer globalContainer; - protected readonly IContainerBuilder containerBuilder; - protected readonly Configuration.SpecFlowConfiguration specFlowConfiguration; - protected readonly IRuntimeBindingRegistryBuilder bindingRegistryBuilder; - - private readonly ITestTracer testTracer; - private readonly Dictionary testRunnerRegistry = new Dictionary(); - private readonly object syncRoot = new object(); + public const string TestRunStartWorkerId = "TestRunStart"; + + protected readonly IObjectContainer _globalContainer; + protected readonly IContainerBuilder _containerBuilder; + protected readonly SpecFlowConfiguration _specFlowConfiguration; + protected readonly IRuntimeBindingRegistryBuilder _bindingRegistryBuilder; + protected readonly ITestTracer _testTracer; + + private readonly ConcurrentDictionary _testRunnerRegistry = new(); public bool IsTestRunInitialized { get; private set; } - private object disposeLockObj = null; + private int _wasDisposed = 0; + private int _wasSingletonInstanceDisabled = 0; + private readonly object createTestRunnerLockObject = new(); public Assembly TestAssembly { get; private set; } public Assembly[] BindingAssemblies { get; private set; } - public bool IsMultiThreaded { get { return testRunnerRegistry.Count > 1; } } + public bool IsMultiThreaded => GetWorkerTestRunnerCount() > 1; - public TestRunnerManager(IObjectContainer globalContainer, IContainerBuilder containerBuilder, Configuration.SpecFlowConfiguration specFlowConfiguration, IRuntimeBindingRegistryBuilder bindingRegistryBuilder, + public TestRunnerManager(IObjectContainer globalContainer, IContainerBuilder containerBuilder, SpecFlowConfiguration specFlowConfiguration, IRuntimeBindingRegistryBuilder bindingRegistryBuilder, ITestTracer testTracer) { - this.globalContainer = globalContainer; - this.containerBuilder = containerBuilder; - this.specFlowConfiguration = specFlowConfiguration; - this.bindingRegistryBuilder = bindingRegistryBuilder; - this.testTracer = testTracer; + _globalContainer = globalContainer; + _containerBuilder = containerBuilder; + _specFlowConfiguration = specFlowConfiguration; + _bindingRegistryBuilder = bindingRegistryBuilder; + _testTracer = testTracer; + } + + private int GetWorkerTestRunnerCount() + { + var hasTestRunStartWorker = _testRunnerRegistry.ContainsKey(TestRunStartWorkerId); + return _testRunnerRegistry.Count - (hasTestRunStartWorker ? 1 : 0); } - public virtual ITestRunner CreateTestRunner(int threadId) + public virtual ITestRunner CreateTestRunner(string testWorkerId = "default-worker") { var testRunner = CreateTestRunnerInstance(); - testRunner.InitializeTestRunner(threadId); + testRunner.InitializeTestRunner(testWorkerId); - lock (this) + if (!IsTestRunInitialized) { - if (!IsTestRunInitialized) + lock (createTestRunnerLockObject) { - InitializeBindingRegistry(testRunner); - IsTestRunInitialized = true; + if (!IsTestRunInitialized) + { + InitializeBindingRegistry(testRunner); + IsTestRunInitialized = true; + } } } @@ -71,18 +77,22 @@ protected virtual void InitializeBindingRegistry(ITestRunner testRunner) BindingAssemblies = GetBindingAssemblies(); BuildBindingRegistry(BindingAssemblies); - EventHandler domainUnload = delegate { OnDomainUnload(); }; - AppDomain.CurrentDomain.DomainUnload += domainUnload; - AppDomain.CurrentDomain.ProcessExit += domainUnload; + void DomainUnload(object sender, EventArgs e) + { + OnDomainUnloadAsync().Wait(); + } + + AppDomain.CurrentDomain.DomainUnload += DomainUnload; + AppDomain.CurrentDomain.ProcessExit += DomainUnload; } protected virtual Assembly[] GetBindingAssemblies() { var bindingAssemblies = new List { TestAssembly }; - var assemblyLoader = globalContainer.Resolve(); + var assemblyLoader = _globalContainer.Resolve(); bindingAssemblies.AddRange( - specFlowConfiguration.AdditionalStepAssemblies.Select(assemblyLoader.Load)); + _specFlowConfiguration.AdditionalStepAssemblies.Select(assemblyLoader.Load)); return bindingAssemblies.ToArray(); } @@ -90,35 +100,35 @@ protected virtual void BuildBindingRegistry(IEnumerable bindingAssembl { foreach (Assembly assembly in bindingAssemblies) { - bindingRegistryBuilder.BuildBindingsFromAssembly(assembly); + _bindingRegistryBuilder.BuildBindingsFromAssembly(assembly); } - bindingRegistryBuilder.BuildingCompleted(); + _bindingRegistryBuilder.BuildingCompleted(); } - protected internal virtual void OnDomainUnload() + protected internal virtual async Task OnDomainUnloadAsync() { - Dispose(); + await DisposeAsync(); } - public void FireTestRunEnd() + public async Task FireTestRunEndAsync() { // this method must not be called multiple times - var onTestRunnerEndExecutionHost = testRunnerRegistry.Values.FirstOrDefault(); + var onTestRunnerEndExecutionHost = _testRunnerRegistry.Values.FirstOrDefault(); if (onTestRunnerEndExecutionHost != null) - onTestRunnerEndExecutionHost.OnTestRunEnd(); + await onTestRunnerEndExecutionHost.OnTestRunEndAsync(); } - public void FireTestRunStart() + public async Task FireTestRunStartAsync() { // this method must not be called multiple times - var onTestRunnerEndExecutionHost = testRunnerRegistry.Values.FirstOrDefault(); - if (onTestRunnerEndExecutionHost != null) - onTestRunnerEndExecutionHost.OnTestRunStart(); + var onTestRunnerStartExecutionHost = _testRunnerRegistry.Values.FirstOrDefault(); + if (onTestRunnerStartExecutionHost != null) + await onTestRunnerStartExecutionHost.OnTestRunStartAsync(); } protected virtual ITestRunner CreateTestRunnerInstance() { - var testThreadContainer = containerBuilder.CreateTestThreadContainer(globalContainer); + var testThreadContainer = _containerBuilder.CreateTestThreadContainer(_globalContainer); return testThreadContainer.Resolve(); } @@ -128,84 +138,108 @@ public void Initialize(Assembly assignedTestAssembly) TestAssembly = assignedTestAssembly; } - public virtual ITestRunner GetTestRunner(int threadId) + public virtual ITestRunner GetTestRunner(string testWorkerId) { + testWorkerId ??= Guid.NewGuid().ToString(); //Creates a Test Runner with a unique test thread try { - return GetTestRunnerWithoutExceptionHandling(threadId); - + return GetTestRunnerWithoutExceptionHandling(testWorkerId); } catch (Exception ex) { - testTracer.TraceError(ex,TimeSpan.Zero); + _testTracer.TraceError(ex,TimeSpan.Zero); throw; } } - private ITestRunner GetTestRunnerWithoutExceptionHandling(int threadId) + private ITestRunner GetTestRunnerWithoutExceptionHandling(string testWorkerId) { - if (!testRunnerRegistry.TryGetValue(threadId, out var testRunner)) - { - lock (syncRoot) + if (testWorkerId == null) + throw new ArgumentNullException(nameof(testWorkerId)); + + bool wasAdded = false; + + var testRunner = _testRunnerRegistry.GetOrAdd( + testWorkerId, + workerId => { - if (!testRunnerRegistry.TryGetValue(threadId, out testRunner)) - { - testRunner = CreateTestRunner(threadId); - testRunnerRegistry.Add(threadId, testRunner); - - if (IsMultiThreaded) - { - FeatureContext.DisableSingletonInstance(); - ScenarioContext.DisableSingletonInstance(); - ScenarioStepContext.DisableSingletonInstance(); - } - } - } + wasAdded = true; + return CreateTestRunner(workerId); + }); + + if (wasAdded && IsMultiThreaded && Interlocked.CompareExchange(ref _wasSingletonInstanceDisabled, 1, 0) == 0) + { + FeatureContext.DisableSingletonInstance(); + ScenarioContext.DisableSingletonInstance(); + ScenarioStepContext.DisableSingletonInstance(); } + return testRunner; } - public virtual void Dispose() + public virtual async Task DisposeAsync() { - if (Interlocked.CompareExchange(ref disposeLockObj, new object(), null) == null) + if (Interlocked.CompareExchange(ref _wasDisposed, 1, 0) == 0) { - FireTestRunEnd(); + await FireTestRunEndAsync(); // this call dispose on this object, but the disposeLockObj will avoid double execution - globalContainer.Dispose(); + _globalContainer.Dispose(); - testRunnerRegistry.Clear(); + _testRunnerRegistry.Clear(); OnTestRunnerManagerDisposed(this); } } #region Static API - private static readonly Dictionary testRunnerManagerRegistry = new Dictionary(1); - private static readonly object testRunnerManagerRegistrySyncRoot = new object(); - private const int FixedLogicalThreadId = 0; + private static readonly ConcurrentDictionary _testRunnerManagerRegistry = new(); public static ITestRunnerManager GetTestRunnerManager(Assembly testAssembly = null, IContainerBuilder containerBuilder = null, bool createIfMissing = true) { - testAssembly ??= Assembly.GetCallingAssembly(); + testAssembly ??= GetCallingAssembly(); - if (!testRunnerManagerRegistry.TryGetValue(testAssembly, out var testRunnerManager)) + if (!createIfMissing) { - lock (testRunnerManagerRegistrySyncRoot) - { - if (!testRunnerManagerRegistry.TryGetValue(testAssembly, out testRunnerManager)) - { - if (!createIfMissing) - return null; - - testRunnerManager = CreateTestRunnerManager(testAssembly, containerBuilder); - testRunnerManagerRegistry.Add(testAssembly, testRunnerManager); - } - } + return _testRunnerManagerRegistry.TryGetValue(testAssembly, out var value) ? value : null; } + + var testRunnerManager = _testRunnerManagerRegistry.GetOrAdd( + testAssembly, + assembly => CreateTestRunnerManager(assembly, containerBuilder)); return testRunnerManager; } + /// + /// This is a workaround method solving not correctly working Assembly.GetCallingAssembly() when called from async method (due to state machine). + /// + private static Assembly GetCallingAssembly([CallerMemberName] string callingMethodName = null) + { + var stackTrace = new StackTrace(); + + var callingMethodIndex = -1; + + for (var i = 0; i < stackTrace.FrameCount; i++) + { + var frame = stackTrace.GetFrame(i); + + if (frame.GetMethod().Name == callingMethodName) + { + callingMethodIndex = i; + break; + } + } + + Assembly result = null; + + if (callingMethodIndex >= 0 && callingMethodIndex + 1 < stackTrace.FrameCount) + { + result = stackTrace.GetFrame(callingMethodIndex + 1).GetMethod().DeclaringType?.Assembly; + } + + return result ?? GetCallingAssembly(); + } + private static ITestRunnerManager CreateTestRunnerManager(Assembly testAssembly, IContainerBuilder containerBuilder = null) { containerBuilder ??= new ContainerBuilder(); @@ -216,73 +250,50 @@ private static ITestRunnerManager CreateTestRunnerManager(Assembly testAssembly, return testRunnerManager; } - public static void OnTestRunEnd(Assembly testAssembly = null, IContainerBuilder containerBuilder = null) + public static async Task OnTestRunEndAsync(Assembly testAssembly = null, IContainerBuilder containerBuilder = null) { - testAssembly ??= Assembly.GetCallingAssembly(); + testAssembly ??= GetCallingAssembly(); var testRunnerManager = GetTestRunnerManager(testAssembly, createIfMissing: false, containerBuilder: containerBuilder); - testRunnerManager?.FireTestRunEnd(); - testRunnerManager?.Dispose(); + if (testRunnerManager != null) + { + await testRunnerManager.FireTestRunEndAsync(); + await testRunnerManager.DisposeAsync(); + } } - public static void OnTestRunStart(Assembly testAssembly = null, IContainerBuilder containerBuilder = null) + public static async Task OnTestRunStartAsync(Assembly testAssembly = null, string testWorkerId = null, IContainerBuilder containerBuilder = null) { - testAssembly ??= Assembly.GetCallingAssembly(); + testAssembly ??= GetCallingAssembly(); var testRunnerManager = GetTestRunnerManager(testAssembly, createIfMissing: true, containerBuilder: containerBuilder); - testRunnerManager.GetTestRunner(GetLogicalThreadId(null)); + testRunnerManager.GetTestRunner(testWorkerId ?? TestRunStartWorkerId); - testRunnerManager.FireTestRunStart(); + await testRunnerManager.FireTestRunStartAsync(); } - public static ITestRunner GetTestRunner(Assembly testAssembly = null, int? managedThreadId = null, IContainerBuilder containerBuilder = null) + public static ITestRunner GetTestRunnerForAssembly(Assembly testAssembly = null, string testWorkerId = null, IContainerBuilder containerBuilder = null) { - testAssembly ??= Assembly.GetCallingAssembly(); - managedThreadId = GetLogicalThreadId(managedThreadId); + testAssembly ??= GetCallingAssembly(); var testRunnerManager = GetTestRunnerManager(testAssembly, containerBuilder); - return testRunnerManager.GetTestRunner(managedThreadId.Value); - } - - - private static int GetLogicalThreadId(int? managedThreadId) - { - if (ParallelExecutionIsDisabled()) - { - return FixedLogicalThreadId; - } - - return managedThreadId ?? Thread.CurrentThread.ManagedThreadId; - } - - private static bool ParallelExecutionIsDisabled() - { - if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable(EnvironmentVariableNames.NCrunch)) || - !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(EnvironmentVariableNames.SpecflowDisableParallelExecution))) - { - return true; - } - - return false; + return testRunnerManager.GetTestRunner(testWorkerId); } - internal static void Reset() + internal static async Task ResetAsync() { - lock (testRunnerManagerRegistrySyncRoot) + while (!_testRunnerManagerRegistry.IsEmpty) { - foreach (var testRunnerManager in testRunnerManagerRegistry.Values.ToArray()) + foreach (var assembly in _testRunnerManagerRegistry.Keys.ToArray()) { - testRunnerManager.Dispose(); + if (_testRunnerManagerRegistry.TryRemove(assembly, out var testRunnerManager)) + { + await testRunnerManager.DisposeAsync(); + } } - testRunnerManagerRegistry.Clear(); } } - private static void OnTestRunnerManagerDisposed(TestRunnerManager testRunnerManager) { - lock (testRunnerManagerRegistrySyncRoot) - { - if (testRunnerManagerRegistry.ContainsKey(testRunnerManager.TestAssembly)) - testRunnerManagerRegistry.Remove(testRunnerManager.TestAssembly); - } + _testRunnerManagerRegistry.TryRemove(testRunnerManager.TestAssembly, out _); } #endregion diff --git a/TechTalk.SpecFlow/Tracing/TraceListenerQueue.cs b/TechTalk.SpecFlow/Tracing/TraceListenerQueue.cs index 133374870..236929108 100644 --- a/TechTalk.SpecFlow/Tracing/TraceListenerQueue.cs +++ b/TechTalk.SpecFlow/Tracing/TraceListenerQueue.cs @@ -67,7 +67,7 @@ public void EnqueueMessage(ITestRunner sourceTestRunner, string message, bool is } } - _messages.Add(new TraceMessage(isToolMessgae, string.Format("#{1}: {0}", message, sourceTestRunner.ThreadId))); + _messages.Add(new TraceMessage(isToolMessgae, string.Format("#{1}: {0}", message, sourceTestRunner.TestWorkerId))); } public void Dispose() diff --git a/Tests/TechTalk.SpecFlow.GeneratorTests/Analytics/AnalyticsTransmitterTests.cs b/Tests/TechTalk.SpecFlow.GeneratorTests/Analytics/AnalyticsTransmitterTests.cs index 775594e29..d7da910dc 100644 --- a/Tests/TechTalk.SpecFlow.GeneratorTests/Analytics/AnalyticsTransmitterTests.cs +++ b/Tests/TechTalk.SpecFlow.GeneratorTests/Analytics/AnalyticsTransmitterTests.cs @@ -22,7 +22,7 @@ public async Task TryTransmitEvent_AnalyticsDisabled_ShouldReturnSuccess() var analyticsTransmitter = new AnalyticsTransmitter(analyticsTransmitterSinkMock.Object, environmentSpecFlowTelemetryCheckerMock.Object); // ACT - var result = await analyticsTransmitter.TransmitEvent(analyticsEventMock.Object); + var result = await analyticsTransmitter.TransmitEventAsync(analyticsEventMock.Object); // ASSERT result.Should().BeAssignableTo(); @@ -41,10 +41,10 @@ public async Task TryTransmitEvent_AnalyticsDisabled_ShouldNotCallSink() var analyticsTransmitter = new AnalyticsTransmitter(analyticsTransmitterSinkMock.Object, environmentSpecFlowTelemetryCheckerMock.Object); // ACT - await analyticsTransmitter.TransmitEvent(analyticsEventMock.Object); + await analyticsTransmitter.TransmitEventAsync(analyticsEventMock.Object); // ASSERT - analyticsTransmitterSinkMock.Verify(sink => sink.TransmitEvent(It.IsAny(), It.IsAny()), Times.Never); + analyticsTransmitterSinkMock.Verify(sink => sink.TransmitEventAsync(It.IsAny(), It.IsAny()), Times.Never); } [Fact] @@ -60,10 +60,10 @@ public async Task TransmitSpecFlowProjectCompilingEvent_AnalyticsEnabled_ShouldC var analyticsTransmitter = new AnalyticsTransmitter(analyticsTransmitterSinkMock.Object, environmentSpecFlowTelemetryCheckerMock.Object); // ACT - await analyticsTransmitter.TransmitSpecFlowProjectCompilingEvent(specFlowProjectCompilingEvent); + await analyticsTransmitter.TransmitSpecFlowProjectCompilingEventAsync(specFlowProjectCompilingEvent); // ASSERT - analyticsTransmitterSinkMock.Verify(m => m.TransmitEvent(It.IsAny(), It.IsAny()), Times.Once); + analyticsTransmitterSinkMock.Verify(m => m.TransmitEventAsync(It.IsAny(), It.IsAny()), Times.Once); } } } diff --git a/Tests/TechTalk.SpecFlow.GeneratorTests/MSBuildTask/GenerateFeatureFileCodeBehindTaskTests.cs b/Tests/TechTalk.SpecFlow.GeneratorTests/MSBuildTask/GenerateFeatureFileCodeBehindTaskTests.cs index 0ce794afe..b502494c2 100644 --- a/Tests/TechTalk.SpecFlow.GeneratorTests/MSBuildTask/GenerateFeatureFileCodeBehindTaskTests.cs +++ b/Tests/TechTalk.SpecFlow.GeneratorTests/MSBuildTask/GenerateFeatureFileCodeBehindTaskTests.cs @@ -33,7 +33,7 @@ private Mock GetFeatureFileCodeBehindGeneratorM private Mock GetAnalyticsTransmitterMock() { var analyticsTransmitterMock = new Mock(); - analyticsTransmitterMock.Setup(at => at.TransmitSpecFlowProjectCompilingEvent(It.IsAny())) + analyticsTransmitterMock.Setup(at => at.TransmitSpecFlowProjectCompilingEventAsync(It.IsAny())) .Callback(() => { }); return analyticsTransmitterMock; } diff --git a/Tests/TechTalk.SpecFlow.GeneratorTests/ScenarioPartHelperTests.cs b/Tests/TechTalk.SpecFlow.GeneratorTests/ScenarioPartHelperTests.cs index 02f87596e..bfb014644 100644 --- a/Tests/TechTalk.SpecFlow.GeneratorTests/ScenarioPartHelperTests.cs +++ b/Tests/TechTalk.SpecFlow.GeneratorTests/ScenarioPartHelperTests.cs @@ -86,7 +86,7 @@ public void GenerateStep_ScenarioOutlineWithMultiLineTextThatIncludesParametersE //ASSERT //That the generated statements includes a Given method invocation - var _givenStatements = _statements.OfType().Select(ces => ces.Expression).OfType().Where(m => m.Method.MethodName == "Given"); + var _givenStatements = _statements.OfType().Select(ces => ces.Expression).OfType().Where(m => m.Method.MethodName.StartsWith("Given")); var _firstGivenStatement = _givenStatements.First(); _firstGivenStatement.Should().NotBeNull(); diff --git a/Tests/TechTalk.SpecFlow.GeneratorTests/UnitTestFeatureGeneratorTests.cs b/Tests/TechTalk.SpecFlow.GeneratorTests/UnitTestFeatureGeneratorTests.cs index 04aeafcdb..ee2b5f74f 100644 --- a/Tests/TechTalk.SpecFlow.GeneratorTests/UnitTestFeatureGeneratorTests.cs +++ b/Tests/TechTalk.SpecFlow.GeneratorTests/UnitTestFeatureGeneratorTests.cs @@ -28,6 +28,7 @@ protected virtual void SetupInternal() { Container = new GeneratorContainerBuilder().CreateContainer(new SpecFlowConfigurationHolder(ConfigSource.Default, null), new ProjectSettings(), Enumerable.Empty()); UnitTestGeneratorProviderMock = new Mock(); + UnitTestGeneratorProviderMock.Setup(utp => utp.GetTestWorkerIdExpression()).Returns(new CodePrimitiveExpression(null)); Container.RegisterInstanceAs(UnitTestGeneratorProviderMock.Object); } diff --git a/Tests/TechTalk.SpecFlow.GeneratorTests/UnitTestProvider/MsTestGeneratorProviderTests.cs b/Tests/TechTalk.SpecFlow.GeneratorTests/UnitTestProvider/MsTestGeneratorProviderTests.cs index bbec96506..30e3e0dcf 100644 --- a/Tests/TechTalk.SpecFlow.GeneratorTests/UnitTestProvider/MsTestGeneratorProviderTests.cs +++ b/Tests/TechTalk.SpecFlow.GeneratorTests/UnitTestProvider/MsTestGeneratorProviderTests.cs @@ -202,7 +202,7 @@ public void MsTestGeneratorProvider_ShouldInvokeFeatureSetupMethodWithGlobalName var featureSetupCall = code .Class() .Members() - .Single(m => m.Name == "TestInitialize") + .Single(m => m.Name == "TestInitializeAsync") .Statements .OfType() .First() @@ -213,8 +213,8 @@ public void MsTestGeneratorProvider_ShouldInvokeFeatureSetupMethodWithGlobalName .As(); featureSetupCall.Should().NotBeNull(); - featureSetupCall.Method.MethodName.Should().Be("FeatureSetup"); - featureSetupCall.Method.TargetObject.As().Type.Options.Should().Be(CodeTypeReferenceOptions.GlobalReference); + featureSetupCall.Method.MethodName.Should().Be("FeatureSetupAsync"); + featureSetupCall.Method.TargetObject.As().Type.BaseType.Should().Contain("global::"); } [Fact] diff --git a/Tests/TechTalk.SpecFlow.GeneratorTests/UnitTestProvider/NUnit3GeneratorProviderTests.cs b/Tests/TechTalk.SpecFlow.GeneratorTests/UnitTestProvider/NUnit3GeneratorProviderTests.cs index e19ecf32b..b52c31765 100644 --- a/Tests/TechTalk.SpecFlow.GeneratorTests/UnitTestProvider/NUnit3GeneratorProviderTests.cs +++ b/Tests/TechTalk.SpecFlow.GeneratorTests/UnitTestProvider/NUnit3GeneratorProviderTests.cs @@ -216,7 +216,7 @@ public void NUnit3TestGeneratorProvider_ShouldNotGenerateObsoleteTestFixtureSetU { var code = GenerateCodeNamespaceFromFeature(SampleFeatureFile); - var featureSetupMethod = code.Class().Members().Single(m => m.Name == "FeatureSetup"); + var featureSetupMethod = code.Class().Members().Single(m => m.Name == "FeatureSetupAsync"); featureSetupMethod.CustomAttributes() .FirstOrDefault(a => a.Name == "NUnit.Framework.TestFixtureSetUpAttribute") @@ -229,7 +229,7 @@ public void NUnit3TestGeneratorProvider_ShouldGenerateNewOneTimeSetUpAttribute() { var code = GenerateCodeNamespaceFromFeature(SampleFeatureFile); - var featureSetupMethod = code.Class().Members().Single(m => m.Name == "FeatureSetup"); + var featureSetupMethod = code.Class().Members().Single(m => m.Name == "FeatureSetupAsync"); // Assert that we do use the NUnit3 attribute featureSetupMethod.CustomAttributes() @@ -243,7 +243,7 @@ public void NUnit3TestGeneratorProvider_ShouldNotGenerateObsoleteTestFixtureTear { var code = GenerateCodeNamespaceFromFeature(SampleFeatureFile); - var featureSetupMethod = code.Class().Members().Single(m => m.Name == "FeatureTearDown"); + var featureSetupMethod = code.Class().Members().Single(m => m.Name == "FeatureTearDownAsync"); featureSetupMethod.CustomAttributes() .FirstOrDefault(a => a.Name == "NUnit.Framework.TestFixtureTearDownAttribute") @@ -256,7 +256,7 @@ public void NUnit3TestGeneratorProvider_ShouldGenerateNewOneTimeTearDownAttribut { var code = GenerateCodeNamespaceFromFeature(SampleFeatureFile); - var featureSetupMethod = code.Class().Members().Single(m => m.Name == "FeatureTearDown"); + var featureSetupMethod = code.Class().Members().Single(m => m.Name == "FeatureTearDownAsync"); // Assert that we do use the NUnit3 attribute featureSetupMethod.CustomAttributes() diff --git a/Tests/TechTalk.SpecFlow.GeneratorTests/UnitTestProvider/XUnit2TestGeneratorProviderTests.cs b/Tests/TechTalk.SpecFlow.GeneratorTests/UnitTestProvider/XUnit2TestGeneratorProviderTests.cs index b8d434b9b..476686d3e 100644 --- a/Tests/TechTalk.SpecFlow.GeneratorTests/UnitTestProvider/XUnit2TestGeneratorProviderTests.cs +++ b/Tests/TechTalk.SpecFlow.GeneratorTests/UnitTestProvider/XUnit2TestGeneratorProviderTests.cs @@ -5,7 +5,6 @@ using FluentAssertions; using Microsoft.CSharp; using TechTalk.SpecFlow.Generator.CodeDom; -using TechTalk.SpecFlow.Generator.Interfaces; using TechTalk.SpecFlow.Generator.UnitTestProvider; using TechTalk.SpecFlow.Parser; using Xunit; @@ -48,7 +47,7 @@ long string "; var document = ParseDocumentFromString(sampleFeatureFile); - var sampleTestGeneratorProvider = new XUnit2TestGeneratorProvider(new CodeDomHelper(CodeDomProviderLanguage.CSharp), new ProjectSettings() { DefaultNamespace = "Target" }); + var sampleTestGeneratorProvider = new XUnit2TestGeneratorProvider(new CodeDomHelper(CodeDomProviderLanguage.CSharp)); var converter = sampleTestGeneratorProvider.CreateUnitTestConverter(); // ACT @@ -95,7 +94,7 @@ long string "; var document = ParseDocumentFromString(sampleFeatureFileMultipleColumns); - var sampleTestGeneratorProvider = new XUnit2TestGeneratorProvider(new CodeDomHelper(CodeDomProviderLanguage.CSharp), new ProjectSettings() { DefaultNamespace = "Target" }); + var sampleTestGeneratorProvider = new XUnit2TestGeneratorProvider(new CodeDomHelper(CodeDomProviderLanguage.CSharp)); var converter = sampleTestGeneratorProvider.CreateUnitTestConverter(); // ACT @@ -146,7 +145,7 @@ long string "; var document = ParseDocumentFromString(sampleFeatureFileWithMultipleExampleSets); - var sampleTestGeneratorProvider = new XUnit2TestGeneratorProvider(new CodeDomHelper(CodeDomProviderLanguage.CSharp), new ProjectSettings() { DefaultNamespace = "Target" }); + var sampleTestGeneratorProvider = new XUnit2TestGeneratorProvider(new CodeDomHelper(CodeDomProviderLanguage.CSharp)); var converter = sampleTestGeneratorProvider.CreateUnitTestConverter(); // ACT @@ -170,7 +169,7 @@ long string public void XUnit2TestGeneratorProvider_ShouldSetDisplayNameForTheoryAttribute() { // Arrange - var provider = new XUnit2TestGeneratorProvider(new CodeDomHelper(new CSharpCodeProvider()), new ProjectSettings() { DefaultNamespace = "Target" }); + var provider = new XUnit2TestGeneratorProvider(new CodeDomHelper(new CSharpCodeProvider())); var context = new Generator.TestClassGenerationContext( unitTestGeneratorProvider: null, document: new SpecFlowDocument( @@ -226,7 +225,7 @@ public void XUnit2TestGeneratorProvider_ShouldSetDisplayNameForTheoryAttribute() public void XUnit2TestGeneratorProvider_ShouldSetSkipAttributeForTheory() { // Arrange - var provider = new XUnit2TestGeneratorProvider(new CodeDomHelper(new CSharpCodeProvider()), new ProjectSettings() { DefaultNamespace = "Target" }); + var provider = new XUnit2TestGeneratorProvider(new CodeDomHelper(new CSharpCodeProvider())); // Act var codeMemberMethod = new CodeMemberMethod @@ -267,7 +266,7 @@ public void XUnit2TestGeneratorProvider_ShouldSetSkipAttributeForTheory() public void XUnit2TestGeneratorProvider_ShouldSetDisplayNameForFactAttribute() { // Arrange - var provider = new XUnit2TestGeneratorProvider(new CodeDomHelper(new CSharpCodeProvider()), new ProjectSettings() { DefaultNamespace = "Target" }); + var provider = new XUnit2TestGeneratorProvider(new CodeDomHelper(new CSharpCodeProvider())); var context = new Generator.TestClassGenerationContext( unitTestGeneratorProvider: null, document: new Parser.SpecFlowDocument( @@ -323,7 +322,7 @@ public void XUnit2TestGeneratorProvider_ShouldInitializeTestOutputHelperFieldInC { // ARRANGE var document = ParseDocumentFromString(SampleFeatureFile); - var provider = new XUnit2TestGeneratorProvider(new CodeDomHelper(CodeDomProviderLanguage.CSharp), new ProjectSettings() { DefaultNamespace = "Target" }); + var provider = new XUnit2TestGeneratorProvider(new CodeDomHelper(CodeDomProviderLanguage.CSharp)); var converter = provider.CreateUnitTestConverter(); // ACT @@ -334,9 +333,9 @@ public void XUnit2TestGeneratorProvider_ShouldInitializeTestOutputHelperFieldInC // ASSERT var classConstructor = code.Class().Members().Single(m => m.Name == ".ctor"); classConstructor.Should().NotBeNull(); - classConstructor.Parameters.Count.Should().Be(3); - classConstructor.Parameters[2].Type.BaseType.Should().Be("Xunit.Abstractions.ITestOutputHelper"); - classConstructor.Parameters[2].Name.Should().Be("testOutputHelper"); + classConstructor.Parameters.Count.Should().Be(2); + classConstructor.Parameters[1].Type.BaseType.Should().Be("Xunit.Abstractions.ITestOutputHelper"); + classConstructor.Parameters[1].Name.Should().Be("testOutputHelper"); var initOutputHelper = classConstructor.Statements.OfType().First(); initOutputHelper.Should().NotBeNull(); @@ -349,7 +348,7 @@ public void XUnit2TestGeneratorProvider_Should_add_testOutputHelper_field_in_cla { // ARRANGE var document = ParseDocumentFromString(SampleFeatureFile); - var provider = new XUnit2TestGeneratorProvider(new CodeDomHelper(CodeDomProviderLanguage.CSharp), new ProjectSettings() { DefaultNamespace = "Target" }); + var provider = new XUnit2TestGeneratorProvider(new CodeDomHelper(CodeDomProviderLanguage.CSharp)); var converter = provider.CreateUnitTestConverter(); // ACT @@ -367,7 +366,7 @@ public void XUnit2TestGeneratorProvider_Should_register_testOutputHelper_on_scen { // ARRANGE var document = ParseDocumentFromString(SampleFeatureFile); - var provider = new XUnit2TestGeneratorProvider(new CodeDomHelper(CodeDomProviderLanguage.CSharp), new ProjectSettings() { DefaultNamespace = "Target" }); + var provider = new XUnit2TestGeneratorProvider(new CodeDomHelper(CodeDomProviderLanguage.CSharp)); var converter = provider.CreateUnitTestConverter(); // ACT @@ -394,7 +393,7 @@ public void XUnit2TestGeneratorProvider_Should_register_testOutputHelper_on_scen [Fact] public void XUnit2TestGeneratorProvider_ShouldHaveParallelExecutionTrait() { - var provider = new XUnit2TestGeneratorProvider(new CodeDomHelper(CodeDomProviderLanguage.CSharp), new ProjectSettings() { DefaultNamespace = "Target" }); + var provider = new XUnit2TestGeneratorProvider(new CodeDomHelper(CodeDomProviderLanguage.CSharp)); provider.GetTraits() .HasFlag(UnitTestGeneratorTraits.ParallelExecution) @@ -413,7 +412,7 @@ public void XUnit2TestGeneratorProvider_WithFeatureWithMatchingTag_ShouldAddNonP Scenario: Simple scenario Given there is something"); - var provider = new XUnit2TestGeneratorProvider(new CodeDomHelper(CodeDomProviderLanguage.CSharp), new ProjectSettings() { DefaultNamespace = "Target" }); + var provider = new XUnit2TestGeneratorProvider(new CodeDomHelper(CodeDomProviderLanguage.CSharp)); var featureGenerator = provider.CreateFeatureGenerator(addNonParallelizableMarkerForTags: new string[] { "nonparallelizable" }); // ACT @@ -437,7 +436,7 @@ public void XUnit2TestGeneratorProvider_WithFeatureWithNoMatchingTag_ShouldNotAd Scenario: Simple scenario Given there is something"); - var provider = new XUnit2TestGeneratorProvider(new CodeDomHelper(CodeDomProviderLanguage.CSharp), new ProjectSettings() { DefaultNamespace = "Target" }); + var provider = new XUnit2TestGeneratorProvider(new CodeDomHelper(CodeDomProviderLanguage.CSharp)); var featureGenerator = provider.CreateFeatureGenerator(addNonParallelizableMarkerForTags: new string[] { "nonparallelizable" }); // ACT diff --git a/Tests/TechTalk.SpecFlow.RuntimeTests/Bindings/AsyncHelperTests.cs b/Tests/TechTalk.SpecFlow.RuntimeTests/Bindings/AsyncHelperTests.cs deleted file mode 100644 index 20da2eaa5..000000000 --- a/Tests/TechTalk.SpecFlow.RuntimeTests/Bindings/AsyncHelperTests.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using TechTalk.SpecFlow.Bindings; -using Xunit; - -namespace TechTalk.SpecFlow.RuntimeTests.Bindings -{ - public class AsyncHelperTests - { - private void NormalMethod() - { - - } - - private void ThrowException() - { - throw new Exception("Hi from async"); - } - - private Task AsyncMethodThrowsException() - { - throw new Exception(); - } - - [Fact] - public void RunSync_NormalMethod() - { - AsyncHelpers.RunSync(() => - { - NormalMethod(); - return Task.CompletedTask; - }); - } - - [Fact] - public void RunSync_ExceptionIsThrown() - { - - Action action = () => AsyncHelpers.RunSync(() => - { - ThrowException(); - return Task.CompletedTask; - }); - - action.Should().Throw().WithMessage("Hi from async"); - } - - [Fact] - public void RunSync_WhenExceptionIsThrown_ShouldRestoreOriginalStackTrace() - { - try - { - AsyncHelpers.RunSync(AsyncMethodThrowsException); - } - catch (Exception ex) - { - ex.StackTrace.TrimStart().Should().StartWith($"at {GetType().FullName}.{nameof(AsyncMethodThrowsException)}"); - } - } - - [Fact] - public void RunSync_WhenExceptionIsThrown_ShouldRestoreSynchronizationContext() - { - var expectedSynchronizationContext = SynchronizationContext.Current; - - Action action = () => AsyncHelpers.RunSync(AsyncMethodThrowsException); - action.Should().Throw(); - - SynchronizationContext.Current.Should().BeSameAs(expectedSynchronizationContext); - } - - private object NormalMethodReturnValue() - { - return null; - } - - private object ThrowExceptionReturnValue() - { - throw new Exception("Hi from async"); - } - - private Task AsyncMethodReturnValueThrowsException() - { - throw new Exception(); - } - - [Fact] - public void RunSyncReturnValue_NormalMethod() - { - AsyncHelpers.RunSync(() => Task.FromResult(NormalMethodReturnValue())); - } - - [Fact] - public void RunSyncReturnValue_ExceptionIsThrown() - { - - Action action = () => AsyncHelpers.RunSync(() => - { - ThrowExceptionReturnValue(); - return Task.CompletedTask; - }); - - action.Should().Throw().WithMessage("Hi from async"); - } - - [Fact] - public void RunSyncReturnValue_WhenExceptionIsThrown_ShouldRestoreOriginalStackTrace() - { - try - { - AsyncHelpers.RunSync(AsyncMethodReturnValueThrowsException); - } - catch (Exception ex) - { - ex.StackTrace.TrimStart().Should().StartWith($"at {GetType().FullName}.{nameof(AsyncMethodReturnValueThrowsException)}"); - } - } - - [Fact] - public void RunSyncReturnValue_WhenExceptionIsThrown_ShouldRestoreSynchronizationContext() - { - var expectedSynchronizationContext = SynchronizationContext.Current; - - Action action = () => AsyncHelpers.RunSync(AsyncMethodReturnValueThrowsException); - action.Should().Throw(); - - SynchronizationContext.Current.Should().BeSameAs(expectedSynchronizationContext); - } - } -} \ No newline at end of file diff --git a/Tests/TechTalk.SpecFlow.RuntimeTests/Bindings/BindingRegistryTests.cs b/Tests/TechTalk.SpecFlow.RuntimeTests/Bindings/BindingRegistryTests.cs index 0aa5bae77..b8533859f 100644 --- a/Tests/TechTalk.SpecFlow.RuntimeTests/Bindings/BindingRegistryTests.cs +++ b/Tests/TechTalk.SpecFlow.RuntimeTests/Bindings/BindingRegistryTests.cs @@ -26,7 +26,7 @@ public void GetStepDefinitions_should_return_all_step_definitions() var result = sut.GetStepDefinitions(); - result.Should().BeEquivalentTo(stepDefinitionBinding1, stepDefinitionBinding2); + result.Should().BeEquivalentTo(new List { stepDefinitionBinding1, stepDefinitionBinding2 }); } [Fact] @@ -41,7 +41,7 @@ public void GetHooks_should_return_all_hooks() var result = sut.GetHooks(); - result.Should().BeEquivalentTo(hook1, hook2); + result.Should().BeEquivalentTo(new List { hook1, hook2 }); } } } diff --git a/Tests/TechTalk.SpecFlow.RuntimeTests/Configuration/JsonConfigTests.cs b/Tests/TechTalk.SpecFlow.RuntimeTests/Configuration/JsonConfigTests.cs index 3c7f55c40..bb116ff59 100644 --- a/Tests/TechTalk.SpecFlow.RuntimeTests/Configuration/JsonConfigTests.cs +++ b/Tests/TechTalk.SpecFlow.RuntimeTests/Configuration/JsonConfigTests.cs @@ -461,7 +461,7 @@ private void AssertDefaultJsonSpecFlowConfiguration(SpecFlowConfiguration config config.MissingOrPendingStepsOutcome.Should().Be(ConfigDefaults.MissingOrPendingStepsOutcome); config.ObsoleteBehavior.Should().Be(ConfigDefaults.ObsoleteBehavior); config.CustomDependencies.Should().NotBeNull(); - config.CustomDependencies.Should().BeEmpty(); + config.CustomDependencies.Count.Should().Be(0); //generator config.AllowDebugGeneratedFiles.Should().Be(ConfigDefaults.AllowDebugGeneratedFiles); diff --git a/Tests/TechTalk.SpecFlow.RuntimeTests/Infrastructure/TestExecutionEngineRuntimePluginEventsTests.cs b/Tests/TechTalk.SpecFlow.RuntimeTests/Infrastructure/TestExecutionEngineRuntimePluginEventsTests.cs index cd82842a8..701abf543 100644 --- a/Tests/TechTalk.SpecFlow.RuntimeTests/Infrastructure/TestExecutionEngineRuntimePluginEventsTests.cs +++ b/Tests/TechTalk.SpecFlow.RuntimeTests/Infrastructure/TestExecutionEngineRuntimePluginEventsTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using BoDi; using FluentAssertions; using Moq; @@ -11,209 +12,210 @@ namespace TechTalk.SpecFlow.RuntimeTests.Infrastructure public partial class TestExecutionEngineTests { private const string SimulatedErrorMessage = "simulated error"; + private void RegisterFailingHook(List hooks) { - TimeSpan duration; var hookMock = CreateHookMock(hooks); - methodBindingInvokerMock.Setup(i => i.InvokeBinding(hookMock.Object, contextManagerStub.Object, null, testTracerStub.Object, out duration)) - .Throws(new Exception(SimulatedErrorMessage)); + methodBindingInvokerMock.Setup(i => i.InvokeBindingAsync(hookMock.Object, contextManagerStub.Object, null, testTracerStub.Object, It.IsAny())) + .ThrowsAsync(new Exception(SimulatedErrorMessage)); } [Fact] - public void Should_emit_runtime_plugin_test_execution_lifecycle_event_beforetestrun() + public async Task Should_emit_runtime_plugin_test_execution_lifecycle_event_beforetestrun() { var testExecutionEngine = CreateTestExecutionEngine(); - testExecutionEngine.OnTestRunStart(); + await testExecutionEngine.OnTestRunStartAsync(); _runtimePluginTestExecutionLifecycleEventEmitter.Verify(e => e.RaiseExecutionLifecycleEvent(HookType.BeforeTestRun, It.IsAny())); } [Fact] - public void Should_emit_runtime_plugin_test_execution_lifecycle_event_beforetestrun_after_hook_error_and_throw_error() + public async Task Should_emit_runtime_plugin_test_execution_lifecycle_event_beforetestrun_after_hook_error_and_throw_error() { var testExecutionEngine = CreateTestExecutionEngine(); RegisterFailingHook(beforeTestRunEvents); - Action act = () => testExecutionEngine.OnTestRunStart(); + Func act = async () => await testExecutionEngine.OnTestRunStartAsync(); + + await act.Should().ThrowAsync().WithMessage(SimulatedErrorMessage); - act.Should().Throw().WithMessage(SimulatedErrorMessage); _runtimePluginTestExecutionLifecycleEventEmitter.Verify(e => e.RaiseExecutionLifecycleEvent(HookType.BeforeTestRun, It.IsAny())); } [Fact] - public void Should_emit_runtime_plugin_test_execution_lifecycle_event_aftertestrun() + public async Task Should_emit_runtime_plugin_test_execution_lifecycle_event_aftertestrun() { var testExecutionEngine = CreateTestExecutionEngine(); - testExecutionEngine.OnTestRunEnd(); + await testExecutionEngine.OnTestRunEndAsync(); _runtimePluginTestExecutionLifecycleEventEmitter.Verify(e => e.RaiseExecutionLifecycleEvent(HookType.AfterTestRun, It.IsAny())); } [Fact] - public void Should_emit_runtime_plugin_test_execution_lifecycle_event_aftertestrun_after_hook_error_and_throw_error() + public async Task Should_emit_runtime_plugin_test_execution_lifecycle_event_aftertestrun_after_hook_error_and_throw_error() { var testExecutionEngine = CreateTestExecutionEngine(); RegisterFailingHook(afterTestRunEvents); - Action act = () => testExecutionEngine.OnTestRunEnd(); + Func act = async () => await testExecutionEngine.OnTestRunEndAsync(); - act.Should().Throw().WithMessage(SimulatedErrorMessage); + await act.Should().ThrowAsync().WithMessage(SimulatedErrorMessage); _runtimePluginTestExecutionLifecycleEventEmitter.Verify(e => e.RaiseExecutionLifecycleEvent(HookType.AfterTestRun, It.IsAny())); } [Fact] - public void Should_emit_runtime_plugin_test_execution_lifecycle_event_beforefeature() + public async Task Should_emit_runtime_plugin_test_execution_lifecycle_event_beforefeature() { var testExecutionEngine = CreateTestExecutionEngine(); - testExecutionEngine.OnFeatureStart(featureInfo); + await testExecutionEngine.OnFeatureStartAsync(featureInfo); _runtimePluginTestExecutionLifecycleEventEmitter.Verify(e => e.RaiseExecutionLifecycleEvent(HookType.BeforeFeature, It.IsAny())); } [Fact] - public void Should_emit_runtime_plugin_test_execution_lifecycle_event_beforefeature_after_hook_error_and_throw_error() + public async Task Should_emit_runtime_plugin_test_execution_lifecycle_event_beforefeature_after_hook_error_and_throw_error() { var testExecutionEngine = CreateTestExecutionEngine(); RegisterFailingHook(beforeFeatureEvents); - Action act = () => testExecutionEngine.OnFeatureStart(featureInfo); + Func act = async () => await testExecutionEngine.OnFeatureStartAsync(featureInfo); - act.Should().Throw().WithMessage(SimulatedErrorMessage); + await act.Should().ThrowAsync().WithMessage(SimulatedErrorMessage); _runtimePluginTestExecutionLifecycleEventEmitter.Verify(e => e.RaiseExecutionLifecycleEvent(HookType.BeforeFeature, It.IsAny())); } [Fact] - public void Should_emit_runtime_plugin_test_execution_lifecycle_event_afterfeature() + public async Task Should_emit_runtime_plugin_test_execution_lifecycle_event_afterfeature() { var testExecutionEngine = CreateTestExecutionEngine(); - testExecutionEngine.OnFeatureEnd(); + await testExecutionEngine.OnFeatureEndAsync(); _runtimePluginTestExecutionLifecycleEventEmitter.Verify(e => e.RaiseExecutionLifecycleEvent(HookType.AfterFeature, It.IsAny())); } [Fact] - public void Should_emit_runtime_plugin_test_execution_lifecycle_event_afterfeature_after_hook_error_and_throw_error() + public async Task Should_emit_runtime_plugin_test_execution_lifecycle_event_afterfeature_after_hook_error_and_throw_error() { var testExecutionEngine = CreateTestExecutionEngine(); RegisterFailingHook(afterFeatureEvents); - Action act = () => testExecutionEngine.OnFeatureEnd(); + Func act = async () => await testExecutionEngine.OnFeatureEndAsync(); - act.Should().Throw().WithMessage(SimulatedErrorMessage); + await act.Should().ThrowAsync().WithMessage(SimulatedErrorMessage); _runtimePluginTestExecutionLifecycleEventEmitter.Verify(e => e.RaiseExecutionLifecycleEvent(HookType.AfterFeature, It.IsAny())); } [Fact] - public void Should_emit_runtime_plugin_test_execution_lifecycle_event_beforescenario() + public async Task Should_emit_runtime_plugin_test_execution_lifecycle_event_beforescenario() { var testExecutionEngine = CreateTestExecutionEngine(); - testExecutionEngine.OnScenarioStart(); + await testExecutionEngine.OnScenarioStartAsync(); _runtimePluginTestExecutionLifecycleEventEmitter.Verify(e => e.RaiseExecutionLifecycleEvent(HookType.BeforeScenario, It.IsAny())); } [Fact] - public void Should_emit_runtime_plugin_test_execution_lifecycle_event_beforescenario_after_hook_error_and_throw_error() + public async Task Should_emit_runtime_plugin_test_execution_lifecycle_event_beforescenario_after_hook_error_and_throw_error() { var testExecutionEngine = CreateTestExecutionEngine(); RegisterFailingHook(beforeScenarioEvents); - Action act = () => + Func act = async () => { //NOTE: the exception will be re-thrown in the OnAfterLastStep - testExecutionEngine.OnScenarioStart(); - testExecutionEngine.OnAfterLastStep(); + await testExecutionEngine.OnScenarioStartAsync(); + await testExecutionEngine.OnAfterLastStepAsync(); }; - act.Should().Throw().WithMessage(SimulatedErrorMessage); + await act.Should().ThrowAsync().WithMessage(SimulatedErrorMessage); _runtimePluginTestExecutionLifecycleEventEmitter.Verify(e => e.RaiseExecutionLifecycleEvent(HookType.BeforeScenario, It.IsAny())); } [Fact] - public void Should_emit_runtime_plugin_test_execution_lifecycle_event_afterscenario() + public async Task Should_emit_runtime_plugin_test_execution_lifecycle_event_afterscenario() { var testExecutionEngine = CreateTestExecutionEngine(); - testExecutionEngine.OnScenarioEnd(); + await testExecutionEngine.OnScenarioEndAsync(); _runtimePluginTestExecutionLifecycleEventEmitter.Verify(e => e.RaiseExecutionLifecycleEvent(HookType.AfterScenario, It.IsAny())); } [Fact] - public void Should_emit_runtime_plugin_test_execution_lifecycle_event_afterscenario_after_hook_error_and_throw_error() + public async Task Should_emit_runtime_plugin_test_execution_lifecycle_event_afterscenario_after_hook_error_and_throw_error() { var testExecutionEngine = CreateTestExecutionEngine(); RegisterFailingHook(afterScenarioEvents); - Action act = () => testExecutionEngine.OnScenarioEnd(); + Func act = async () => await testExecutionEngine.OnScenarioEndAsync(); - act.Should().Throw().WithMessage(SimulatedErrorMessage); + await act.Should().ThrowAsync().WithMessage(SimulatedErrorMessage); _runtimePluginTestExecutionLifecycleEventEmitter.Verify(e => e.RaiseExecutionLifecycleEvent(HookType.AfterScenario, It.IsAny())); } [Fact] - public void Should_emit_runtime_plugin_test_execution_lifecycle_event_beforestep() + public async Task Should_emit_runtime_plugin_test_execution_lifecycle_event_beforestep() { var testExecutionEngine = CreateTestExecutionEngine(); RegisterStepDefinition(); - testExecutionEngine.Step(StepDefinitionKeyword.Given, null, "foo", null, null); + await testExecutionEngine.StepAsync(StepDefinitionKeyword.Given, null, "foo", null, null); _runtimePluginTestExecutionLifecycleEventEmitter.Verify(e => e.RaiseExecutionLifecycleEvent(HookType.BeforeStep, It.IsAny())); } [Fact] - public void Should_emit_runtime_plugin_test_execution_lifecycle_event_beforestep_after_hook_error_and_throw_error() + public async Task Should_emit_runtime_plugin_test_execution_lifecycle_event_beforestep_after_hook_error_and_throw_error() { var testExecutionEngine = CreateTestExecutionEngine(); RegisterStepDefinition(); RegisterFailingHook(beforeStepEvents); - Action act = () => + Func act = async () => { //NOTE: the exception will be re-thrown in the OnAfterLastStep - testExecutionEngine.Step(StepDefinitionKeyword.Given, null, "foo", null, null); - testExecutionEngine.OnAfterLastStep(); + await testExecutionEngine.StepAsync(StepDefinitionKeyword.Given, null, "foo", null, null); + await testExecutionEngine.OnAfterLastStepAsync(); }; - act.Should().Throw().WithMessage(SimulatedErrorMessage); + await act.Should().ThrowAsync().WithMessage(SimulatedErrorMessage); _runtimePluginTestExecutionLifecycleEventEmitter.Verify(e => e.RaiseExecutionLifecycleEvent(HookType.BeforeStep, It.IsAny())); } [Fact] - public void Should_emit_runtime_plugin_test_execution_lifecycle_event_afterstep() + public async Task Should_emit_runtime_plugin_test_execution_lifecycle_event_afterstep() { var testExecutionEngine = CreateTestExecutionEngine(); RegisterStepDefinition(); - testExecutionEngine.Step(StepDefinitionKeyword.Given, null, "foo", null, null); + await testExecutionEngine.StepAsync(StepDefinitionKeyword.Given, null, "foo", null, null); _runtimePluginTestExecutionLifecycleEventEmitter.Verify(e => e.RaiseExecutionLifecycleEvent(HookType.AfterStep, It.IsAny())); } [Fact] - public void Should_emit_runtime_plugin_test_execution_lifecycle_event_afterstep_after_hook_error_and_throw_error() + public async Task Should_emit_runtime_plugin_test_execution_lifecycle_event_afterstep_after_hook_error_and_throw_error() { var testExecutionEngine = CreateTestExecutionEngine(); RegisterStepDefinition(); RegisterFailingHook(afterStepEvents); - Action act = () => + Func act = async () => { //NOTE: the exception will be re-thrown in the OnAfterLastStep - testExecutionEngine.Step(StepDefinitionKeyword.Given, null, "foo", null, null); - testExecutionEngine.OnAfterLastStep(); + await testExecutionEngine.StepAsync(StepDefinitionKeyword.Given, null, "foo", null, null); + await testExecutionEngine.OnAfterLastStepAsync(); }; - act.Should().Throw().WithMessage(SimulatedErrorMessage); + await act.Should().ThrowAsync().WithMessage(SimulatedErrorMessage); _runtimePluginTestExecutionLifecycleEventEmitter.Verify(e => e.RaiseExecutionLifecycleEvent(HookType.AfterStep, It.IsAny())); } diff --git a/Tests/TechTalk.SpecFlow.RuntimeTests/Infrastructure/TestExecutionEngineTests.cs b/Tests/TechTalk.SpecFlow.RuntimeTests/Infrastructure/TestExecutionEngineTests.cs index 374ef61dd..ee6fe2377 100644 --- a/Tests/TechTalk.SpecFlow.RuntimeTests/Infrastructure/TestExecutionEngineTests.cs +++ b/Tests/TechTalk.SpecFlow.RuntimeTests/Infrastructure/TestExecutionEngineTests.cs @@ -1,191 +1,190 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Reflection; -using BoDi; -using Moq; -using Xunit; -using TechTalk.SpecFlow.BindingSkeletons; -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; -using TechTalk.SpecFlow.UnitTestProvider; -using FluentAssertions; -using TechTalk.SpecFlow.Analytics; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using BoDi; +using Moq; +using Xunit; +using TechTalk.SpecFlow.BindingSkeletons; +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; +using TechTalk.SpecFlow.UnitTestProvider; +using FluentAssertions; +using TechTalk.SpecFlow.Analytics; using TechTalk.SpecFlow.Events; using TechTalk.SpecFlow.Plugins; - -namespace TechTalk.SpecFlow.RuntimeTests.Infrastructure + +namespace TechTalk.SpecFlow.RuntimeTests.Infrastructure { public partial class TestExecutionEngineTests - { - private ScenarioContext scenarioContext; - private SpecFlowConfiguration specFlowConfiguration; - private Mock bindingRegistryStub; - private Mock errorProviderStub; - private Mock contextManagerStub; - private Mock testTracerStub; - private Mock stepDefinitionMatcherStub; - private Mock methodBindingInvokerMock; - private Mock stepDefinitionSkeletonProviderMock; - private Mock testObjectResolverMock; - private Mock obsoleteTestHandlerMock; - private FeatureInfo featureInfo; - private ScenarioInfo scenarioInfo; - private ObjectContainer testThreadContainer; - private ObjectContainer featureContainer; - private ObjectContainer scenarioContainer; - private TestObjectResolver defaultTestObjectResolver = new TestObjectResolver(); - private ITestPendingMessageFactory _testPendingMessageFactory; - private ITestUndefinedMessageFactory _testUndefinedMessageFactory; - private Mock _analyticsEventProvider; - private Mock _analyticsTransmitter; - private Mock _testRunnerManager; + { + private ScenarioContext scenarioContext; + private SpecFlowConfiguration specFlowConfiguration; + private Mock bindingRegistryStub; + private Mock errorProviderStub; + private Mock contextManagerStub; + private Mock testTracerStub; + private Mock stepDefinitionMatcherStub; + private Mock methodBindingInvokerMock; + private Mock stepDefinitionSkeletonProviderMock; + private Mock testObjectResolverMock; + private Mock obsoleteTestHandlerMock; + private FeatureInfo featureInfo; + private ScenarioInfo scenarioInfo; + private ObjectContainer testThreadContainer; + private ObjectContainer featureContainer; + private ObjectContainer scenarioContainer; + private TestObjectResolver defaultTestObjectResolver = new TestObjectResolver(); + private ITestPendingMessageFactory _testPendingMessageFactory; + private ITestUndefinedMessageFactory _testUndefinedMessageFactory; + private Mock _analyticsEventProvider; + private Mock _analyticsTransmitter; + private Mock _testRunnerManager; private Mock _runtimePluginTestExecutionLifecycleEventEmitter; private Mock _testThreadExecutionEventPublisher; private Mock _stepArgumentTypeConverterMock; - - private List beforeScenarioEvents; - private List afterScenarioEvents; - private List beforeStepEvents; - private List afterStepEvents; - private List beforeFeatureEvents; - private List afterFeatureEvents; - private List beforeTestRunEvents; - private List afterTestRunEvents; - private List beforeScenarioBlockEvents; - private List afterScenarioBlockEvents; - + + private List beforeScenarioEvents; + private List afterScenarioEvents; + private List beforeStepEvents; + private List afterStepEvents; + private List beforeFeatureEvents; + private List afterFeatureEvents; + private List beforeTestRunEvents; + private List afterTestRunEvents; + private List beforeScenarioBlockEvents; + private List afterScenarioBlockEvents; + private List stepTransformations; - - class DummyClass - { - public static DummyClass LastInstance = null; - public DummyClass() - { - LastInstance = this; - } - } - + + class DummyClass + { + public static DummyClass LastInstance = null; + public DummyClass() + { + LastInstance = this; + } + } + class AnotherDummyClass { } - public TestExecutionEngineTests() - { - specFlowConfiguration = ConfigurationLoader.GetDefault(); - - testThreadContainer = new ObjectContainer(); - featureContainer = new ObjectContainer(); - scenarioContainer = new ObjectContainer(); - - beforeScenarioEvents = new List(); - afterScenarioEvents = new List(); - beforeStepEvents = new List(); - afterStepEvents = new List(); - beforeFeatureEvents = new List(); - afterFeatureEvents = new List(); - beforeTestRunEvents = new List(); - afterTestRunEvents = new List(); - beforeScenarioBlockEvents = new List(); - afterScenarioBlockEvents = new List(); - + public TestExecutionEngineTests() + { + specFlowConfiguration = ConfigurationLoader.GetDefault(); + + testThreadContainer = new ObjectContainer(); + featureContainer = new ObjectContainer(); + scenarioContainer = new ObjectContainer(); + + beforeScenarioEvents = new List(); + afterScenarioEvents = new List(); + beforeStepEvents = new List(); + afterStepEvents = new List(); + beforeFeatureEvents = new List(); + afterFeatureEvents = new List(); + beforeTestRunEvents = new List(); + afterTestRunEvents = new List(); + beforeScenarioBlockEvents = new List(); + afterScenarioBlockEvents = new List(); + stepTransformations = new List(); - stepDefinitionSkeletonProviderMock = new Mock(); - testObjectResolverMock = new Mock(); - testObjectResolverMock.Setup(bir => bir.ResolveBindingInstance(It.IsAny(), It.IsAny())) - .Returns((Type t, IObjectContainer container) => defaultTestObjectResolver.ResolveBindingInstance(t, container)); - - var culture = new CultureInfo("en-US", false); - contextManagerStub = new Mock(); - scenarioInfo = new ScenarioInfo("scenario_title", "scenario_description", null, null); - scenarioContext = new ScenarioContext(scenarioContainer, scenarioInfo, testObjectResolverMock.Object); - scenarioContainer.RegisterInstanceAs(scenarioContext); - contextManagerStub.Setup(cm => cm.ScenarioContext).Returns(scenarioContext); + stepDefinitionSkeletonProviderMock = new Mock(); + testObjectResolverMock = new Mock(); + testObjectResolverMock.Setup(bir => bir.ResolveBindingInstance(It.IsAny(), It.IsAny())) + .Returns((Type t, IObjectContainer container) => defaultTestObjectResolver.ResolveBindingInstance(t, container)); + + var culture = new CultureInfo("en-US", false); + contextManagerStub = new Mock(); + scenarioInfo = new ScenarioInfo("scenario_title", "scenario_description", null, null); + scenarioContext = new ScenarioContext(scenarioContainer, scenarioInfo, testObjectResolverMock.Object); + scenarioContainer.RegisterInstanceAs(scenarioContext); + contextManagerStub.Setup(cm => cm.ScenarioContext).Returns(scenarioContext); featureInfo = new FeatureInfo(culture, "feature path", "feature_title", "", ProgrammingLanguage.CSharp); - var featureContext = new FeatureContext(featureContainer, featureInfo, specFlowConfiguration); - featureContainer.RegisterInstanceAs(featureContext); - contextManagerStub.Setup(cm => cm.FeatureContext).Returns(featureContext); - contextManagerStub.Setup(cm => cm.StepContext).Returns(new ScenarioStepContext(new StepInfo(StepDefinitionType.Given, "step_title", null, null))); - - bindingRegistryStub = new Mock(); - bindingRegistryStub.Setup(br => br.GetHooks(HookType.BeforeStep)).Returns(beforeStepEvents); - bindingRegistryStub.Setup(br => br.GetHooks(HookType.AfterStep)).Returns(afterStepEvents); - bindingRegistryStub.Setup(br => br.GetHooks(HookType.BeforeScenarioBlock)).Returns(beforeScenarioBlockEvents); - bindingRegistryStub.Setup(br => br.GetHooks(HookType.AfterScenarioBlock)).Returns(afterScenarioBlockEvents); - bindingRegistryStub.Setup(br => br.GetHooks(HookType.BeforeFeature)).Returns(beforeFeatureEvents); - bindingRegistryStub.Setup(br => br.GetHooks(HookType.AfterFeature)).Returns(afterFeatureEvents); - bindingRegistryStub.Setup(br => br.GetHooks(HookType.BeforeTestRun)).Returns(beforeTestRunEvents); - bindingRegistryStub.Setup(br => br.GetHooks(HookType.AfterTestRun)).Returns(afterTestRunEvents); - bindingRegistryStub.Setup(br => br.GetHooks(HookType.BeforeScenario)).Returns(beforeScenarioEvents); - bindingRegistryStub.Setup(br => br.GetHooks(HookType.AfterScenario)).Returns(afterScenarioEvents); + var featureContext = new FeatureContext(featureContainer, featureInfo, specFlowConfiguration); + featureContainer.RegisterInstanceAs(featureContext); + contextManagerStub.Setup(cm => cm.FeatureContext).Returns(featureContext); + contextManagerStub.Setup(cm => cm.StepContext).Returns(new ScenarioStepContext(new StepInfo(StepDefinitionType.Given, "step_title", null, null))); + + bindingRegistryStub = new Mock(); + bindingRegistryStub.Setup(br => br.GetHooks(HookType.BeforeStep)).Returns(beforeStepEvents); + bindingRegistryStub.Setup(br => br.GetHooks(HookType.AfterStep)).Returns(afterStepEvents); + bindingRegistryStub.Setup(br => br.GetHooks(HookType.BeforeScenarioBlock)).Returns(beforeScenarioBlockEvents); + bindingRegistryStub.Setup(br => br.GetHooks(HookType.AfterScenarioBlock)).Returns(afterScenarioBlockEvents); + bindingRegistryStub.Setup(br => br.GetHooks(HookType.BeforeFeature)).Returns(beforeFeatureEvents); + bindingRegistryStub.Setup(br => br.GetHooks(HookType.AfterFeature)).Returns(afterFeatureEvents); + bindingRegistryStub.Setup(br => br.GetHooks(HookType.BeforeTestRun)).Returns(beforeTestRunEvents); + bindingRegistryStub.Setup(br => br.GetHooks(HookType.AfterTestRun)).Returns(afterTestRunEvents); + bindingRegistryStub.Setup(br => br.GetHooks(HookType.BeforeScenario)).Returns(beforeScenarioEvents); + bindingRegistryStub.Setup(br => br.GetHooks(HookType.AfterScenario)).Returns(afterScenarioEvents); bindingRegistryStub.Setup(br => br.GetStepTransformations()).Returns(stepTransformations); - specFlowConfiguration = ConfigurationLoader.GetDefault(); - errorProviderStub = new Mock(); - testTracerStub = new Mock(); - stepDefinitionMatcherStub = new Mock(); - methodBindingInvokerMock = new Mock(); - - obsoleteTestHandlerMock = new Mock(); - - - - _testPendingMessageFactory = new TestPendingMessageFactory(errorProviderStub.Object); - _testUndefinedMessageFactory = new TestUndefinedMessageFactory(stepDefinitionSkeletonProviderMock.Object, errorProviderStub.Object, specFlowConfiguration); - - _analyticsEventProvider = new Mock(); - _analyticsTransmitter = new Mock(); - _analyticsTransmitter.Setup(at => at.TransmitSpecFlowProjectRunningEvent(It.IsAny())) - .Callback(() => { }); - - _testRunnerManager = new Mock(); - _testRunnerManager.Setup(trm => trm.TestAssembly).Returns(Assembly.GetCallingAssembly); + specFlowConfiguration = ConfigurationLoader.GetDefault(); + errorProviderStub = new Mock(); + testTracerStub = new Mock(); + stepDefinitionMatcherStub = new Mock(); + methodBindingInvokerMock = new Mock(); + + obsoleteTestHandlerMock = new Mock(); + + _testPendingMessageFactory = new TestPendingMessageFactory(errorProviderStub.Object); + _testUndefinedMessageFactory = new TestUndefinedMessageFactory(stepDefinitionSkeletonProviderMock.Object, errorProviderStub.Object, specFlowConfiguration); + + _analyticsEventProvider = new Mock(); + _analyticsTransmitter = new Mock(); + _analyticsTransmitter.Setup(at => at.TransmitSpecFlowProjectRunningEventAsync(It.IsAny())) + .Callback(() => { }); + + _testRunnerManager = new Mock(); + _testRunnerManager.Setup(trm => trm.TestAssembly).Returns(Assembly.GetCallingAssembly); _runtimePluginTestExecutionLifecycleEventEmitter = new Mock(); _testThreadExecutionEventPublisher = new Mock(); _stepArgumentTypeConverterMock = new Mock(); - } - - private TestExecutionEngine CreateTestExecutionEngine() - { - return new TestExecutionEngine( + } + + private TestExecutionEngine CreateTestExecutionEngine() + { + return new TestExecutionEngine( new Mock().Object, testTracerStub.Object, errorProviderStub.Object, _stepArgumentTypeConverterMock.Object, specFlowConfiguration, - bindingRegistryStub.Object, + bindingRegistryStub.Object, new Mock().Object, contextManagerStub.Object, stepDefinitionMatcherStub.Object, - methodBindingInvokerMock.Object, - obsoleteTestHandlerMock.Object, - _analyticsEventProvider.Object, - _analyticsTransmitter.Object, - _testRunnerManager.Object, + methodBindingInvokerMock.Object, + obsoleteTestHandlerMock.Object, + _analyticsEventProvider.Object, + _analyticsTransmitter.Object, + _testRunnerManager.Object, _runtimePluginTestExecutionLifecycleEventEmitter.Object, _testThreadExecutionEventPublisher.Object, _testPendingMessageFactory, _testUndefinedMessageFactory, - testObjectResolverMock.Object, - testThreadContainer); - } - + testObjectResolverMock.Object, + testThreadContainer); + } + private Mock RegisterStepDefinition() - { + { var methodStub = new Mock(); var stepDefStub = new Mock(); stepDefStub.Setup(sd => sd.Method).Returns(methodStub.Object); - + StepDefinitionAmbiguityReason ambiguityReason; List candidatingMatches; stepDefinitionMatcherStub.Setup(sdm => sdm.GetBestMatch(It.IsAny(), It.IsAny(), out ambiguityReason, out candidatingMatches)) @@ -193,430 +192,426 @@ private Mock RegisterStepDefinition() new BindingMatch(stepDefStub.Object, 0, new object[0], new StepContext("bla", "foo", new List(), CultureInfo.InvariantCulture))); return stepDefStub; - } - + } + private Mock RegisterStepDefinitionWithTransformation(IBindingType bindingType) { var bindingParameterStub = new Mock(); bindingParameterStub.Setup(bp => bp.Type).Returns(bindingType); - var methodStub = new Mock(); + var methodStub = new Mock(); methodStub.Setup(m => m.Parameters).Returns(new[] { bindingParameterStub.Object }); - var stepDefStub = new Mock(); - stepDefStub.Setup(sd => sd.Method).Returns(methodStub.Object); - - StepDefinitionAmbiguityReason ambiguityReason; - List candidatingMatches; - stepDefinitionMatcherStub.Setup(sdm => sdm.GetBestMatch(It.IsAny(), It.IsAny(), out ambiguityReason, out candidatingMatches)) - .Returns( + var stepDefStub = new Mock(); + stepDefStub.Setup(sd => sd.Method).Returns(methodStub.Object); + + StepDefinitionAmbiguityReason ambiguityReason; + List candidatingMatches; + stepDefinitionMatcherStub.Setup(sdm => sdm.GetBestMatch(It.IsAny(), It.IsAny(), out ambiguityReason, out candidatingMatches)) + .Returns( new BindingMatch(stepDefStub.Object, 0, new object[] { "userName" }, new StepContext("bla", "foo", new List(), CultureInfo.InvariantCulture))); - - return stepDefStub; - } - - private Mock RegisterUndefinedStepDefinition() - { - var methodStub = new Mock(); - var stepDefStub = new Mock(); - stepDefStub.Setup(sd => sd.Method).Returns(methodStub.Object); - - StepDefinitionAmbiguityReason ambiguityReason; - List candidatingMatches; - stepDefinitionMatcherStub.Setup(sdm => sdm.GetBestMatch(It.IsAny(), It.IsAny(), out ambiguityReason, out candidatingMatches)) - .Returns(BindingMatch.NonMatching); - - return stepDefStub; - } - + + return stepDefStub; + } + + private Mock RegisterUndefinedStepDefinition() + { + var methodStub = new Mock(); + var stepDefStub = new Mock(); + stepDefStub.Setup(sd => sd.Method).Returns(methodStub.Object); + + StepDefinitionAmbiguityReason ambiguityReason; + List candidatingMatches; + stepDefinitionMatcherStub.Setup(sdm => sdm.GetBestMatch(It.IsAny(), It.IsAny(), out ambiguityReason, out candidatingMatches)) + .Returns(BindingMatch.NonMatching); + + return stepDefStub; + } + private void RegisterFailingStepDefinition(TimeSpan? expectedDuration = null) - { - var stepDefStub = RegisterStepDefinition(); - TimeSpan duration; - if (expectedDuration.HasValue) duration = expectedDuration.Value; - methodBindingInvokerMock.Setup(i => i.InvokeBinding(stepDefStub.Object, contextManagerStub.Object, It.IsAny(), testTracerStub.Object, out duration)) - .Throws(new Exception("simulated error")); - } - - private Mock CreateHookMock(List hookList) - { - var mock = new Mock(); - hookList.Add(mock.Object); - return mock; - } - - private Mock CreateParametrizedHookMock(List hookList, params Type[] paramTypes) - { - var hookMock = CreateHookMock(hookList); - var bindingMethod = new BindingMethod(new BindingType("AssemblyBT", "BT", "Test.BT"), "X", + { + var stepDefStub = RegisterStepDefinition(); + + methodBindingInvokerMock.Setup(i => i.InvokeBindingAsync(stepDefStub.Object, contextManagerStub.Object, It.IsAny(), testTracerStub.Object, It.IsAny())) + .Callback((IBinding _, IContextManager _, object[] arguments, ITestTracer _, DurationHolder durationHolder) => + { + if (expectedDuration.HasValue) + { + durationHolder.Duration = expectedDuration.Value; + } + }) + .ThrowsAsync(new Exception("simulated error")); + } + + private Mock CreateHookMock(List hookList) + { + var mock = new Mock(); + hookList.Add(mock.Object); + return mock; + } + + private Mock CreateParametrizedHookMock(List hookList, params Type[] paramTypes) + { + var hookMock = CreateHookMock(hookList); + var bindingMethod = new BindingMethod(new BindingType("AssemblyBT", "BT", "Test.BT"), "X", paramTypes.Select((paramType, i) => new BindingParameter(new RuntimeBindingType(paramType), "p" + i)), - RuntimeBindingType.Void); - hookMock.Setup(h => h.Method).Returns(bindingMethod); - return hookMock; - } - + RuntimeBindingType.Void); + hookMock.Setup(h => h.Method).Returns(bindingMethod); + return hookMock; + } + private IStepArgumentTransformationBinding CreateStepTransformationBinding(string regexString, IBindingMethod transformMethod) { return new StepArgumentTransformationBinding(regexString, transformMethod); } - private void AssertHooksWasCalledWithParam(Mock hookMock, object paramObj) - { - TimeSpan duration; - methodBindingInvokerMock.Verify(i => i.InvokeBinding(hookMock.Object, contextManagerStub.Object, - It.Is((object[] args) => args != null && args.Length > 0 && args.Any(arg => arg == paramObj)), - testTracerStub.Object, out duration), Times.Once()); - } - - [Fact] - public void Should_execute_before_step() - { - var testExecutionEngine = CreateTestExecutionEngine(); - RegisterStepDefinition(); - - var hookMock = CreateHookMock(beforeStepEvents); - - testExecutionEngine.Step(StepDefinitionKeyword.Given, null, "foo", null, null); - - TimeSpan duration; - methodBindingInvokerMock.Verify(i => i.InvokeBinding(hookMock.Object, contextManagerStub.Object, null, testTracerStub.Object, out duration), Times.Once()); - } - - [Fact] - public void Should_execute_after_step() - { - var testExecutionEngine = CreateTestExecutionEngine(); - RegisterStepDefinition(); - - var hookMock = CreateHookMock(afterStepEvents); - - testExecutionEngine.Step(StepDefinitionKeyword.Given, null, "foo", null, null); - - TimeSpan duration; - methodBindingInvokerMock.Verify(i => i.InvokeBinding(hookMock.Object, contextManagerStub.Object, null, testTracerStub.Object, out duration), Times.Once()); - } - - [Fact] - public void Should_not_execute_step_when_there_was_an_error_earlier() - { - var testExecutionEngine = CreateTestExecutionEngine(); - var stepDefMock = RegisterStepDefinition(); - - scenarioContext.ScenarioExecutionStatus = ScenarioExecutionStatus.TestError; - - testExecutionEngine.Step(StepDefinitionKeyword.Given, null, "foo", null, null); - - TimeSpan duration; - methodBindingInvokerMock.Verify(i => i.InvokeBinding(stepDefMock.Object, It.IsAny(), It.IsAny(), It.IsAny(), out duration), Times.Never()); - } - - [Fact] - public void Should_not_execute_step_hooks_when_there_was_an_error_earlier() - { - var testExecutionEngine = CreateTestExecutionEngine(); - RegisterStepDefinition(); - - scenarioContext.ScenarioExecutionStatus = ScenarioExecutionStatus.TestError; - - var beforeStepMock = CreateHookMock(beforeStepEvents); - var afterStepMock = CreateHookMock(afterStepEvents); - - testExecutionEngine.Step(StepDefinitionKeyword.Given, null, "foo", null, null); - - TimeSpan duration; - methodBindingInvokerMock.Verify(i => i.InvokeBinding(beforeStepMock.Object, contextManagerStub.Object, null, testTracerStub.Object, out duration), Times.Never()); - methodBindingInvokerMock.Verify(i => i.InvokeBinding(afterStepMock.Object, contextManagerStub.Object, null, testTracerStub.Object, out duration), Times.Never()); - } - - [Fact] - public void Should_not_execute_step_argument_transformations_when_there_was_an_error_earlier() - { - var testExecutionEngine = CreateTestExecutionEngine(); - + private void AssertHooksWasCalledWithParam(Mock hookMock, object paramObj) + { + methodBindingInvokerMock.Verify(i => i.InvokeBindingAsync(hookMock.Object, contextManagerStub.Object, + It.Is((object[] args) => args != null && args.Length > 0 && args.Any(arg => arg == paramObj)), + testTracerStub.Object, It.IsAny()), Times.Once()); + } + + [Fact] + public async Task Should_execute_before_step() + { + var testExecutionEngine = CreateTestExecutionEngine(); + RegisterStepDefinition(); + + var hookMock = CreateHookMock(beforeStepEvents); + + await testExecutionEngine.StepAsync(StepDefinitionKeyword.Given, null, "foo", null, null); + + methodBindingInvokerMock.Verify(i => i.InvokeBindingAsync(hookMock.Object, contextManagerStub.Object, null, testTracerStub.Object, It.IsAny()), Times.Once()); + } + + [Fact] + public async Task Should_execute_after_step() + { + var testExecutionEngine = CreateTestExecutionEngine(); + RegisterStepDefinition(); + + var hookMock = CreateHookMock(afterStepEvents); + + await testExecutionEngine.StepAsync(StepDefinitionKeyword.Given, null, "foo", null, null); + + methodBindingInvokerMock.Verify(i => i.InvokeBindingAsync(hookMock.Object, contextManagerStub.Object, null, testTracerStub.Object, It.IsAny()), Times.Once()); + } + + [Fact] + public async Task Should_not_execute_step_when_there_was_an_error_earlier() + { + var testExecutionEngine = CreateTestExecutionEngine(); + var stepDefMock = RegisterStepDefinition(); + + scenarioContext.ScenarioExecutionStatus = ScenarioExecutionStatus.TestError; + + await testExecutionEngine.StepAsync(StepDefinitionKeyword.Given, null, "foo", null, null); + + methodBindingInvokerMock.Verify(i => i.InvokeBindingAsync(stepDefMock.Object, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never()); + } + + [Fact] + public async Task Should_not_execute_step_hooks_when_there_was_an_error_earlier() + { + var testExecutionEngine = CreateTestExecutionEngine(); + RegisterStepDefinition(); + + scenarioContext.ScenarioExecutionStatus = ScenarioExecutionStatus.TestError; + + var beforeStepMock = CreateHookMock(beforeStepEvents); + var afterStepMock = CreateHookMock(afterStepEvents); + + await testExecutionEngine.StepAsync(StepDefinitionKeyword.Given, null, "foo", null, null); + + methodBindingInvokerMock.Verify(i => i.InvokeBindingAsync(beforeStepMock.Object, contextManagerStub.Object, null, testTracerStub.Object, It.IsAny()), Times.Never()); + methodBindingInvokerMock.Verify(i => i.InvokeBindingAsync(afterStepMock.Object, contextManagerStub.Object, null, testTracerStub.Object, It.IsAny()), Times.Never()); + } + + [Fact] + public async Task Should_not_execute_step_argument_transformations_when_there_was_an_error_earlier() + { + var testExecutionEngine = CreateTestExecutionEngine(); + var bindingTypeStub = new Mock(); RegisterStepDefinitionWithTransformation(bindingTypeStub.Object); - + scenarioContext.ScenarioExecutionStatus = ScenarioExecutionStatus.TestError; - + UserCreator stepTransformationInstance = new UserCreator(); var transformMethod = new RuntimeBindingMethod(stepTransformationInstance.GetType().GetMethod("Create")); var stepTransformationBinding = CreateStepTransformationBinding(@"user (\w+)", transformMethod); stepTransformations.Add(stepTransformationBinding); - - testExecutionEngine.Step(StepDefinitionKeyword.Given, null, "user bar", null, null); - - _stepArgumentTypeConverterMock.Verify(i => i.Convert(It.IsAny(), bindingTypeStub.Object, It.IsAny()), Times.Never); - } - - [Fact] - public void Should_execute_after_step_when_step_definition_failed() - { - var testExecutionEngine = CreateTestExecutionEngine(); - RegisterFailingStepDefinition(); - + + await testExecutionEngine.StepAsync(StepDefinitionKeyword.Given, null, "user bar", null, null); + + _stepArgumentTypeConverterMock.Verify(i => i.ConvertAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + } + + [Fact] + public async Task Should_execute_after_step_when_step_definition_failed() + { + var testExecutionEngine = CreateTestExecutionEngine(); + RegisterFailingStepDefinition(); + var hookMock = CreateHookMock(afterStepEvents); - - testExecutionEngine.Step(StepDefinitionKeyword.Given, null, "foo", null, null); - - TimeSpan duration; - methodBindingInvokerMock.Verify(i => i.InvokeBinding(hookMock.Object, contextManagerStub.Object, null, testTracerStub.Object, out duration)); - } - - [Fact] - public void Should_cleanup_step_context_after_scenario_block_hook_error() - { - TimeSpan duration; - var testExecutionEngine = CreateTestExecutionEngine(); - RegisterStepDefinition(); - - var hookMock = CreateHookMock(beforeScenarioBlockEvents); - methodBindingInvokerMock.Setup(i => i.InvokeBinding(hookMock.Object, contextManagerStub.Object, null, testTracerStub.Object, out duration)) - .Throws(new Exception("simulated error")); - - try - { - testExecutionEngine.Step(StepDefinitionKeyword.Given, null, "foo", null, null); - - Assert.True(false, "execution of the step should have failed because of the exeption thrown by the before scenario block hook"); - } - catch (Exception) - { - } - - methodBindingInvokerMock.Verify(i => i.InvokeBinding(hookMock.Object, contextManagerStub.Object, null, testTracerStub.Object, out duration), Times.Once()); - contextManagerStub.Verify(cm => cm.CleanupStepContext()); - } - - [Fact] - public void Should_not_execute_afterstep_when_step_is_undefined() - { - var testExecutionEngine = CreateTestExecutionEngine(); - RegisterUndefinedStepDefinition(); - - var afterStepMock = CreateHookMock(afterStepEvents); - - testExecutionEngine.Step(StepDefinitionKeyword.Given, null, "undefined", null, null); - - TimeSpan duration; - methodBindingInvokerMock.Verify(i => i.InvokeBinding(afterStepMock.Object, contextManagerStub.Object, null, testTracerStub.Object, out duration), Times.Never()); - } + + await testExecutionEngine.StepAsync(StepDefinitionKeyword.Given, null, "foo", null, null); + + methodBindingInvokerMock.Verify(i => i.InvokeBindingAsync(hookMock.Object, contextManagerStub.Object, null, testTracerStub.Object, It.IsAny())); + } + + [Fact] + public async Task Should_cleanup_step_context_after_scenario_block_hook_error() + { + var testExecutionEngine = CreateTestExecutionEngine(); + RegisterStepDefinition(); + + var hookMock = CreateHookMock(beforeScenarioBlockEvents); + methodBindingInvokerMock.Setup(i => i.InvokeBindingAsync(hookMock.Object, contextManagerStub.Object, null, testTracerStub.Object, It.IsAny())) + .Throws(new Exception("simulated error")); + + try + { + await testExecutionEngine.StepAsync(StepDefinitionKeyword.Given, null, "foo", null, null); + + Assert.True(false, "execution of the step should have failed because of the exeption thrown by the before scenario block hook"); + } + catch (Exception) + { + } + + methodBindingInvokerMock.Verify(i => i.InvokeBindingAsync(hookMock.Object, contextManagerStub.Object, null, testTracerStub.Object, It.IsAny()), Times.Once()); + contextManagerStub.Verify(cm => cm.CleanupStepContext()); + } + + [Fact] + public async Task Should_not_execute_afterstep_when_step_is_undefined() + { + var testExecutionEngine = CreateTestExecutionEngine(); + RegisterUndefinedStepDefinition(); + + var afterStepMock = CreateHookMock(afterStepEvents); + + await testExecutionEngine.StepAsync(StepDefinitionKeyword.Given, null, "undefined", null, null); + + methodBindingInvokerMock.Verify(i => i.InvokeBindingAsync(afterStepMock.Object, contextManagerStub.Object, null, testTracerStub.Object, It.IsAny()), Times.Never()); + } - [Fact] - public void Should_cleanup_scenario_context_on_scenario_end() - { - var testExecutionEngine = CreateTestExecutionEngine(); - RegisterStepDefinition(); - + [Fact] + public async Task Should_cleanup_scenario_context_on_scenario_end() + { + var testExecutionEngine = CreateTestExecutionEngine(); + RegisterStepDefinition(); + testExecutionEngine.OnScenarioInitialize(scenarioInfo); - testExecutionEngine.OnScenarioStart(); - testExecutionEngine.OnScenarioEnd(); + await testExecutionEngine.OnScenarioStartAsync(); + await testExecutionEngine.OnScenarioEndAsync(); contextManagerStub.Verify(cm => cm.CleanupScenarioContext(), Times.Once); } - [Fact] - public void Should_cleanup_scenario_context_after_AfterScenario_hook_error() + [Fact] + public async Task Should_cleanup_scenario_context_after_AfterScenario_hook_error() { - TimeSpan duration; var testExecutionEngine = CreateTestExecutionEngine(); RegisterStepDefinition(); var afterHook = CreateParametrizedHookMock(afterScenarioEvents, typeof(DummyClass)); var hookMock = CreateHookMock(afterScenarioEvents); - methodBindingInvokerMock.Setup(i => i.InvokeBinding(hookMock.Object, contextManagerStub.Object, null, testTracerStub.Object, out duration)) + methodBindingInvokerMock.Setup(i => i.InvokeBindingAsync(hookMock.Object, contextManagerStub.Object, null, testTracerStub.Object, It.IsAny())) .Throws(new Exception("simulated error")); testExecutionEngine.OnScenarioInitialize(scenarioInfo); - testExecutionEngine.OnScenarioStart(); - Action act = () => testExecutionEngine.OnScenarioEnd(); + await testExecutionEngine.OnScenarioStartAsync(); + Func act = async () => await testExecutionEngine.OnScenarioEndAsync(); - act.Should().Throw().WithMessage("simulated error"); + await act.Should().ThrowAsync().WithMessage("simulated error"); contextManagerStub.Verify(cm => cm.CleanupScenarioContext(), Times.Once); } - [Fact] - public void Should_resolve_FeatureContext_hook_parameter() - { - var testExecutionEngine = CreateTestExecutionEngine(); - RegisterStepDefinition(); - - var hookMock = CreateParametrizedHookMock(beforeFeatureEvents, typeof(FeatureContext)); - - testExecutionEngine.OnFeatureStart(featureInfo); - AssertHooksWasCalledWithParam(hookMock, contextManagerStub.Object.FeatureContext); - } - - [Fact] - public void Should_resolve_custom_class_hook_parameter() - { - var testExecutionEngine = CreateTestExecutionEngine(); - RegisterStepDefinition(); - - var hookMock = CreateParametrizedHookMock(beforeFeatureEvents, typeof(DummyClass)); - - testExecutionEngine.OnFeatureStart(featureInfo); - AssertHooksWasCalledWithParam(hookMock, DummyClass.LastInstance); - } - - [Fact] - public void Should_resolve_container_hook_parameter() + [Fact] + public async Task Should_resolve_FeatureContext_hook_parameter() { var testExecutionEngine = CreateTestExecutionEngine(); RegisterStepDefinition(); - var hookMock = CreateParametrizedHookMock(beforeTestRunEvents, typeof(IObjectContainer)); - - testExecutionEngine.OnTestRunStart(); - - AssertHooksWasCalledWithParam(hookMock, testThreadContainer); - } - - [Fact] - public void Should_resolve_multiple_hook_parameter() - { - var testExecutionEngine = CreateTestExecutionEngine(); - RegisterStepDefinition(); - - var hookMock = CreateParametrizedHookMock(beforeFeatureEvents, typeof(DummyClass), typeof(FeatureContext)); - - testExecutionEngine.OnFeatureStart(featureInfo); - AssertHooksWasCalledWithParam(hookMock, DummyClass.LastInstance); - AssertHooksWasCalledWithParam(hookMock, contextManagerStub.Object.FeatureContext); - } - - [Fact] - public void Should_resolve_BeforeAfterTestRun_hook_parameter_from_test_thread_container() - { - var testExecutionEngine = CreateTestExecutionEngine(); - RegisterStepDefinition(); - - var beforeHook = CreateParametrizedHookMock(beforeTestRunEvents, typeof(DummyClass)); - var afterHook = CreateParametrizedHookMock(afterTestRunEvents, typeof(DummyClass)); - - testExecutionEngine.OnTestRunStart(); - testExecutionEngine.OnTestRunEnd(); - - AssertHooksWasCalledWithParam(beforeHook, DummyClass.LastInstance); - AssertHooksWasCalledWithParam(afterHook, DummyClass.LastInstance); + var hookMock = CreateParametrizedHookMock(beforeFeatureEvents, typeof(FeatureContext)); + + await testExecutionEngine.OnFeatureStartAsync(featureInfo); + AssertHooksWasCalledWithParam(hookMock, contextManagerStub.Object.FeatureContext); + } + + [Fact] + public async Task Should_resolve_custom_class_hook_parameter() + { + var testExecutionEngine = CreateTestExecutionEngine(); + RegisterStepDefinition(); + + var hookMock = CreateParametrizedHookMock(beforeFeatureEvents, typeof(DummyClass)); + + await testExecutionEngine.OnFeatureStartAsync(featureInfo); + AssertHooksWasCalledWithParam(hookMock, DummyClass.LastInstance); + } + + [Fact] + public async Task Should_resolve_container_hook_parameter() + { + var testExecutionEngine = CreateTestExecutionEngine(); + RegisterStepDefinition(); + + var hookMock = CreateParametrizedHookMock(beforeTestRunEvents, typeof(IObjectContainer)); + + await testExecutionEngine.OnTestRunStartAsync(); + + AssertHooksWasCalledWithParam(hookMock, testThreadContainer); + } + + [Fact] + public async Task Should_resolve_multiple_hook_parameter() + { + var testExecutionEngine = CreateTestExecutionEngine(); + RegisterStepDefinition(); + + var hookMock = CreateParametrizedHookMock(beforeFeatureEvents, typeof(DummyClass), typeof(FeatureContext)); + + await testExecutionEngine.OnFeatureStartAsync(featureInfo); + AssertHooksWasCalledWithParam(hookMock, DummyClass.LastInstance); + AssertHooksWasCalledWithParam(hookMock, contextManagerStub.Object.FeatureContext); + } + + [Fact] + public async Task Should_resolve_BeforeAfterTestRun_hook_parameter_from_test_thread_container() + { + var testExecutionEngine = CreateTestExecutionEngine(); + RegisterStepDefinition(); + + var beforeHook = CreateParametrizedHookMock(beforeTestRunEvents, typeof(DummyClass)); + var afterHook = CreateParametrizedHookMock(afterTestRunEvents, typeof(DummyClass)); + + await testExecutionEngine.OnTestRunStartAsync(); + await testExecutionEngine.OnTestRunEndAsync(); + + AssertHooksWasCalledWithParam(beforeHook, DummyClass.LastInstance); + AssertHooksWasCalledWithParam(afterHook, DummyClass.LastInstance); testObjectResolverMock.Verify(bir => bir.ResolveBindingInstance(typeof(DummyClass), testThreadContainer), - Times.Exactly(2)); - } - - [Fact] - public void Should_resolve_BeforeAfterScenario_hook_parameter_from_scenario_container() - { - var testExecutionEngine = CreateTestExecutionEngine(); - RegisterStepDefinition(); - - var beforeHook = CreateParametrizedHookMock(beforeScenarioEvents, typeof(DummyClass)); - var afterHook = CreateParametrizedHookMock(afterScenarioEvents, typeof(DummyClass)); - - testExecutionEngine.OnScenarioInitialize(scenarioInfo); - testExecutionEngine.OnScenarioStart(); - testExecutionEngine.OnScenarioEnd(); - - AssertHooksWasCalledWithParam(beforeHook, DummyClass.LastInstance); - AssertHooksWasCalledWithParam(afterHook, DummyClass.LastInstance); - testObjectResolverMock.Verify(bir => bir.ResolveBindingInstance(typeof(DummyClass), scenarioContainer), - Times.Exactly(2)); - } - - [Fact] - public void Should_be_possible_to_register_instance_in_scenario_container_before_firing_scenario_events() - { - var testExecutionEngine = CreateTestExecutionEngine(); - var instanceToAddBeforeScenarioEventFiring = new AnotherDummyClass(); - var beforeHook = CreateParametrizedHookMock(beforeScenarioEvents, typeof(DummyClass)); - - // Setup binding method mock so it attempts to resolve an instance from the scenario container. - // If this fails, then the instance was not registered before the method was invoked. - TimeSpan dummyOutTimeSpan; - AnotherDummyClass actualInstance = null; - methodBindingInvokerMock.Setup(s => s.InvokeBinding(It.IsAny(), It.IsAny(), - It.IsAny(),It.IsAny(), out dummyOutTimeSpan)) - .Callback(() => actualInstance = testExecutionEngine.ScenarioContext.ScenarioContainer.Resolve()); - - testExecutionEngine.OnScenarioInitialize(scenarioInfo); - testExecutionEngine.ScenarioContext.ScenarioContainer.RegisterInstanceAs(instanceToAddBeforeScenarioEventFiring); - testExecutionEngine.OnScenarioStart(); - actualInstance.Should().BeSameAs(instanceToAddBeforeScenarioEventFiring); - - AssertHooksWasCalledWithParam(beforeHook, DummyClass.LastInstance); - } - - [Fact] - public void Should_resolve_BeforeAfterScenarioBlock_hook_parameter_from_scenario_container() - { - var testExecutionEngine = CreateTestExecutionEngine(); - RegisterStepDefinition(); - - var beforeHook = CreateParametrizedHookMock(beforeScenarioBlockEvents, typeof(DummyClass)); - var afterHook = CreateParametrizedHookMock(afterScenarioBlockEvents, typeof(DummyClass)); - - testExecutionEngine.Step(StepDefinitionKeyword.Given, null, "foo", null, null); - testExecutionEngine.OnAfterLastStep(); - - AssertHooksWasCalledWithParam(beforeHook, DummyClass.LastInstance); - AssertHooksWasCalledWithParam(afterHook, DummyClass.LastInstance); - testObjectResolverMock.Verify(bir => bir.ResolveBindingInstance(typeof(DummyClass), scenarioContainer), - Times.Exactly(2)); - } - - [Fact] - public void Should_resolve_BeforeAfterStep_hook_parameter_from_scenario_container() - { - var testExecutionEngine = CreateTestExecutionEngine(); - RegisterStepDefinition(); - - var beforeHook = CreateParametrizedHookMock(beforeStepEvents, typeof(DummyClass)); - var afterHook = CreateParametrizedHookMock(afterStepEvents, typeof(DummyClass)); - - testExecutionEngine.Step(StepDefinitionKeyword.Given, null, "foo", null, null); - - AssertHooksWasCalledWithParam(beforeHook, DummyClass.LastInstance); - AssertHooksWasCalledWithParam(afterHook, DummyClass.LastInstance); - testObjectResolverMock.Verify(bir => bir.ResolveBindingInstance(typeof(DummyClass), scenarioContainer), - Times.Exactly(2)); - } - - [Fact] - public void Should_resolve_BeforeAfterFeature_hook_parameter_from_feature_container() - { - var testExecutionEngine = CreateTestExecutionEngine(); - RegisterStepDefinition(); - - var beforeHook = CreateParametrizedHookMock(beforeFeatureEvents, typeof(DummyClass)); - var afterHook = CreateParametrizedHookMock(afterFeatureEvents, typeof(DummyClass)); - - testExecutionEngine.OnFeatureStart(featureInfo); - testExecutionEngine.OnFeatureEnd(); - - AssertHooksWasCalledWithParam(beforeHook, DummyClass.LastInstance); - AssertHooksWasCalledWithParam(afterHook, DummyClass.LastInstance); - testObjectResolverMock.Verify(bir => bir.ResolveBindingInstance(typeof(DummyClass), featureContainer), - Times.Exactly(2)); - } - - [Fact] - public void Should_TryToSend_ProjectRunningEvent() - { + Times.Exactly(2)); + } + + [Fact] + public async Task Should_resolve_BeforeAfterScenario_hook_parameter_from_scenario_container() + { + var testExecutionEngine = CreateTestExecutionEngine(); + RegisterStepDefinition(); + + var beforeHook = CreateParametrizedHookMock(beforeScenarioEvents, typeof(DummyClass)); + var afterHook = CreateParametrizedHookMock(afterScenarioEvents, typeof(DummyClass)); + + testExecutionEngine.OnScenarioInitialize(scenarioInfo); + await testExecutionEngine.OnScenarioStartAsync(); + await testExecutionEngine.OnScenarioEndAsync(); + + AssertHooksWasCalledWithParam(beforeHook, DummyClass.LastInstance); + AssertHooksWasCalledWithParam(afterHook, DummyClass.LastInstance); + testObjectResolverMock.Verify(bir => bir.ResolveBindingInstance(typeof(DummyClass), scenarioContainer), + Times.Exactly(2)); + } + + [Fact] + public async Task Should_be_possible_to_register_instance_in_scenario_container_before_firing_scenario_events() + { + var testExecutionEngine = CreateTestExecutionEngine(); + var instanceToAddBeforeScenarioEventFiring = new AnotherDummyClass(); + var beforeHook = CreateParametrizedHookMock(beforeScenarioEvents, typeof(DummyClass)); + + // Setup binding method mock so it attempts to resolve an instance from the scenario container. + // If this fails, then the instance was not registered before the method was invoked. + AnotherDummyClass actualInstance = null; + methodBindingInvokerMock.Setup(s => s.InvokeBindingAsync(It.IsAny(), It.IsAny(), + It.IsAny(),It.IsAny(), It.IsAny())) + .Callback(() => actualInstance = testExecutionEngine.ScenarioContext.ScenarioContainer.Resolve()); + + testExecutionEngine.OnScenarioInitialize(scenarioInfo); + testExecutionEngine.ScenarioContext.ScenarioContainer.RegisterInstanceAs(instanceToAddBeforeScenarioEventFiring); + await testExecutionEngine.OnScenarioStartAsync(); + actualInstance.Should().BeSameAs(instanceToAddBeforeScenarioEventFiring); + + AssertHooksWasCalledWithParam(beforeHook, DummyClass.LastInstance); + } + + [Fact] + public async Task Should_resolve_BeforeAfterScenarioBlock_hook_parameter_from_scenario_container() + { + var testExecutionEngine = CreateTestExecutionEngine(); + RegisterStepDefinition(); + + var beforeHook = CreateParametrizedHookMock(beforeScenarioBlockEvents, typeof(DummyClass)); + var afterHook = CreateParametrizedHookMock(afterScenarioBlockEvents, typeof(DummyClass)); + + await testExecutionEngine.StepAsync(StepDefinitionKeyword.Given, null, "foo", null, null); + await testExecutionEngine.OnAfterLastStepAsync(); + + AssertHooksWasCalledWithParam(beforeHook, DummyClass.LastInstance); + AssertHooksWasCalledWithParam(afterHook, DummyClass.LastInstance); + testObjectResolverMock.Verify(bir => bir.ResolveBindingInstance(typeof(DummyClass), scenarioContainer), + Times.Exactly(2)); + } + + [Fact] + public async Task Should_resolve_BeforeAfterStep_hook_parameter_from_scenario_container() + { + var testExecutionEngine = CreateTestExecutionEngine(); + RegisterStepDefinition(); + + var beforeHook = CreateParametrizedHookMock(beforeStepEvents, typeof(DummyClass)); + var afterHook = CreateParametrizedHookMock(afterStepEvents, typeof(DummyClass)); + + await testExecutionEngine.StepAsync(StepDefinitionKeyword.Given, null, "foo", null, null); + + AssertHooksWasCalledWithParam(beforeHook, DummyClass.LastInstance); + AssertHooksWasCalledWithParam(afterHook, DummyClass.LastInstance); + testObjectResolverMock.Verify(bir => bir.ResolveBindingInstance(typeof(DummyClass), scenarioContainer), + Times.Exactly(2)); + } + + [Fact] + public async Task Should_resolve_BeforeAfterFeature_hook_parameter_from_feature_container() + { + var testExecutionEngine = CreateTestExecutionEngine(); + RegisterStepDefinition(); + + var beforeHook = CreateParametrizedHookMock(beforeFeatureEvents, typeof(DummyClass)); + var afterHook = CreateParametrizedHookMock(afterFeatureEvents, typeof(DummyClass)); + + await testExecutionEngine.OnFeatureStartAsync(featureInfo); + await testExecutionEngine.OnFeatureEndAsync(); + + AssertHooksWasCalledWithParam(beforeHook, DummyClass.LastInstance); + AssertHooksWasCalledWithParam(afterHook, DummyClass.LastInstance); + testObjectResolverMock.Verify(bir => bir.ResolveBindingInstance(typeof(DummyClass), featureContainer), + Times.Exactly(2)); + } + + [Fact] + public async Task Should_TryToSend_ProjectRunningEvent() + { _analyticsTransmitter.SetupGet(at => at.IsEnabled).Returns(true); - var testExecutionEngine = CreateTestExecutionEngine(); - - testExecutionEngine.OnTestRunStart(); + var testExecutionEngine = CreateTestExecutionEngine(); + + await testExecutionEngine.OnTestRunStartAsync(); - _analyticsTransmitter.Verify(at => at.TransmitSpecFlowProjectRunningEvent(It.IsAny()), Times.Once); + _analyticsTransmitter.Verify(at => at.TransmitSpecFlowProjectRunningEventAsync(It.IsAny()), Times.Once); } - [Theory] - [InlineData(1, 3)] - [InlineData(3, 1)] - public void Should_execute_all_ISkippedStepHandlers_for_each_skipped_step(int numberOfHandlers, int numberOfSkippedSteps) + [Theory] + [InlineData(1, 3)] + [InlineData(3, 1)] + public async Task Should_execute_all_ISkippedStepHandlers_for_each_skipped_step(int numberOfHandlers, int numberOfSkippedSteps) { - var sut = CreateTestExecutionEngine(); - scenarioContext.ScenarioExecutionStatus = ScenarioExecutionStatus.TestError; - - var skippedStepHandlerMocks = new List>(); + var sut = CreateTestExecutionEngine(); + scenarioContext.ScenarioExecutionStatus = ScenarioExecutionStatus.TestError; + + var skippedStepHandlerMocks = new List>(); for (int i = 0; i < numberOfHandlers; i++) { var mockHandler = new Mock(); @@ -628,112 +623,116 @@ public void Should_execute_all_ISkippedStepHandlers_for_each_skipped_step(int nu for (int i = 0; i < numberOfSkippedSteps; i++) { RegisterStepDefinition(); - sut.Step(StepDefinitionKeyword.Given, null, "foo", null, null); + await sut.StepAsync(StepDefinitionKeyword.Given, null, "foo", null, null); } foreach (var handler in skippedStepHandlerMocks) { handler.Verify(action => action.Handle(It.IsAny()), Times.Exactly(numberOfSkippedSteps)); - } + } } - [Fact] - public void Should_not_change_ScenarioExecutionStatus_on_dummy_ISkippedStepHandler() - { - var sut = CreateTestExecutionEngine(); - scenarioContext.ScenarioExecutionStatus = ScenarioExecutionStatus.TestError; - + [Fact] + public async Task Should_not_change_ScenarioExecutionStatus_on_dummy_ISkippedStepHandler() + { + var sut = CreateTestExecutionEngine(); + scenarioContext.ScenarioExecutionStatus = ScenarioExecutionStatus.TestError; + var mockHandler = new Mock(); mockHandler.Setup(b => b.Handle(It.IsAny())).Callback(() => Console.WriteLine("ISkippedStepHandler")); scenarioContext.ScenarioContainer.RegisterInstanceAs(mockHandler.Object); - RegisterStepDefinition(); - sut.Step(StepDefinitionKeyword.Given, null, "foo", null, null); - + RegisterStepDefinition(); + await sut.StepAsync(StepDefinitionKeyword.Given, null, "foo", null, null); + scenarioContext.ScenarioExecutionStatus.Should().Be(ScenarioExecutionStatus.TestError); } - [Fact] - public void Should_not_call_ISkippedStepHandler_on_UndefinedStepDefinition() + [Fact] + public async Task Should_not_call_ISkippedStepHandler_on_UndefinedStepDefinition() { - var sut = CreateTestExecutionEngine(); - scenarioContext.ScenarioExecutionStatus = ScenarioExecutionStatus.TestError; - + var sut = CreateTestExecutionEngine(); + scenarioContext.ScenarioExecutionStatus = ScenarioExecutionStatus.TestError; + var mockHandler = new Mock(); mockHandler.Setup(b => b.Handle(It.IsAny())).Verifiable(); scenarioContext.ScenarioContainer.RegisterInstanceAs(mockHandler.Object); RegisterUndefinedStepDefinition(); - sut.Step(StepDefinitionKeyword.Given, null, "foo", null, null); + await sut.StepAsync(StepDefinitionKeyword.Given, null, "foo", null, null); mockHandler.Verify(action => action.Handle(It.IsAny()), Times.Never); } - [Fact] - public void Should_not_call_ISkippedStepHandler_on_succesfull_test_run() + [Fact] + public async Task Should_not_call_ISkippedStepHandler_on_succesfull_test_run() { - var sut = CreateTestExecutionEngine(); - + var sut = CreateTestExecutionEngine(); + var mockHandler = new Mock(); mockHandler.Setup(b => b.Handle(It.IsAny())).Verifiable(); scenarioContext.ScenarioContainer.RegisterInstanceAs(mockHandler.Object); RegisterStepDefinition(); - sut.Step(StepDefinitionKeyword.Given, null, "foo", null, null); - - mockHandler.Verify(action => action.Handle(It.IsAny()), Times.Never); - } - - [Fact] - public void Should_not_call_ISkippedStepHandler_if_only_last_step_is_failing() - { - var sut = CreateTestExecutionEngine(); - - var mockHandler = new Mock(); - mockHandler.Setup(b => b.Handle(It.IsAny())).Callback(() => Console.WriteLine("ISkippedStepHandler")); - scenarioContext.ScenarioContainer.RegisterInstanceAs(mockHandler.Object); - - RegisterFailingStepDefinition(); - sut.Step(StepDefinitionKeyword.Given, null, "foo", null, null); + await sut.StepAsync(StepDefinitionKeyword.Given, null, "foo", null, null); mockHandler.Verify(action => action.Handle(It.IsAny()), Times.Never); } - [Fact] - public void Should_set_correct_duration_in_case_of_failed_step() - { + [Fact] + public async Task Should_not_call_ISkippedStepHandler_if_only_last_step_is_failing() + { + var sut = CreateTestExecutionEngine(); + + var mockHandler = new Mock(); + mockHandler.Setup(b => b.Handle(It.IsAny())).Callback(() => Console.WriteLine("ISkippedStepHandler")); + scenarioContext.ScenarioContainer.RegisterInstanceAs(mockHandler.Object); + + RegisterFailingStepDefinition(); + await sut.StepAsync(StepDefinitionKeyword.Given, null, "foo", null, null); + + mockHandler.Verify(action => action.Handle(It.IsAny()), Times.Never); + } + + [Fact] + public async Task Should_set_correct_duration_in_case_of_failed_step() + { TimeSpan executionDuration = TimeSpan.Zero; testTracerStub.Setup(c => c.TraceError(It.IsAny(), It.IsAny())) .Callback((ex, duration) => executionDuration = duration); - - var testExecutionEngine = CreateTestExecutionEngine(); - + + var testExecutionEngine = CreateTestExecutionEngine(); + TimeSpan expectedDuration = TimeSpan.FromSeconds(5); RegisterFailingStepDefinition(expectedDuration); - - testExecutionEngine.Step(StepDefinitionKeyword.Given, null, "foo", null, null); + + await testExecutionEngine.StepAsync(StepDefinitionKeyword.Given, null, "foo", null, null); testTracerStub.Verify(tracer => tracer.TraceError(It.IsAny(), It.IsAny()), Times.Once()); executionDuration.Should().Be(expectedDuration); - } - - [Fact] - public void Should_set_correct_duration_in_case_of_passed_step() - { + } + + [Fact] + public async Task Should_set_correct_duration_in_case_of_passed_step() + { TimeSpan executionDuration = TimeSpan.Zero; testTracerStub.Setup(c => c.TraceStepDone(It.IsAny(), It.IsAny(), It.IsAny())) .Callback((match, arguments, duration) => executionDuration = duration); - - var testExecutionEngine = CreateTestExecutionEngine(); - + + var testExecutionEngine = CreateTestExecutionEngine(); + TimeSpan expectedDuration = TimeSpan.FromSeconds(5); - var stepDefStub = RegisterStepDefinition(); - methodBindingInvokerMock.Setup(i => i.InvokeBinding(stepDefStub.Object, contextManagerStub.Object, It.IsAny(), testTracerStub.Object, out expectedDuration)); - - testExecutionEngine.Step(StepDefinitionKeyword.Given, null, "foo", null, null); + var stepDefStub = RegisterStepDefinition(); + methodBindingInvokerMock + .Setup(i => i.InvokeBindingAsync(stepDefStub.Object, contextManagerStub.Object, It.IsAny(), testTracerStub.Object, It.IsAny())) + .Callback((IBinding _, IContextManager _, object[] arguments, ITestTracer _, DurationHolder durationHolder) => durationHolder.Duration = expectedDuration) + .ReturnsAsync(new object()); + + await testExecutionEngine.StepAsync(StepDefinitionKeyword.Given, null, "foo", null, null); + testTracerStub.Verify(tracer => tracer.TraceStepDone(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once()); executionDuration.Should().Be(expectedDuration); - } - } + } + } } \ No newline at end of file diff --git a/Tests/TechTalk.SpecFlow.RuntimeTests/Infrastructure/TestThreadContextTests.cs b/Tests/TechTalk.SpecFlow.RuntimeTests/Infrastructure/TestThreadContextTests.cs index 184853c92..65f25ef97 100644 --- a/Tests/TechTalk.SpecFlow.RuntimeTests/Infrastructure/TestThreadContextTests.cs +++ b/Tests/TechTalk.SpecFlow.RuntimeTests/Infrastructure/TestThreadContextTests.cs @@ -12,7 +12,7 @@ public class TestThreadContextTests : StepExecutionTestsBase { public ContextManager CreateContextManager(IObjectContainer testThreadContainer = null) { - return new ContextManager(new Mock().Object, testThreadContainer ?? this.TestThreadContainer, ContainerBuilderStub); + return new ContextManager(new Mock().Object, testThreadContainer ?? TestThreadContainer, ContainerBuilderStub); } [Fact] diff --git a/Tests/TechTalk.SpecFlow.RuntimeTests/Infrastructure/TestThreadExecutionEventPublisherTests.cs b/Tests/TechTalk.SpecFlow.RuntimeTests/Infrastructure/TestThreadExecutionEventPublisherTests.cs index 7a00e3eab..cc02a585e 100644 --- a/Tests/TechTalk.SpecFlow.RuntimeTests/Infrastructure/TestThreadExecutionEventPublisherTests.cs +++ b/Tests/TechTalk.SpecFlow.RuntimeTests/Infrastructure/TestThreadExecutionEventPublisherTests.cs @@ -1,8 +1,11 @@ using System; +using System.Threading.Tasks; using Moq; using TechTalk.SpecFlow.Bindings; using TechTalk.SpecFlow.Bindings.Reflection; using TechTalk.SpecFlow.Events; +using TechTalk.SpecFlow.Infrastructure; +using TechTalk.SpecFlow.Tracing; using Xunit; namespace TechTalk.SpecFlow.RuntimeTests.Infrastructure @@ -10,11 +13,11 @@ namespace TechTalk.SpecFlow.RuntimeTests.Infrastructure public partial class TestExecutionEngineTests { [Fact] - public void Should_publish_step_started_event() + public async Task Should_publish_step_started_event() { var testExecutionEngine = CreateTestExecutionEngine(); - testExecutionEngine.Step(StepDefinitionKeyword.Given, null, "foo", null, null); + await testExecutionEngine.StepAsync(StepDefinitionKeyword.Given, null, "foo", null, null); _testThreadExecutionEventPublisher.Verify(te => te.PublishEvent(It.Is(e => e.ScenarioContext.Equals(scenarioContext) && @@ -24,12 +27,12 @@ public void Should_publish_step_started_event() } [Fact] - public void Should_publish_step_binding_started_event() + public async Task Should_publish_step_binding_started_event() { var stepDef = RegisterStepDefinition().Object; var testExecutionEngine = CreateTestExecutionEngine(); - testExecutionEngine.Step(StepDefinitionKeyword.Given, null, "foo", null, null); + await testExecutionEngine.StepAsync(StepDefinitionKeyword.Given, null, "foo", null, null); _testThreadExecutionEventPublisher.Verify(te => te.PublishEvent(It.Is(e => @@ -38,14 +41,17 @@ public void Should_publish_step_binding_started_event() } [Fact] - public void Should_publish_step_binding_finished_event() + public async Task Should_publish_step_binding_finished_event() { var stepDef = RegisterStepDefinition().Object; TimeSpan expectedDuration = TimeSpan.FromSeconds(5); - methodBindingInvokerMock.Setup(i => i.InvokeBinding(stepDef, contextManagerStub.Object, It.IsAny(), testTracerStub.Object, out expectedDuration)); + methodBindingInvokerMock + .Setup(i => i.InvokeBindingAsync(stepDef, contextManagerStub.Object, It.IsAny(), testTracerStub.Object, It.IsAny())) + .Callback((IBinding _, IContextManager _, object[] arguments, ITestTracer _, DurationHolder durationHolder) => durationHolder.Duration = expectedDuration) + .ReturnsAsync(new object()); var testExecutionEngine = CreateTestExecutionEngine(); - testExecutionEngine.Step(StepDefinitionKeyword.Given, null, "foo", null, null); + await testExecutionEngine.StepAsync(StepDefinitionKeyword.Given, null, "foo", null, null); _testThreadExecutionEventPublisher.Verify(te => te.PublishEvent(It.Is(e => @@ -55,11 +61,11 @@ public void Should_publish_step_binding_finished_event() } [Fact] - public void Should_publish_step_finished_event() + public async Task Should_publish_step_finished_event() { var testExecutionEngine = CreateTestExecutionEngine(); - testExecutionEngine.Step(StepDefinitionKeyword.Given, null, "foo", null, null); + await testExecutionEngine.StepAsync(StepDefinitionKeyword.Given, null, "foo", null, null); _testThreadExecutionEventPublisher.Verify(te => te.PublishEvent(It.Is(e => @@ -70,29 +76,32 @@ public void Should_publish_step_finished_event() } [Fact] - public void Should_publish_step_skipped_event() + public async Task Should_publish_step_skipped_event() { RegisterStepDefinition(); var testExecutionEngine = CreateTestExecutionEngine(); //a step will be skipped if the ScenarioExecutionStatus is not OK scenarioContext.ScenarioExecutionStatus = ScenarioExecutionStatus.TestError; - testExecutionEngine.Step(StepDefinitionKeyword.Given, null, "foo", null, null); + await testExecutionEngine.StepAsync(StepDefinitionKeyword.Given, null, "foo", null, null); _testThreadExecutionEventPublisher.Verify(te => te.PublishEvent(It.IsAny()), Times.Once); } [Fact] - public void Should_publish_hook_binding_events() + public async Task Should_publish_hook_binding_events() { var hookType = HookType.AfterScenario; TimeSpan expectedDuration = TimeSpan.FromSeconds(5); var expectedHookBinding = new HookBinding(new Mock().Object, hookType, null, 1); - methodBindingInvokerMock.Setup(i => i.InvokeBinding(expectedHookBinding, contextManagerStub.Object, It.IsAny(), testTracerStub.Object, out expectedDuration)); + methodBindingInvokerMock + .Setup(i => i.InvokeBindingAsync(expectedHookBinding, contextManagerStub.Object, It.IsAny(), testTracerStub.Object, It.IsAny())) + .Callback((IBinding _, IContextManager _, object[] arguments, ITestTracer _, DurationHolder durationHolder) => durationHolder.Duration = expectedDuration) + .ReturnsAsync(new object()); var testExecutionEngine = CreateTestExecutionEngine(); - testExecutionEngine.InvokeHook(methodBindingInvokerMock.Object, expectedHookBinding, hookType); + await testExecutionEngine.InvokeHookAsync(methodBindingInvokerMock.Object, expectedHookBinding, hookType); _testThreadExecutionEventPublisher.Verify(te => te.PublishEvent(It.Is(e => @@ -106,12 +115,12 @@ public void Should_publish_hook_binding_events() } [Fact] - public void Should_publish_scenario_started_event() + public async Task Should_publish_scenario_started_event() { var testExecutionEngine = CreateTestExecutionEngine(); testExecutionEngine.OnScenarioInitialize(scenarioInfo); - testExecutionEngine.OnScenarioStart(); + await testExecutionEngine.OnScenarioStartAsync(); _testThreadExecutionEventPublisher.Verify(te => te.PublishEvent(It.Is(e => @@ -121,14 +130,14 @@ public void Should_publish_scenario_started_event() } [Fact] - public void Should_publish_scenario_finished_event() + public async Task Should_publish_scenario_finished_event() { var testExecutionEngine = CreateTestExecutionEngine(); testExecutionEngine.OnScenarioInitialize(scenarioInfo); - testExecutionEngine.OnScenarioStart(); - testExecutionEngine.OnAfterLastStep(); - testExecutionEngine.OnScenarioEnd(); + await testExecutionEngine.OnScenarioStartAsync(); + await testExecutionEngine.OnAfterLastStepAsync(); + await testExecutionEngine.OnScenarioEndAsync(); _testThreadExecutionEventPublisher.Verify(te => te.PublishEvent(It.Is(e => @@ -138,22 +147,22 @@ public void Should_publish_scenario_finished_event() } [Fact] - public void Should_publish_hook_started_finished_events() + public async Task Should_publish_hook_started_finished_events() { var testExecutionEngine = CreateTestExecutionEngine(); RegisterStepDefinition(); - testExecutionEngine.OnTestRunStart(); - testExecutionEngine.OnFeatureStart(featureInfo); + await testExecutionEngine.OnTestRunStartAsync(); + await testExecutionEngine.OnFeatureStartAsync(featureInfo); testExecutionEngine.OnScenarioInitialize(scenarioInfo); - testExecutionEngine.OnScenarioStart(); - testExecutionEngine.Step(StepDefinitionKeyword.Given, null, "foo", null, null); - testExecutionEngine.OnAfterLastStep(); - testExecutionEngine.OnScenarioEnd(); + await testExecutionEngine.OnScenarioStartAsync(); + await testExecutionEngine.StepAsync(StepDefinitionKeyword.Given, null, "foo", null, null); + await testExecutionEngine.OnAfterLastStepAsync(); + await testExecutionEngine.OnScenarioEndAsync(); - testExecutionEngine.OnFeatureEnd(); - testExecutionEngine.OnTestRunEnd(); + await testExecutionEngine.OnFeatureEndAsync(); + await testExecutionEngine.OnTestRunEndAsync(); AssertHookEventsForHookType(HookType.BeforeTestRun); AssertHookEventsForHookType(HookType.AfterTestRun); @@ -166,14 +175,14 @@ public void Should_publish_hook_started_finished_events() } [Fact] - public void Should_publish_scenario_skipped_event() + public async Task Should_publish_scenario_skipped_event() { var testExecutionEngine = CreateTestExecutionEngine(); testExecutionEngine.OnScenarioInitialize(scenarioInfo); testExecutionEngine.OnScenarioSkipped(); - testExecutionEngine.OnAfterLastStep(); - testExecutionEngine.OnScenarioEnd(); + await testExecutionEngine.OnAfterLastStepAsync(); + await testExecutionEngine.OnScenarioEndAsync(); _testThreadExecutionEventPublisher.Verify(te => te.PublishEvent(It.Is(e => @@ -190,11 +199,11 @@ public void Should_publish_scenario_skipped_event() } [Fact] - public void Should_publish_feature_started_event() + public async Task Should_publish_feature_started_event() { var testExecutionEngine = CreateTestExecutionEngine(); - testExecutionEngine.OnFeatureStart(featureInfo); + await testExecutionEngine.OnFeatureStartAsync(featureInfo); _testThreadExecutionEventPublisher.Verify(te => te.PublishEvent(It.Is(e => @@ -203,11 +212,11 @@ public void Should_publish_feature_started_event() } [Fact] - public void Should_publish_feature_finished_event() + public async Task Should_publish_feature_finished_event() { var testExecutionEngine = CreateTestExecutionEngine(); - testExecutionEngine.OnFeatureEnd(); + await testExecutionEngine.OnFeatureEndAsync(); _testThreadExecutionEventPublisher.Verify(te => te.PublishEvent(It.Is(e => @@ -216,22 +225,22 @@ public void Should_publish_feature_finished_event() } [Fact] - public void Should_publish_testrun_started_event() + public async Task Should_publish_testrun_started_event() { var testExecutionEngine = CreateTestExecutionEngine(); - testExecutionEngine.OnTestRunStart(); + await testExecutionEngine.OnTestRunStartAsync(); _testThreadExecutionEventPublisher.Verify(te => te.PublishEvent(It.IsAny()), Times.Once); } [Fact] - public void Should_publish_testrun_finished_event() + public async Task Should_publish_testrun_finished_event() { var testExecutionEngine = CreateTestExecutionEngine(); - testExecutionEngine.OnTestRunEnd(); + await testExecutionEngine.OnTestRunEndAsync(); _testThreadExecutionEventPublisher.Verify(te => te.PublishEvent(It.IsAny()), Times.Once); diff --git a/Tests/TechTalk.SpecFlow.RuntimeTests/ScenarioStepContextTests.cs b/Tests/TechTalk.SpecFlow.RuntimeTests/ScenarioStepContextTests.cs index 50e73b6b3..a8360b1c8 100644 --- a/Tests/TechTalk.SpecFlow.RuntimeTests/ScenarioStepContextTests.cs +++ b/Tests/TechTalk.SpecFlow.RuntimeTests/ScenarioStepContextTests.cs @@ -31,7 +31,7 @@ public void CleanupStepContext_WhenNotInitialized_ShouldTraceWarning() /// An object that implements . private IContextManager ResolveContextManager(ITestTracer testTracer) { - var container = this.CreateTestThreadObjectContainer(testTracer); + var container = CreateTestThreadObjectContainer(testTracer); var contextManager = container.Resolve(); return contextManager; } @@ -267,9 +267,9 @@ public void CurrentTopLevelStepDefinitionType_AfterInitializingSubStepThatHasASu var mockTracer = new Mock(); var contextManager = ResolveContextManager(mockTracer.Object); - contextManager.InitializeStepContext(this.CreateStepInfo("I have called initialize once", StepDefinitionType.Given)); - contextManager.InitializeStepContext(this.CreateStepInfo("I have called initialize a second time, to initialize a sub step", StepDefinitionType.When)); - contextManager.InitializeStepContext(this.CreateStepInfo("I have called initialize a third time, to initialize a sub step of a sub step", StepDefinitionType.Then)); + contextManager.InitializeStepContext(CreateStepInfo("I have called initialize once", StepDefinitionType.Given)); + contextManager.InitializeStepContext(CreateStepInfo("I have called initialize a second time, to initialize a sub step", StepDefinitionType.When)); + contextManager.InitializeStepContext(CreateStepInfo("I have called initialize a third time, to initialize a sub step of a sub step", StepDefinitionType.Then)); contextManager.CleanupStepContext(); // return from sub step of sub step var actualCurrentTopLevelStepDefinitionType = contextManager.CurrentTopLevelStepDefinitionType; @@ -284,7 +284,7 @@ public void CurrentTopLevelStepDefinitionType_AfterInitializingNewScenarioContex var contextManager = ResolveContextManager(mockTracer.Object); contextManager.InitializeFeatureContext(new FeatureInfo(new CultureInfo("en-US", false), string.Empty, "F", null)); - contextManager.InitializeStepContext(this.CreateStepInfo("I have called initialize once")); + contextManager.InitializeStepContext(CreateStepInfo("I have called initialize once")); //// Do not call CleanupStepContext, in order to simulate an inconsistent state contextManager.InitializeScenarioContext(new ScenarioInfo("the next scenario", "description of the next scenario", null, null)); @@ -299,7 +299,7 @@ public void Dispose_InInconsistentState_ShouldNotThrowException() var mockTracer = new Mock(); var contextManager = ResolveContextManager(mockTracer.Object); - contextManager.InitializeStepContext(this.CreateStepInfo("I have called initialize once")); + contextManager.InitializeStepContext(CreateStepInfo("I have called initialize once")); //// do not call CleanupStepContext to simulate inconsistent state Action disposeAction = () => ((IDisposable) contextManager).Dispose(); @@ -313,7 +313,7 @@ public void Dispose_InConsistentState_ShouldNotThrowException() var mockTracer = new Mock(); var contextManager = ResolveContextManager(mockTracer.Object); - contextManager.InitializeStepContext(this.CreateStepInfo("I have called initialize once")); + contextManager.InitializeStepContext(CreateStepInfo("I have called initialize once")); contextManager.CleanupStepContext(); diff --git a/Tests/TechTalk.SpecFlow.RuntimeTests/StepArgumentTypeConverterTest.cs b/Tests/TechTalk.SpecFlow.RuntimeTests/StepArgumentTypeConverterTest.cs index c6e50eff2..76ab65ae1 100644 --- a/Tests/TechTalk.SpecFlow.RuntimeTests/StepArgumentTypeConverterTest.cs +++ b/Tests/TechTalk.SpecFlow.RuntimeTests/StepArgumentTypeConverterTest.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using System.Diagnostics; using System.Globalization; +using System.Threading.Tasks; using FluentAssertions; using Moq; using Xunit; @@ -16,7 +17,7 @@ namespace TechTalk.SpecFlow.RuntimeTests public class StepArgumentTypeConverterTests { private IStepArgumentTypeConverter _stepArgumentTypeConverter; - private readonly Mock methodBindingInvokerStub = new Mock(); + private readonly Mock methodBindingInvokerStub = new Mock(); private CultureInfo _enUSCulture; public StepArgumentTypeConverterTests() @@ -30,30 +31,30 @@ public StepArgumentTypeConverterTests() } [Fact] - public void ShouldConvertStringToStringType() + public async Task ShouldConvertStringToStringType() { - var result = _stepArgumentTypeConverter.Convert("testValue", typeof(string), _enUSCulture); + var result = await _stepArgumentTypeConverter.ConvertAsync("testValue", typeof(string), _enUSCulture); result.Should().Be("testValue"); } [Fact] - public void ShouldConvertStringToIntType() + public async Task ShouldConvertStringToIntType() { - var result = _stepArgumentTypeConverter.Convert("10", typeof(int), _enUSCulture); + var result = await _stepArgumentTypeConverter.ConvertAsync("10", typeof(int), _enUSCulture); result.Should().Be(10); } [Fact] - public void ShouldConvertStringToDateType() + public async Task ShouldConvertStringToDateType() { - var result = _stepArgumentTypeConverter.Convert("2009/10/06", typeof(DateTime), _enUSCulture); + var result = await _stepArgumentTypeConverter.ConvertAsync("2009/10/06", typeof(DateTime), _enUSCulture); result.Should().Be(new DateTime(2009, 10, 06)); } [Fact] - public void ShouldConvertStringToFloatType() + public async Task ShouldConvertStringToFloatType() { - var result = _stepArgumentTypeConverter.Convert("10.01", typeof(float), _enUSCulture); + var result = await _stepArgumentTypeConverter.ConvertAsync("10.01", typeof(float), _enUSCulture); result.Should().Be(10.01f); } @@ -63,59 +64,59 @@ private enum TestEnumeration } [Fact] - public void ShouldConvertStringToEnumerationType() + public async Task ShouldConvertStringToEnumerationType() { - var result = _stepArgumentTypeConverter.Convert("Value1", typeof(TestEnumeration), _enUSCulture); + var result = await _stepArgumentTypeConverter.ConvertAsync("Value1", typeof(TestEnumeration), _enUSCulture); result.Should().Be(TestEnumeration.Value1); } [Fact] - public void ShouldConvertStringToEnumerationTypeWithDifferingCase() + public async Task ShouldConvertStringToEnumerationTypeWithDifferingCase() { - var result = _stepArgumentTypeConverter.Convert("vALUE1", typeof(TestEnumeration), _enUSCulture); + var result = await _stepArgumentTypeConverter.ConvertAsync("vALUE1", typeof(TestEnumeration), _enUSCulture); result.Should().Be(TestEnumeration.Value1); } [Fact] - public void ShouldConvertStringToEnumerationTypeWithWhitespace() + public async Task ShouldConvertStringToEnumerationTypeWithWhitespace() { - var result = _stepArgumentTypeConverter.Convert("Value 1", typeof(TestEnumeration), _enUSCulture); + var result = await _stepArgumentTypeConverter.ConvertAsync("Value 1", typeof(TestEnumeration), _enUSCulture); result.Should().Be(TestEnumeration.Value1); } [Fact] - public void ShouldConvertGuidToGuidType() + public async Task ShouldConvertGuidToGuidType() { - var result = _stepArgumentTypeConverter.Convert("{EF338B79-FD29-488F-8CA7-39C67C2B8874}", typeof (Guid), _enUSCulture); + var result = await _stepArgumentTypeConverter.ConvertAsync("{EF338B79-FD29-488F-8CA7-39C67C2B8874}", typeof (Guid), _enUSCulture); result.Should().Be(new Guid("{EF338B79-FD29-488F-8CA7-39C67C2B8874}")); } [Fact] - public void ShouldConvertNullableGuidToGuidType() + public async Task ShouldConvertNullableGuidToGuidType() { - var result = _stepArgumentTypeConverter.Convert("{1081CFD1-F31F-420F-9360-40590ABEF887}", typeof(Guid?), _enUSCulture); + var result = await _stepArgumentTypeConverter.ConvertAsync("{1081CFD1-F31F-420F-9360-40590ABEF887}", typeof(Guid?), _enUSCulture); result.Should().Be(new Guid("{1081CFD1-F31F-420F-9360-40590ABEF887}")); } [Fact] - public void ShouldConvertNullableGuidWithEmptyValueToNull() + public async Task ShouldConvertNullableGuidWithEmptyValueToNull() { - var result = _stepArgumentTypeConverter.Convert("", typeof(Guid?), _enUSCulture); + var result = await _stepArgumentTypeConverter.ConvertAsync("", typeof(Guid?), _enUSCulture); result.Should().BeNull(); } [Fact] - public void ShouldConvertLooseGuids() + public async Task ShouldConvertLooseGuids() { - var result = _stepArgumentTypeConverter.Convert("1", typeof (Guid), _enUSCulture); + var result = await _stepArgumentTypeConverter.ConvertAsync("1", typeof (Guid), _enUSCulture); result.Should().Be(new Guid("10000000-0000-0000-0000-000000000000")); } [Fact] - public void ShouldUseATypeConverterWhenAvailable() + public async Task ShouldUseATypeConverterWhenAvailable() { var originalValue = new DateTimeOffset(2019, 7, 29, 0, 0, 0, TimeSpan.Zero); - var result = _stepArgumentTypeConverter.Convert(originalValue, typeof (TestClass), _enUSCulture); + var result = await _stepArgumentTypeConverter.ConvertAsync(originalValue, typeof (TestClass), _enUSCulture); result.Should().BeOfType(typeof(TestClass)); result.As().Time.Should().Be(originalValue); } diff --git a/Tests/TechTalk.SpecFlow.RuntimeTests/StepExecutionTests.cs b/Tests/TechTalk.SpecFlow.RuntimeTests/StepExecutionTests.cs index aa197b9b8..39ef49223 100644 --- a/Tests/TechTalk.SpecFlow.RuntimeTests/StepExecutionTests.cs +++ b/Tests/TechTalk.SpecFlow.RuntimeTests/StepExecutionTests.cs @@ -105,13 +105,13 @@ public virtual void BindingWithoutParam2() public class StepExecutionTests : StepExecutionTestsBase { [Fact] - public void ShouldCallBindingWithoutParameter() + public async Task ShouldCallBindingWithoutParameter() { var (testRunner, bindingMock) = GetTestRunnerFor(); //bindingInstance.Expect(b => b.BindingWithoutParam()); - testRunner.Given("sample step without param"); + await testRunner.GivenAsync("sample step without param"); GetLastTestStatus().Should().Be(ScenarioExecutionStatus.OK); @@ -119,11 +119,11 @@ public void ShouldCallBindingWithoutParameter() } [Fact] - public void ShouldCallBindingSingleParameter() + public async Task ShouldCallBindingSingleParameter() { var (testRunner, bindingMock) = GetTestRunnerFor(); - testRunner.Given("sample step with single param"); + await testRunner.GivenAsync("sample step with single param"); GetLastTestStatus().Should().Be(ScenarioExecutionStatus.OK); @@ -132,13 +132,13 @@ public void ShouldCallBindingSingleParameter() } [Fact] - public void ShouldCallBindingMultipleParameter() + public async Task ShouldCallBindingMultipleParameter() { var (testRunner, bindingMock) = GetTestRunnerFor(); //bindingInstance.Expect(b => b.BindingWithMultipleParam("multi", "ple")); - testRunner.Given("sample step with multiple param"); + await testRunner.GivenAsync("sample step with multiple param"); GetLastTestStatus().Should().Be(ScenarioExecutionStatus.OK); @@ -146,7 +146,7 @@ public void ShouldCallBindingMultipleParameter() } [Fact] - public void ShouldCallBindingWithTableParameter() + public async Task ShouldCallBindingWithTableParameter() { var (testRunner, bindingMock) = GetTestRunnerFor(); @@ -155,14 +155,14 @@ public void ShouldCallBindingWithTableParameter() //MockRepository.ReplayAll(); - testRunner.Given("sample step with table param", null, table); + await testRunner.GivenAsync("sample step with table param", null, table); GetLastTestStatus().Should().Be(ScenarioExecutionStatus.OK); bindingMock.Verify(x => x.BindingWithTableParam(table)); } [Fact] - public void ShouldCallBindingWithMlStringParam() + public async Task ShouldCallBindingWithMlStringParam() { var (testRunner, bindingMock) = GetTestRunnerFor(); @@ -171,14 +171,14 @@ public void ShouldCallBindingWithMlStringParam() //MockRepository.ReplayAll(); - testRunner.Given("sample step with multi-line string param", mlString, null); + await testRunner.GivenAsync("sample step with multi-line string param", mlString, null); GetLastTestStatus().Should().Be(ScenarioExecutionStatus.OK); bindingMock.Verify(x => x.BindingWithMlStringParam(mlString)); } [Fact] - public void ShouldCallBindingWithTableAndMlStringParam() + public async Task ShouldCallBindingWithTableAndMlStringParam() { var (testRunner, bindingMock) = GetTestRunnerFor(); @@ -188,14 +188,14 @@ public void ShouldCallBindingWithTableAndMlStringParam() //MockRepository.ReplayAll(); - testRunner.Given("sample step with table and multi-line string param", mlString, table); + await testRunner.GivenAsync("sample step with table and multi-line string param", mlString, table); GetLastTestStatus().Should().Be(ScenarioExecutionStatus.OK); bindingMock.Verify(x => x.BindingWithTableAndMlStringParam(mlString, table)); } [Fact] - public void ShouldCallBindingWithMixedParams() + public async Task ShouldCallBindingWithMixedParams() { var (testRunner, bindingMock) = GetTestRunnerFor(); @@ -205,26 +205,26 @@ public void ShouldCallBindingWithMixedParams() //MockRepository.ReplayAll(); - testRunner.Given("sample step with mixed params", mlString, table); + await testRunner.GivenAsync("sample step with mixed params", mlString, table); GetLastTestStatus().Should().Be(ScenarioExecutionStatus.OK); bindingMock.Verify(x => x.BindingWithMixedParams("mixed", mlString, table)); } [Fact] - public void ShouldRaiseAmbiguousIfMultipleMatch() + public async Task ShouldRaiseAmbiguousIfMultipleMatch() { var (testRunner, bindingMock) = GetTestRunnerFor(); //MockRepository.ReplayAll(); - testRunner.Given("sample step without param"); + await testRunner.GivenAsync("sample step without param"); GetLastTestStatus().Should().Be(ScenarioExecutionStatus.BindingError); } [Fact] - public void ShouldDistinguishByTableParam_CallWithoutTable() + public async Task ShouldDistinguishByTableParam_CallWithoutTable() { var (testRunner, bindingMock) = GetTestRunnerFor(); @@ -232,14 +232,14 @@ public void ShouldDistinguishByTableParam_CallWithoutTable() //MockRepository.ReplayAll(); - testRunner.Given("Distinguish by table param"); + await testRunner.GivenAsync("Distinguish by table param"); GetLastTestStatus().Should().Be(ScenarioExecutionStatus.OK); bindingMock.Verify(x => x.DistinguishByTableParam1()); } [Fact] - public void ShouldDistinguishByTableParam_CallWithTable() + public async Task ShouldDistinguishByTableParam_CallWithTable() { var (testRunner, bindingMock) = GetTestRunnerFor(); @@ -248,26 +248,26 @@ public void ShouldDistinguishByTableParam_CallWithTable() //MockRepository.ReplayAll(); - testRunner.Given("Distinguish by table param", null, table); + await testRunner.GivenAsync("Distinguish by table param", null, table); GetLastTestStatus().Should().Be(ScenarioExecutionStatus.OK); bindingMock.Verify(x => x.DistinguishByTableParam2(table)); } [Fact] - public void ShouldRaiseBindingErrorIfWrongParamNumber() + public async Task ShouldRaiseBindingErrorIfWrongParamNumber() { var (testRunner, bindingMock) = GetTestRunnerFor(); //MockRepository.ReplayAll(); - testRunner.Given("sample step with wrong param number"); + await testRunner.GivenAsync("sample step with wrong param number"); GetLastTestStatus().Should().Be(ScenarioExecutionStatus.BindingError); } [Fact] - public void ShouldCallBindingThatReturnsTask() + public async Task ShouldCallBindingThatReturnsTask() { var (testRunner, bindingMock) = GetTestRunnerFor(); @@ -287,13 +287,13 @@ public void ShouldCallBindingThatReturnsTask() //MockRepository.ReplayAll(); - testRunner.Given("Returns a Task"); + await testRunner.GivenAsync("Returns a Task"); Assert.True(taskFinished); } [Fact] - public void ShouldCallBindingThatReturnsTaskAndReportError() + public async Task ShouldCallBindingThatReturnsTaskAndReportError() { var (testRunner, bindingMock) = GetTestRunnerFor(); @@ -314,14 +314,11 @@ public void ShouldCallBindingThatReturnsTaskAndReportError() //MockRepository.ReplayAll(); - testRunner.Given("Returns a Task"); + await testRunner.GivenAsync("Returns a Task"); Assert.True(taskFinished); GetLastTestStatus().Should().Be(ScenarioExecutionStatus.TestError); Assert.Equal("catch meee", ContextManagerStub.ScenarioContext.TestError.Message); } - - - } } diff --git a/Tests/TechTalk.SpecFlow.RuntimeTests/StepExecutionTestsBase.cs b/Tests/TechTalk.SpecFlow.RuntimeTests/StepExecutionTestsBase.cs index 26b7b01c8..4ed86b201 100644 --- a/Tests/TechTalk.SpecFlow.RuntimeTests/StepExecutionTestsBase.cs +++ b/Tests/TechTalk.SpecFlow.RuntimeTests/StepExecutionTestsBase.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Threading.Tasks; using BoDi; using Moq; using TechTalk.SpecFlow.Bindings; @@ -9,11 +10,11 @@ using TechTalk.SpecFlow.Configuration; using TechTalk.SpecFlow.Infrastructure; using TechTalk.SpecFlow.Tracing; - +using Xunit; namespace TechTalk.SpecFlow.RuntimeTests { - public class StepExecutionTestsBase + public class StepExecutionTestsBase : IAsyncLifetime { protected CultureInfo FeatureLanguage; protected Mock StepArgumentTypeConverterStub; @@ -83,14 +84,11 @@ protected virtual CultureInfo GetFeatureLanguage() protected virtual CultureInfo GetBindingCulture() { return new CultureInfo("en-US", false); - } + } - public StepExecutionTestsBase() + public async Task InitializeAsync() { - TestRunnerManager.Reset(); - - - + await TestRunnerManager.ResetAsync(); // FeatureContext and ScenarioContext is needed, because the [Binding]-instances live there FeatureLanguage = GetFeatureLanguage(); @@ -170,5 +168,9 @@ protected ScenarioExecutionStatus GetLastTestStatus() { return ContextManagerStub.ScenarioContext.ScenarioExecutionStatus; } + + public async Task DisposeAsync() + { + } } } \ No newline at end of file diff --git a/Tests/TechTalk.SpecFlow.RuntimeTests/StepExecutionTestsWithConversions.cs b/Tests/TechTalk.SpecFlow.RuntimeTests/StepExecutionTestsWithConversions.cs index 50e458180..57735ab23 100644 --- a/Tests/TechTalk.SpecFlow.RuntimeTests/StepExecutionTestsWithConversions.cs +++ b/Tests/TechTalk.SpecFlow.RuntimeTests/StepExecutionTestsWithConversions.cs @@ -1,6 +1,7 @@ using System; using System.Globalization; using System.Linq.Expressions; +using System.Threading.Tasks; using FluentAssertions; using Xunit; using Moq; @@ -13,20 +14,19 @@ namespace TechTalk.SpecFlow.RuntimeTests { internal static class LegacyStepArgumentTypeConverterExtensions { - public static object Convert(this IStepArgumentTypeConverter converter, object value, Type typeToConvertTo, CultureInfo cultureInfo) + public static async Task ConvertAsync(this IStepArgumentTypeConverter converter, object value, Type typeToConvertTo, CultureInfo cultureInfo) { - return converter.Convert(value, new RuntimeBindingType(typeToConvertTo), cultureInfo); + return await converter.ConvertAsync(value, new RuntimeBindingType(typeToConvertTo), cultureInfo); } - public static Expression> GetCanConvertMethodFilter(object argument, Type type) { return c => c.CanConvert(argument, It.Is(bt => bt.TypeEquals(type)), It.IsAny()); } - public static Expression> GetConvertMethodFilter(object argument, Type type) + public static Expression>> GetConvertAsyncMethodFilter(object argument, Type type) { - return c => c.Convert(It.Is(s => s.Equals(argument)), It.Is(bt => bt.TypeEquals(type)), It.IsAny()); + return c => c.ConvertAsync(It.Is(s => s.Equals(argument)), It.Is(bt => bt.TypeEquals(type)), It.IsAny()); //Arg.Is.Equal(argument), //Arg.Matches(bt => bt.TypeEquals(type)), //Arg.Is.Anything); @@ -65,7 +65,7 @@ public virtual void DoubleArgWithTable(double param, Table table) public class StepExecutionTestsWithConversions : StepExecutionTestsBase { [Fact] - public void ShouldCallBindingWithSimpleConvertParam() + public async Task ShouldCallBindingWithSimpleConvertParam() { var (testRunner, bindingMock) = GetTestRunnerFor(); @@ -73,30 +73,30 @@ public void ShouldCallBindingWithSimpleConvertParam() //MockRepository.ReplayAll(); - testRunner.Given("sample step with simple convert param: 1.23"); + await testRunner.GivenAsync("sample step with simple convert param: 1.23"); GetLastTestStatus().Should().Be(ScenarioExecutionStatus.OK); bindingMock.Verify(x => x.BindingWithSimpleConvertParam(1.23)); } [Fact] - public void ShouldRaiseErrorIfSimpleConvertParamFails() + public async Task ShouldRaiseErrorIfSimpleConvertParamFails() { var (testRunner, bindingMock) = GetTestRunnerFor(); //MockRepository.ReplayAll(); - testRunner.Given("sample step with simple convert param: not-a-double"); + await testRunner.GivenAsync("sample step with simple convert param: not-a-double"); GetLastTestStatus().Should().Be(ScenarioExecutionStatus.TestError); } [Fact] - public void ShouldCallTheOnlyThatCanConvert() + public async Task ShouldCallTheOnlyThatCanConvert() { StepArgumentTypeConverterStub.Setup(LegacyStepArgumentTypeConverterExtensions.GetCanConvertMethodFilter("argument", typeof(double))).Returns(true); //StepArgumentTypeConverterStub.Setup(c => c.CanConvert(It.IsAny(), It.IsAny(), It.IsAny())).Returns(false); - StepArgumentTypeConverterStub.Setup(LegacyStepArgumentTypeConverterExtensions.GetConvertMethodFilter("argument", typeof(double))).Returns(1.23); + StepArgumentTypeConverterStub.Setup(LegacyStepArgumentTypeConverterExtensions.GetConvertAsyncMethodFilter("argument", typeof(double))).ReturnsAsync(1.23); var (testRunner, bindingMock) = GetTestRunnerWithConverterStub(); @@ -107,7 +107,7 @@ public void ShouldCallTheOnlyThatCanConvert() //MockRepository.ReplayAll(); - testRunner.Given("sample step for argument convert: argument"); + await testRunner.GivenAsync("sample step for argument convert: argument"); GetLastTestStatus().Should().Be(ScenarioExecutionStatus.OK, ContextManagerStub.ScenarioContext.TestError?.ToString()); @@ -118,7 +118,7 @@ public void ShouldCallTheOnlyThatCanConvert() [Fact] - public void ShouldRaiseAmbiguousIfMultipleCanConvert() + public async Task ShouldRaiseAmbiguousIfMultipleCanConvert() { StepArgumentTypeConverterStub.Setup(LegacyStepArgumentTypeConverterExtensions.GetCanConvertMethodFilter("argument", typeof(double))).Returns(true); StepArgumentTypeConverterStub.Setup(LegacyStepArgumentTypeConverterExtensions.GetCanConvertMethodFilter("argument", typeof(int))).Returns(true); @@ -130,14 +130,14 @@ public void ShouldRaiseAmbiguousIfMultipleCanConvert() //MockRepository.ReplayAll(); - testRunner.Given("sample step for argument convert: argument"); + await testRunner.GivenAsync("sample step for argument convert: argument"); GetLastTestStatus().Should().Be(ScenarioExecutionStatus.BindingError, ContextManagerStub.ScenarioContext.TestError?.ToString()); } [Fact] - public void ShouldCallTheOnlyThatCanConvertWithTable() + public async Task ShouldCallTheOnlyThatCanConvertWithTable() { Table table = new Table("h1"); @@ -145,8 +145,8 @@ public void ShouldCallTheOnlyThatCanConvertWithTable() StepArgumentTypeConverterStub.Setup(LegacyStepArgumentTypeConverterExtensions.GetCanConvertMethodFilter("argument", typeof(double))).Returns(true); //StepArgumentTypeConverterStub.Setup(c => c.CanConvert(It.IsAny(), It.IsAny(), It.IsAny())).Returns(false); - StepArgumentTypeConverterStub.Setup(LegacyStepArgumentTypeConverterExtensions.GetConvertMethodFilter(table, typeof(Table))).Returns(table); - StepArgumentTypeConverterStub.Setup(LegacyStepArgumentTypeConverterExtensions.GetConvertMethodFilter("argument", typeof(double))).Returns(1.23); + StepArgumentTypeConverterStub.Setup(LegacyStepArgumentTypeConverterExtensions.GetConvertAsyncMethodFilter(table, typeof(Table))).ReturnsAsync(table); + StepArgumentTypeConverterStub.Setup(LegacyStepArgumentTypeConverterExtensions.GetConvertAsyncMethodFilter("argument", typeof(double))).ReturnsAsync(1.23); var (testRunner, bindingMock) = GetTestRunnerWithConverterStub(); @@ -156,7 +156,7 @@ public void ShouldCallTheOnlyThatCanConvertWithTable() //MockRepository.ReplayAll(); - testRunner.Given("sample step for argument convert with table: argument", null, table); + await testRunner.GivenAsync("sample step for argument convert with table: argument", null, table); GetLastTestStatus().Should().Be(ScenarioExecutionStatus.OK, ContextManagerStub.ScenarioContext.TestError?.ToString()); @@ -164,7 +164,7 @@ public void ShouldCallTheOnlyThatCanConvertWithTable() } [Fact] - public void ShouldRaiseParamErrorIfNoneCanConvert() + public async Task ShouldRaiseParamErrorIfNoneCanConvert() { var (testRunner, bindingMock) = GetTestRunnerFor(); @@ -173,7 +173,7 @@ public void ShouldRaiseParamErrorIfNoneCanConvert() // MockRepository.ReplayAll(); - testRunner.Given("sample step for argument convert: argument"); + await testRunner.GivenAsync("sample step for argument convert: argument"); GetLastTestStatus().Should().Be(ScenarioExecutionStatus.BindingError, ContextManagerStub.ScenarioContext.TestError?.ToString()); } diff --git a/Tests/TechTalk.SpecFlow.RuntimeTests/StepExecutionTestsWithConversionsFeatureIsNonEnglishButBindingIsEnglishCulture.cs b/Tests/TechTalk.SpecFlow.RuntimeTests/StepExecutionTestsWithConversionsFeatureIsNonEnglishButBindingIsEnglishCulture.cs index 0163aca98..d0de8c35d 100644 --- a/Tests/TechTalk.SpecFlow.RuntimeTests/StepExecutionTestsWithConversionsFeatureIsNonEnglishButBindingIsEnglishCulture.cs +++ b/Tests/TechTalk.SpecFlow.RuntimeTests/StepExecutionTestsWithConversionsFeatureIsNonEnglishButBindingIsEnglishCulture.cs @@ -1,6 +1,7 @@ using System; using System.Globalization; using System.Threading; +using System.Threading.Tasks; using FluentAssertions; using Xunit; @@ -29,7 +30,7 @@ protected override CultureInfo GetFeatureLanguage() } [Fact] - public void ShouldCallBindingWithSimpleConvertParam() + public async Task ShouldCallBindingWithSimpleConvertParam() { var (testRunner, bindingMock) = GetTestRunnerFor(); @@ -37,20 +38,20 @@ public void ShouldCallBindingWithSimpleConvertParam() //MockRepository.ReplayAll(); - testRunner.Given("sample step with simple convert param: 1.23"); // German uses ',' as decimal separator, but BindingCulture is english + await testRunner.GivenAsync("sample step with simple convert param: 1.23"); // German uses ',' as decimal separator, but BindingCulture is english GetLastTestStatus().Should().Be(ScenarioExecutionStatus.OK); bindingMock.Verify(x => x.BindingWithSimpleConvertParam(1.23)); } [Fact] - public void ShouldExecuteBindingWithTheProperCulture() + public async Task ShouldExecuteBindingWithTheProperCulture() { var (testRunner, bindingMock) = GetTestRunnerFor(); //MockRepository.ReplayAll(); - testRunner.Given("argument 1.23 should be able to convert to 1.23 even though it has english localization"); // German uses ',' as decimal separator, but BindingCulture is english + await testRunner.GivenAsync("argument 1.23 should be able to convert to 1.23 even though it has english localization"); // German uses ',' as decimal separator, but BindingCulture is english GetLastTestStatus().Should().Be(ScenarioExecutionStatus.OK); bindingMock.Verify(x => x.InBindingConversion("1.23", 1.23)); diff --git a/Tests/TechTalk.SpecFlow.RuntimeTests/StepExecutionTestsWithConversionsForTables.cs b/Tests/TechTalk.SpecFlow.RuntimeTests/StepExecutionTestsWithConversionsForTables.cs index d5278a9f9..7bf52cb53 100644 --- a/Tests/TechTalk.SpecFlow.RuntimeTests/StepExecutionTestsWithConversionsForTables.cs +++ b/Tests/TechTalk.SpecFlow.RuntimeTests/StepExecutionTestsWithConversionsForTables.cs @@ -1,4 +1,5 @@ using System.Globalization; +using System.Threading.Tasks; using FluentAssertions; using Moq; using Xunit; @@ -37,7 +38,7 @@ public virtual void ParameterMultilineArgumentAndTable(string param, string mult public class StepExecutionTestsWithConversionsForTables : StepExecutionTestsBase { [Fact] - public void ShouldCallTheUserConverterToConvertTableWithTable() + public async Task ShouldCallTheUserConverterToConvertTableWithTable() { var (testRunner, bindingMock) = GetTestRunnerWithConverterStub(); @@ -46,19 +47,19 @@ public void ShouldCallTheUserConverterToConvertTableWithTable() // return false unless its a User StepArgumentTypeConverterStub.Setup(LegacyStepArgumentTypeConverterExtensions.GetCanConvertMethodFilter(table, typeof(User))).Returns(true); - StepArgumentTypeConverterStub.Setup(LegacyStepArgumentTypeConverterExtensions.GetConvertMethodFilter(table, typeof(User))).Returns(user); + StepArgumentTypeConverterStub.Setup(LegacyStepArgumentTypeConverterExtensions.GetConvertAsyncMethodFilter(table, typeof(User))).ReturnsAsync(user); //bindingInstance.Expect(b => b.SingleTable(user)); //MockRepository.ReplayAll(); - testRunner.Given("sample step for argument convert with table", null, table); + await testRunner.GivenAsync("sample step for argument convert with table", null, table); GetLastTestStatus().Should().Be(ScenarioExecutionStatus.OK); bindingMock.Verify(x => x.SingleTable(user)); } [Fact] - public void ShouldCallTheUserConverterToConvertTableWithTableAndMultilineArg() + public async Task ShouldCallTheUserConverterToConvertTableWithTableAndMultilineArg() { var (testRunner, bindingMock) = GetTestRunnerWithConverterStub(); @@ -68,22 +69,22 @@ public void ShouldCallTheUserConverterToConvertTableWithTableAndMultilineArg() // return false unless its a User StepArgumentTypeConverterStub.Setup(LegacyStepArgumentTypeConverterExtensions.GetCanConvertMethodFilter(table, typeof(User))).Returns(true); StepArgumentTypeConverterStub.Setup(c => c.CanConvert(It.IsAny(), It.IsAny(), It.IsAny())).Returns(false); - StepArgumentTypeConverterStub.Setup(LegacyStepArgumentTypeConverterExtensions.GetConvertMethodFilter(multiLineArg, typeof(string))).Returns(multiLineArg); - StepArgumentTypeConverterStub.Setup(LegacyStepArgumentTypeConverterExtensions.GetConvertMethodFilter(table, typeof(User))).Returns(user); + StepArgumentTypeConverterStub.Setup(LegacyStepArgumentTypeConverterExtensions.GetConvertAsyncMethodFilter(multiLineArg, typeof(string))).ReturnsAsync(multiLineArg); + StepArgumentTypeConverterStub.Setup(LegacyStepArgumentTypeConverterExtensions.GetConvertAsyncMethodFilter(table, typeof(User))).ReturnsAsync(user); //bindingInstance.Expect(b => b.MultilineArgumentAndTable(multiLineArg, user)); //MockRepository.ReplayAll(); - testRunner.Given("sample step for argument convert with multiline argument and table", multiLineArg, table); + await testRunner.GivenAsync("sample step for argument convert with multiline argument and table", multiLineArg, table); GetLastTestStatus().Should().Be(ScenarioExecutionStatus.OK); bindingMock.Verify(x => x.MultilineArgumentAndTable(multiLineArg, user)); } [Fact] - public void ShouldCallTheUserConverterToConvertTableWithTableAndMultilineArgAndParameter() + public async Task ShouldCallTheUserConverterToConvertTableWithTableAndMultilineArgAndParameter() { var (testRunner, bindingMock) = GetTestRunnerWithConverterStub(); @@ -95,15 +96,15 @@ public void ShouldCallTheUserConverterToConvertTableWithTableAndMultilineArgAndP // must also stub CanConvert & Convert for the string argument as we've introduced a parameter StepArgumentTypeConverterStub.Setup(LegacyStepArgumentTypeConverterExtensions.GetCanConvertMethodFilter(table, typeof(User))).Returns(true); StepArgumentTypeConverterStub.Setup(c => c.CanConvert(It.IsAny(), It.IsAny(), It.IsAny())).Returns(false); - StepArgumentTypeConverterStub.Setup(LegacyStepArgumentTypeConverterExtensions.GetConvertMethodFilter(table, typeof(User))).Returns(user); - StepArgumentTypeConverterStub.Setup(LegacyStepArgumentTypeConverterExtensions.GetConvertMethodFilter(argumentValue, typeof(string))).Returns(argumentValue); - StepArgumentTypeConverterStub.Setup(LegacyStepArgumentTypeConverterExtensions.GetConvertMethodFilter(multiLineArg, typeof(string))).Returns(multiLineArg); + StepArgumentTypeConverterStub.Setup(LegacyStepArgumentTypeConverterExtensions.GetConvertAsyncMethodFilter(table, typeof(User))).ReturnsAsync(user); + StepArgumentTypeConverterStub.Setup(LegacyStepArgumentTypeConverterExtensions.GetConvertAsyncMethodFilter(argumentValue, typeof(string))).ReturnsAsync(argumentValue); + StepArgumentTypeConverterStub.Setup(LegacyStepArgumentTypeConverterExtensions.GetConvertAsyncMethodFilter(multiLineArg, typeof(string))).ReturnsAsync(multiLineArg); //bindingInstance.Expect(b => b.ParameterMultilineArgumentAndTable(argumentValue, multiLineArg, user)); //MockRepository.ReplayAll(); - testRunner.Given("sample step for argument convert with parameter, multiline argument and table: argument", multiLineArg, table); + await testRunner.GivenAsync("sample step for argument convert with parameter, multiline argument and table: argument", multiLineArg, table); GetLastTestStatus().Should().Be(ScenarioExecutionStatus.OK); bindingMock.Verify(x => x.ParameterMultilineArgumentAndTable(argumentValue, multiLineArg, user)); diff --git a/Tests/TechTalk.SpecFlow.RuntimeTests/StepExecutionTestsWithConversionsInNonEnglishCulture.cs b/Tests/TechTalk.SpecFlow.RuntimeTests/StepExecutionTestsWithConversionsInNonEnglishCulture.cs index 581250ac3..03dac2931 100644 --- a/Tests/TechTalk.SpecFlow.RuntimeTests/StepExecutionTestsWithConversionsInNonEnglishCulture.cs +++ b/Tests/TechTalk.SpecFlow.RuntimeTests/StepExecutionTestsWithConversionsInNonEnglishCulture.cs @@ -1,6 +1,7 @@ using System; using System.Globalization; using System.Threading; +using System.Threading.Tasks; using FluentAssertions; using Xunit; @@ -34,7 +35,7 @@ protected override CultureInfo GetBindingCulture() } [Fact] - public void ShouldCallBindingWithSimpleConvertParam() + public async Task ShouldCallBindingWithSimpleConvertParam() { var (testRunner, bindingMock) = GetTestRunnerFor(); @@ -42,20 +43,20 @@ public void ShouldCallBindingWithSimpleConvertParam() //MockRepository.ReplayAll(); - testRunner.Given("sample step with simple convert param: 1,23"); // German uses , as decimal separator + await testRunner.GivenAsync("sample step with simple convert param: 1,23"); // German uses , as decimal separator GetLastTestStatus().Should().Be(ScenarioExecutionStatus.OK); bindingMock.Verify(x => x.BindingWithSimpleConvertParam(1.23)); } [Fact] - public void ShouldExecuteBindingWithTheProperCulture() + public async Task ShouldExecuteBindingWithTheProperCulture() { var (testRunner, bindingMock) = GetTestRunnerFor(); //MockRepository.ReplayAll(); - testRunner.Given("argument 1,23 should be able to convert to 1,23"); // German uses , as decimal separator + await testRunner.GivenAsync("argument 1,23 should be able to convert to 1,23"); // German uses , as decimal separator GetLastTestStatus().Should().Be(ScenarioExecutionStatus.OK); bindingMock.Verify(x => x.InBindingConversion("1,23", 1.23)); diff --git a/Tests/TechTalk.SpecFlow.RuntimeTests/StepTransformationTests.cs b/Tests/TechTalk.SpecFlow.RuntimeTests/StepTransformationTests.cs index e9bced3a7..d25c8fad3 100644 --- a/Tests/TechTalk.SpecFlow.RuntimeTests/StepTransformationTests.cs +++ b/Tests/TechTalk.SpecFlow.RuntimeTests/StepTransformationTests.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Linq; using System.Reflection; +using System.Threading.Tasks; using BoDi; using FluentAssertions; using Moq; @@ -68,7 +69,7 @@ public class StepTransformationTests { private readonly Mock bindingRegistryStub = new Mock(); private readonly Mock contextManagerStub = new Mock(); - private readonly Mock methodBindingInvokerStub = new Mock(); + private readonly Mock methodBindingInvokerStub = new Mock(); private readonly List stepTransformations = new List(); public StepTransformationTests() @@ -91,7 +92,7 @@ private IStepArgumentTransformationBinding CreateStepTransformationBinding(strin } [Fact] - public void UserConverterShouldConvertStringToUser() + public async Task UserConverterShouldConvertStringToUser() { UserCreator stepTransformationInstance = new UserCreator(); var transformMethod = stepTransformationInstance.GetType().GetMethod("Create"); @@ -99,16 +100,15 @@ public void UserConverterShouldConvertStringToUser() stepTransformationBinding.Regex.IsMatch("user xyz").Should().BeTrue(); - var invoker = new BindingInvoker(ConfigurationLoader.GetDefault(), new Mock().Object, new SynchronousBindingDelegateInvoker()); - TimeSpan duration; - var result = invoker.InvokeBinding(stepTransformationBinding, contextManagerStub.Object, new object[] { "xyz" }, new Mock().Object, out duration); + var invoker = new BindingInvoker(ConfigurationLoader.GetDefault(), new Mock().Object, new BindingDelegateInvoker()); + var result = await invoker.InvokeBindingAsync(stepTransformationBinding, contextManagerStub.Object, new object[] { "xyz" }, new Mock().Object, new DurationHolder()); Assert.NotNull(result); result.Should().BeOfType(); ((User) result).Name.Should().Be("xyz"); } [Fact] - public void TypeToTypeConverterShouldConvertStringToStringUsingRegex() + public async Task TypeToTypeConverterShouldConvertStringToStringUsingRegex() { TypeToTypeConverter stepTransformationInstance = new TypeToTypeConverter(); var transformMethod = stepTransformationInstance.GetType().GetMethod("StringToStringConvertRegex"); @@ -116,39 +116,36 @@ public void TypeToTypeConverterShouldConvertStringToStringUsingRegex() Assert.Matches(stepTransformationBinding.Regex, "string xyz"); - var invoker = new BindingInvoker(ConfigurationLoader.GetDefault(), new Mock().Object, new SynchronousBindingDelegateInvoker()); - TimeSpan duration; - var result = invoker.InvokeBinding(stepTransformationBinding, contextManagerStub.Object, new object[] { "xyz" }, new Mock().Object, out duration); + var invoker = new BindingInvoker(ConfigurationLoader.GetDefault(), new Mock().Object, new BindingDelegateInvoker()); + var result = await invoker.InvokeBindingAsync(stepTransformationBinding, contextManagerStub.Object, new object[] { "xyz" }, new Mock().Object, new DurationHolder()); Assert.NotNull(result); result.GetType().Should().Be(); result.Should().Be("prefix xyz"); } [Fact] - public void TypeToTypeConverterShouldConvertStringToString() + public async Task TypeToTypeConverterShouldConvertStringToString() { TypeToTypeConverter stepTransformationInstance = new TypeToTypeConverter(); var transformMethod = stepTransformationInstance.GetType().GetMethod("StringToStringConvert"); var stepTransformationBinding = CreateStepTransformationBinding(@"", transformMethod); - var invoker = new BindingInvoker(ConfigurationLoader.GetDefault(), new Mock().Object, new SynchronousBindingDelegateInvoker()); - TimeSpan duration; - var result = invoker.InvokeBinding(stepTransformationBinding, contextManagerStub.Object, new object[] { "xyz" }, new Mock().Object, out duration); + var invoker = new BindingInvoker(ConfigurationLoader.GetDefault(), new Mock().Object, new BindingDelegateInvoker()); + var result = await invoker.InvokeBindingAsync(stepTransformationBinding, contextManagerStub.Object, new object[] { "xyz" }, new Mock().Object, new DurationHolder()); Assert.NotNull(result); result.GetType().Should().Be(); result.Should().Be("prefix xyz"); } [Fact] - public void TypeToTypeConverterShouldConvertTableToTable() + public async Task TypeToTypeConverterShouldConvertTableToTable() { TypeToTypeConverter stepTransformationInstance = new TypeToTypeConverter(); var transformMethod = stepTransformationInstance.GetType().GetMethod("TableToTableConvert"); var stepTransformationBinding = CreateStepTransformationBinding(@"", transformMethod); - var invoker = new BindingInvoker(ConfigurationLoader.GetDefault(), new Mock().Object, new SynchronousBindingDelegateInvoker()); - TimeSpan duration; - var result = invoker.InvokeBinding(stepTransformationBinding, contextManagerStub.Object, new object[] { new Table("h1") }, new Mock().Object, out duration); + var invoker = new BindingInvoker(ConfigurationLoader.GetDefault(), new Mock().Object, new BindingDelegateInvoker()); + var result = await invoker.InvokeBindingAsync(stepTransformationBinding, contextManagerStub.Object, new object[] { new Table("h1") }, new Mock().Object, new DurationHolder()); Assert.NotNull(result); result.GetType().Should().Be(); @@ -156,20 +153,20 @@ public void TypeToTypeConverterShouldConvertTableToTable() } [Fact] - public void StepArgumentTypeConverterShouldUseUserConverterForConversion() + public async Task 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); + methodBindingInvokerStub + .Setup(i => i.InvokeBindingAsync(stepTransformationBinding, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(resultUser); var stepArgumentTypeConverter = CreateStepArgumentTypeConverter(); - var result = stepArgumentTypeConverter.Convert("user xyz", typeof(User), new CultureInfo("en-US", false)); + var result = await stepArgumentTypeConverter.ConvertAsync("user xyz", typeof(User), new CultureInfo("en-US", false)); result.Should().Be(resultUser); } @@ -179,7 +176,7 @@ private StepArgumentTypeConverter CreateStepArgumentTypeConverter() } [Fact] - public void ShouldUseStepArgumentTransformationToConvertTable() + public async Task ShouldUseStepArgumentTransformationToConvertTable() { var table = new Table("Name"); @@ -187,15 +184,15 @@ public void ShouldUseStepArgumentTransformationToConvertTable() 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); + methodBindingInvokerStub + .Setup(i => i.InvokeBindingAsync(stepTransformationBinding, It.IsAny(), new object[] { table }, It.IsAny(), It.IsAny())) + .ReturnsAsync(resultUsers); var stepArgumentTypeConverter = CreateStepArgumentTypeConverter(); - var result = stepArgumentTypeConverter.Convert(table, typeof(IEnumerable), new CultureInfo("en-US", false)); + var result = await stepArgumentTypeConverter.ConvertAsync(table, typeof(IEnumerable), new CultureInfo("en-US", false)); result.Should().NotBeNull(); result.Should().Be(resultUsers); diff --git a/Tests/TechTalk.SpecFlow.RuntimeTests/StepsTest.cs b/Tests/TechTalk.SpecFlow.RuntimeTests/StepsTest.cs index b1d9796c0..9797d0564 100644 --- a/Tests/TechTalk.SpecFlow.RuntimeTests/StepsTest.cs +++ b/Tests/TechTalk.SpecFlow.RuntimeTests/StepsTest.cs @@ -1,3 +1,4 @@ +using System.Threading.Tasks; using BoDi; using Moq; using Xunit; @@ -28,43 +29,43 @@ public StepsTest() } [Fact] - public void GivenIsDelegatedToObjectContainerCurrentTestRunner() + public async Task GivenIsDelegatedToObjectContainerCurrentTestRunner() { - steps.Given(Text, MultilineTextArg, table); + await steps.GivenAsync(Text, MultilineTextArg, table); - mockTestRunner.Verify(m => m.Given(Text, MultilineTextArg, table, null)); + mockTestRunner.Verify(m => m.GivenAsync(Text, MultilineTextArg, table, null)); } [Fact] - public void WhenIsDelegatedToObjectContainerCurrentTestRunner() + public async Task WhenIsDelegatedToObjectContainerCurrentTestRunner() { - steps.When(Text, MultilineTextArg, table); + await steps.WhenAsync(Text, MultilineTextArg, table); - mockTestRunner.Verify(m => m.When(Text, MultilineTextArg, table, null)); + mockTestRunner.Verify(m => m.WhenAsync(Text, MultilineTextArg, table, null)); } [Fact] - public void ThenIsDelegatedToObjectContainerCurrentTestRunner() + public async Task ThenIsDelegatedToObjectContainerCurrentTestRunner() { - steps.Then(Text, MultilineTextArg, table); + await steps.ThenAsync(Text, MultilineTextArg, table); - mockTestRunner.Verify(m => m.Then(Text, MultilineTextArg, table, null)); + mockTestRunner.Verify(m => m.ThenAsync(Text, MultilineTextArg, table, null)); } [Fact] - public void ButIsDelegatedToObjectContainerCurrentTestRunner() + public async Task ButIsDelegatedToObjectContainerCurrentTestRunner() { - steps.But(Text, MultilineTextArg, table); + await steps.ButAsync(Text, MultilineTextArg, table); - mockTestRunner.Verify(m => m.But(Text, MultilineTextArg, table, null)); + mockTestRunner.Verify(m => m.ButAsync(Text, MultilineTextArg, table, null)); } [Fact] - public void AndIsDelegatedToObjectContainerCurrentTestRunner() + public async Task AndIsDelegatedToObjectContainerCurrentTestRunner() { - steps.And(Text, MultilineTextArg, table); + await steps.AndAsync(Text, MultilineTextArg, table); - mockTestRunner.Verify(m => m.And(Text, MultilineTextArg, table, null)); + mockTestRunner.Verify(m => m.AndAsync(Text, MultilineTextArg, table, null)); } public class StepsTestableHelper : Steps diff --git a/Tests/TechTalk.SpecFlow.RuntimeTests/TechTalk.SpecFlow.RuntimeTests.csproj b/Tests/TechTalk.SpecFlow.RuntimeTests/TechTalk.SpecFlow.RuntimeTests.csproj index d52a74e03..d42613938 100644 --- a/Tests/TechTalk.SpecFlow.RuntimeTests/TechTalk.SpecFlow.RuntimeTests.csproj +++ b/Tests/TechTalk.SpecFlow.RuntimeTests/TechTalk.SpecFlow.RuntimeTests.csproj @@ -9,6 +9,10 @@ true + + 1701;1702;CS1998 + + @@ -19,7 +23,7 @@ - + diff --git a/Tests/TechTalk.SpecFlow.RuntimeTests/TestRunnerManagerRunnerCreationTests.cs b/Tests/TechTalk.SpecFlow.RuntimeTests/TestRunnerManagerRunnerCreationTests.cs index 6575eb00b..67f33812a 100644 --- a/Tests/TechTalk.SpecFlow.RuntimeTests/TestRunnerManagerRunnerCreationTests.cs +++ b/Tests/TechTalk.SpecFlow.RuntimeTests/TestRunnerManagerRunnerCreationTests.cs @@ -2,6 +2,7 @@ using System.Globalization; using System.Linq; using System.Reflection; +using System.Threading.Tasks; using BoDi; using FluentAssertions; using Moq; @@ -47,7 +48,7 @@ public void Should_resolve_a_test_runner() { var factory = CreateTestRunnerFactory(); - var testRunner = factory.CreateTestRunner(0); + var testRunner = factory.CreateTestRunner("0"); testRunner.Should().NotBeNull(); } @@ -55,7 +56,7 @@ public void Should_resolve_a_test_runner() public void Should_initialize_test_runner_with_the_provided_assembly() { var factory = CreateTestRunnerFactory(); - factory.CreateTestRunner(0); + factory.CreateTestRunner("0"); factory.IsTestRunInitialized.Should().BeTrue(); } @@ -66,7 +67,7 @@ public void Should_initialize_test_runner_with_additional_step_assemblies() var factory = CreateTestRunnerFactory(); _specFlowConfigurationStub.AddAdditionalStepAssembly(anotherAssembly); - factory.CreateTestRunner(0); + factory.CreateTestRunner("0"); factory.IsTestRunInitialized.Should().BeTrue(); } @@ -77,30 +78,30 @@ public void Should_initialize_test_runner_with_the_provided_assembly_even_if_the var factory = CreateTestRunnerFactory(); _specFlowConfigurationStub.AddAdditionalStepAssembly(anotherAssembly); - factory.CreateTestRunner(0); + factory.CreateTestRunner("0"); factory.IsTestRunInitialized.Should().BeTrue(); } [Fact] - public void Should_resolve_a_test_runner_specific_test_tracer() + public async Task Should_resolve_a_test_runner_specific_test_tracer() { //This test can't run in NCrunch as when NCrunch runs the tests it will disable the ability to get different test runners for each thread //as it manages the parallelisation //see https://github.com/techtalk/SpecFlow/issues/638 if (!TestEnvironmentHelper.IsBeingRunByNCrunch()) { - var testRunner1 = TestRunnerManager.GetTestRunner(anAssembly, 0, new RuntimeTestsContainerBuilder()); - testRunner1.OnFeatureStart(new FeatureInfo(new CultureInfo("en-US", false), string.Empty, "sds", "sss")); + var testRunner1 = TestRunnerManager.GetTestRunnerForAssembly(anAssembly, "0", new RuntimeTestsContainerBuilder()); + await testRunner1.OnFeatureStartAsync(new FeatureInfo(new CultureInfo("en-US", false), string.Empty, "sds", "sss")); testRunner1.OnScenarioInitialize(new ScenarioInfo("foo", "foo_desc", null, null)); - testRunner1.OnScenarioStart(); + await testRunner1.OnScenarioStartAsync(); var tracer1 = testRunner1.ScenarioContext.ScenarioContainer.Resolve(); - var testRunner2 = TestRunnerManager.GetTestRunner(anAssembly, 1, new RuntimeTestsContainerBuilder()); - testRunner2.OnFeatureStart(new FeatureInfo(new CultureInfo("en-US", false), string.Empty, "sds", "sss")); + var testRunner2 = TestRunnerManager.GetTestRunnerForAssembly(anAssembly, "1", new RuntimeTestsContainerBuilder()); + await testRunner2.OnFeatureStartAsync(new FeatureInfo(new CultureInfo("en-US", false), string.Empty, "sds", "sss")); testRunner2.OnScenarioInitialize(new ScenarioInfo("foo", "foo_desc", null, null)); - testRunner1.OnScenarioStart(); + await testRunner1.OnScenarioStartAsync(); var tracer2 = testRunner2.ScenarioContext.ScenarioContainer.Resolve(); tracer1.Should().NotBeSameAs(tracer2); diff --git a/Tests/TechTalk.SpecFlow.RuntimeTests/TestRunnerManagerStaticApiTest.cs b/Tests/TechTalk.SpecFlow.RuntimeTests/TestRunnerManagerStaticApiTest.cs index c21c6376b..d9de262bb 100644 --- a/Tests/TechTalk.SpecFlow.RuntimeTests/TestRunnerManagerStaticApiTest.cs +++ b/Tests/TechTalk.SpecFlow.RuntimeTests/TestRunnerManagerStaticApiTest.cs @@ -1,45 +1,41 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; -using System.Text; using System.Threading.Tasks; using FluentAssertions; using Xunit; namespace TechTalk.SpecFlow.RuntimeTests { - - public class TestRunnerManagerStaticApiTest + public class TestRunnerManagerStaticApiTest : IAsyncLifetime { private readonly Assembly thisAssembly = Assembly.GetExecutingAssembly(); private readonly Assembly anotherAssembly = typeof(TestRunnerManager).Assembly; - public TestRunnerManagerStaticApiTest() + public async Task InitializeAsync() { - TestRunnerManager.Reset(); + await TestRunnerManager.ResetAsync(); } [Fact] - public void GetTestRunner_without_arguments_should_return_TestRunner_instance() + public async Task GetTestRunner_without_arguments_should_return_TestRunner_instance() { - var testRunner = TestRunnerManager.GetTestRunner(containerBuilder: new RuntimeTestsContainerBuilder()); + var testRunner = TestRunnerManager.GetTestRunnerForAssembly(testWorkerId: "0", containerBuilder: new RuntimeTestsContainerBuilder()); testRunner.Should().NotBeNull(); testRunner.Should().BeOfType(); } [Fact] - public void GetTestRunner_should_return_different_instances_for_different_assemblies() + public async Task GetTestRunner_should_return_different_instances_for_different_assemblies() { - var testRunner1 = TestRunnerManager.GetTestRunner(thisAssembly, containerBuilder: new RuntimeTestsContainerBuilder()); - var testRunner2 = TestRunnerManager.GetTestRunner(anotherAssembly, containerBuilder: new RuntimeTestsContainerBuilder()); + var testRunner1 = TestRunnerManager.GetTestRunnerForAssembly(thisAssembly, "0", containerBuilder: new RuntimeTestsContainerBuilder()); + var testRunner2 = TestRunnerManager.GetTestRunnerForAssembly(anotherAssembly, "0", containerBuilder: new RuntimeTestsContainerBuilder()); testRunner1.Should().NotBe(testRunner2); } [Fact] - public void GetTestRunnerManager_without_arguments_should_return_an_instance_for_the_calling_assembly() + public async Task GetTestRunnerManager_without_arguments_should_return_an_instance_for_the_calling_assembly() { var testRunnerManager = TestRunnerManager.GetTestRunnerManager(containerBuilder: new RuntimeTestsContainerBuilder()); @@ -48,9 +44,9 @@ public void GetTestRunnerManager_without_arguments_should_return_an_instance_for } [Fact] - public void GetTestRunnerManager_should_return_null_when_called_with_no_create_flag_and_there_was_no_instance_created_yet() + public async Task GetTestRunnerManager_should_return_null_when_called_with_no_create_flag_and_there_was_no_instance_created_yet() { - TestRunnerManager.Reset(); + await TestRunnerManager.ResetAsync(); var testRunnerManager = TestRunnerManager.GetTestRunnerManager(createIfMissing: false, containerBuilder: new RuntimeTestsContainerBuilder()); @@ -82,66 +78,66 @@ public static void BeforeTestRun() } [Fact] - public void OnTestRunEnd_should_fire_AfterTestRun_events() + public async Task OnTestRunEnd_should_fire_AfterTestRun_events() { // make sure a test runner is initialized - TestRunnerManager.GetTestRunner(thisAssembly, containerBuilder: new RuntimeTestsContainerBuilder()); + TestRunnerManager.GetTestRunnerForAssembly(thisAssembly, "0", containerBuilder: new RuntimeTestsContainerBuilder()); AfterTestRunTestBinding.AfterTestRunCallCount = 0; //reset - TestRunnerManager.OnTestRunEnd(thisAssembly); + await TestRunnerManager.OnTestRunEndAsync(thisAssembly); AfterTestRunTestBinding.AfterTestRunCallCount.Should().Be(1); } [Fact] - public void OnTestRunEnd_without_arguments_should_fire_AfterTestRun_events_for_calling_assembly() + public async Task OnTestRunEnd_without_arguments_should_fire_AfterTestRun_events_for_calling_assembly() { // make sure a test runner is initialized - TestRunnerManager.GetTestRunner(thisAssembly, containerBuilder: new RuntimeTestsContainerBuilder()); + TestRunnerManager.GetTestRunnerForAssembly(thisAssembly, "0", containerBuilder: new RuntimeTestsContainerBuilder()); AfterTestRunTestBinding.AfterTestRunCallCount = 0; //reset - TestRunnerManager.OnTestRunEnd(); + await TestRunnerManager.OnTestRunEndAsync(); AfterTestRunTestBinding.AfterTestRunCallCount.Should().Be(1); } [Fact] - public void OnTestRunEnd_should_not_fire_AfterTestRun_events_multiple_times() + public async Task OnTestRunEnd_should_not_fire_AfterTestRun_events_multiple_times() { // make sure a test runner is initialized - TestRunnerManager.GetTestRunner(thisAssembly, containerBuilder: new RuntimeTestsContainerBuilder()); + TestRunnerManager.GetTestRunnerForAssembly(thisAssembly, "0", containerBuilder: new RuntimeTestsContainerBuilder()); AfterTestRunTestBinding.AfterTestRunCallCount = 0; //reset - TestRunnerManager.OnTestRunEnd(thisAssembly); - TestRunnerManager.OnTestRunEnd(thisAssembly); + await TestRunnerManager.OnTestRunEndAsync(thisAssembly); + await TestRunnerManager.OnTestRunEndAsync(thisAssembly); AfterTestRunTestBinding.AfterTestRunCallCount.Should().Be(1); } [Fact] - public void OnTestRunStart_should_fire_BeforeTestRun_events() + public async Task OnTestRunStart_should_fire_BeforeTestRun_events() { BeforeTestRunTestBinding.BeforeTestRunCallCount = 0; //reset - TestRunnerManager.OnTestRunStart(thisAssembly, containerBuilder: new RuntimeTestsContainerBuilder()); + await TestRunnerManager.OnTestRunStartAsync(thisAssembly, containerBuilder: new RuntimeTestsContainerBuilder()); BeforeTestRunTestBinding.BeforeTestRunCallCount.Should().Be(1); } [Fact] - public void OnTestRunStart_without_arguments_should_fire_BeforeTestRun_events_for_calling_assembly() + public async Task OnTestRunStart_without_arguments_should_fire_BeforeTestRun_events_for_calling_assembly() { BeforeTestRunTestBinding.BeforeTestRunCallCount = 0; //reset - TestRunnerManager.OnTestRunStart(containerBuilder: new RuntimeTestsContainerBuilder()); + await TestRunnerManager.OnTestRunStartAsync(containerBuilder: new RuntimeTestsContainerBuilder()); BeforeTestRunTestBinding.BeforeTestRunCallCount.Should().Be(1); } [Fact] - public void OnTestRunStart_should_not_fire_BeforeTestRun_events_multiple_times() + public async Task OnTestRunStart_should_not_fire_BeforeTestRun_events_multiple_times() { BeforeTestRunTestBinding.BeforeTestRunCallCount = 0; //reset - TestRunnerManager.OnTestRunStart(thisAssembly, containerBuilder: new RuntimeTestsContainerBuilder()); - TestRunnerManager.OnTestRunStart(thisAssembly); + await TestRunnerManager.OnTestRunStartAsync(thisAssembly, containerBuilder: new RuntimeTestsContainerBuilder()); + await TestRunnerManager.OnTestRunStartAsync(thisAssembly); BeforeTestRunTestBinding.BeforeTestRunCallCount.Should().Be(1); } @@ -161,5 +157,9 @@ public void OnTestRunStart_should_not_fire_BeforeTestRun_events_multiple_times() // AfterTestRunTestBinding.AfterTestRunCallCount.Should().Be(1); //} + + public async Task DisposeAsync() + { + } } } diff --git a/Tests/TechTalk.SpecFlow.RuntimeTests/TestRunnerManagerTest.cs b/Tests/TechTalk.SpecFlow.RuntimeTests/TestRunnerManagerTest.cs index dc728185c..c5f5fe341 100644 --- a/Tests/TechTalk.SpecFlow.RuntimeTests/TestRunnerManagerTest.cs +++ b/Tests/TechTalk.SpecFlow.RuntimeTests/TestRunnerManagerTest.cs @@ -24,7 +24,7 @@ public TestRunnerManagerTest() [Fact] public void CreateTestRunner_should_be_able_to_create_a_testrunner() { - var testRunner = testRunnerManager.CreateTestRunner(0); + var testRunner = testRunnerManager.CreateTestRunner("0"); testRunner.Should().NotBeNull(); testRunner.Should().BeOfType(); @@ -33,7 +33,7 @@ public void CreateTestRunner_should_be_able_to_create_a_testrunner() [Fact] public void GetTestRunner_should_be_able_to_create_a_testrunner() { - var testRunner = testRunnerManager.GetTestRunner(0); + var testRunner = testRunnerManager.GetTestRunner("0"); testRunner.Should().NotBeNull(); testRunner.Should().BeOfType(); @@ -42,8 +42,8 @@ public void GetTestRunner_should_be_able_to_create_a_testrunner() [Fact] public void GetTestRunner_should_cache_instance() { - var testRunner1 = testRunnerManager.GetTestRunner(threadId: 0); - var testRunner2 = testRunnerManager.GetTestRunner(threadId: 0); + var testRunner1 = testRunnerManager.GetTestRunner("0"); + var testRunner2 = testRunnerManager.GetTestRunner("0"); testRunner1.Should().Be(testRunner2); @@ -52,8 +52,8 @@ public void GetTestRunner_should_cache_instance() [Fact] public void Should_return_different_instances_for_different_thread_ids() { - var testRunner1 = testRunnerManager.GetTestRunner(threadId: 1); - var testRunner2 = testRunnerManager.GetTestRunner(threadId: 2); + var testRunner1 = testRunnerManager.GetTestRunner("0"); + var testRunner2 = testRunnerManager.GetTestRunner("1"); testRunner1.Should().NotBe(testRunner2); } diff --git a/Tests/TechTalk.SpecFlow.RuntimeTests/TraceListenerQueueTests.cs b/Tests/TechTalk.SpecFlow.RuntimeTests/TraceListenerQueueTests.cs index 428cf3899..156131a60 100644 --- a/Tests/TechTalk.SpecFlow.RuntimeTests/TraceListenerQueueTests.cs +++ b/Tests/TechTalk.SpecFlow.RuntimeTests/TraceListenerQueueTests.cs @@ -62,8 +62,8 @@ void WriteTestOutputCallback(string message) private Mock GetTestRunnerMock() { var testRunnerMock = new Mock(); - testRunnerMock.SetupGet(r => r.ThreadId) - .Returns(() => Thread.CurrentThread.ManagedThreadId); + testRunnerMock.SetupGet(r => r.TestWorkerId) + .Returns(() => Thread.CurrentThread.ManagedThreadId.ToString()); return testRunnerMock; } diff --git a/Tests/TechTalk.SpecFlow.Specs.Generator.SpecFlowPlugin/CustomXUnitGeneratorProvider.cs b/Tests/TechTalk.SpecFlow.Specs.Generator.SpecFlowPlugin/CustomXUnitGeneratorProvider.cs index 3e6b58f27..a69485ef3 100644 --- a/Tests/TechTalk.SpecFlow.Specs.Generator.SpecFlowPlugin/CustomXUnitGeneratorProvider.cs +++ b/Tests/TechTalk.SpecFlow.Specs.Generator.SpecFlowPlugin/CustomXUnitGeneratorProvider.cs @@ -10,7 +10,7 @@ class CustomXUnitGeneratorProvider : XUnit2TestGeneratorProvider { private readonly Combination _combination; - public CustomXUnitGeneratorProvider(CodeDomHelper codeDomHelper, Combination combination, ProjectSettings projectSettings) : base(codeDomHelper, projectSettings) + public CustomXUnitGeneratorProvider(CodeDomHelper codeDomHelper, Combination combination, ProjectSettings projectSettings) : base(codeDomHelper) { _combination = combination; } diff --git a/Tests/TechTalk.SpecFlow.Specs.Generator.SpecFlowPlugin/MultiFeatureGenerator.cs b/Tests/TechTalk.SpecFlow.Specs.Generator.SpecFlowPlugin/MultiFeatureGenerator.cs index 72b9341aa..7e4b443ed 100644 --- a/Tests/TechTalk.SpecFlow.Specs.Generator.SpecFlowPlugin/MultiFeatureGenerator.cs +++ b/Tests/TechTalk.SpecFlow.Specs.Generator.SpecFlowPlugin/MultiFeatureGenerator.cs @@ -91,10 +91,14 @@ private SpecFlowDocument CloneDocumentAndAddTags(SpecFlowDocument specFlowDocume var tags = new List(); var specFlowFeature = specFlowDocument.SpecFlowFeature; tags.AddRange(specFlowFeature.Tags); - if (!HasFeatureTag(specFlowDocument.SpecFlowFeature, combination.UnitTestProvider)) - tags.Add(new Tag(null, combination.UnitTestProvider)); - if (!HasFeatureTag(specFlowDocument.SpecFlowFeature, combination.TargetFramework)) - tags.Add(new Tag(null, combination.TargetFramework)); + if (!HasFeatureTag(specFlowDocument.SpecFlowFeature, "@" + combination.UnitTestProvider)) + tags.Add(new Tag(null, "@" + combination.UnitTestProvider)); + foreach (string otherUnitTestProvider in _unitTestProviderTags.Where(utp => !utp.Equals(combination.UnitTestProvider, StringComparison.InvariantCultureIgnoreCase))) + { + tags.RemoveAll(t => ("@" + otherUnitTestProvider).Equals(t.Name, StringComparison.InvariantCultureIgnoreCase)); + } + if (!HasFeatureTag(specFlowDocument.SpecFlowFeature, "@" + combination.TargetFramework)) + tags.Add(new Tag(null, "@" + combination.TargetFramework)); var feature = new SpecFlowFeature(tags.ToArray(), specFlowFeature.Location, specFlowFeature.Language, diff --git a/Tests/TechTalk.SpecFlow.Specs/Features/Contexts/AccessingContexts.feature b/Tests/TechTalk.SpecFlow.Specs/Features/Contexts/AccessingContexts.feature index 002b82ebb..023d0a21e 100644 --- a/Tests/TechTalk.SpecFlow.Specs/Features/Contexts/AccessingContexts.feature +++ b/Tests/TechTalk.SpecFlow.Specs/Features/Contexts/AccessingContexts.feature @@ -1,5 +1,25 @@ Feature: Accessing Contexts +@focus +Scenario: The obsolete ScenarioContext.Current can be accessed from a non-parallel execution + Given the following step definition + """ + [When(@"the ScenarioContext.Current is accessed")] + public void WhenTheScenarioContextCurrentIsAccessed() + { + if (ScenarioContext.Current == null) + throw new Exception("ScenarioContext.Current is null"); + } + """ + And a scenario 'Sample scenario' as + """ + When the ScenarioContext.Current is accessed + """ + When I execute the tests + Then the execution summary should contain + | Succeeded | + | 1 | + Scenario: Should be able to inject ScenarioContext Given the following binding class """ diff --git a/Tests/TechTalk.SpecFlow.Specs/Features/Contexts/ScenarioInfo/Arguments.feature b/Tests/TechTalk.SpecFlow.Specs/Features/Contexts/ScenarioInfo/Arguments.feature index e641d86b7..26db8361b 100644 --- a/Tests/TechTalk.SpecFlow.Specs/Features/Contexts/ScenarioInfo/Arguments.feature +++ b/Tests/TechTalk.SpecFlow.Specs/Features/Contexts/ScenarioInfo/Arguments.feature @@ -8,12 +8,12 @@ Scenario: Scenario arguments are empty for regular scenario using TechTalk.SpecFlow; [Binding] - public class A + public class A : Steps { [Then("B")] public void B() { - if (ScenarioContext.Current.ScenarioInfo.Arguments.Count != 0) throw new Exception("Scenario arguments are not empty"); + if (ScenarioContext.ScenarioInfo.Arguments.Count != 0) throw new Exception("Scenario arguments are not empty"); } } """ diff --git a/Tests/TechTalk.SpecFlow.Specs/Features/Contexts/ScenarioInfo/Description.feature b/Tests/TechTalk.SpecFlow.Specs/Features/Contexts/ScenarioInfo/Description.feature index 301051f38..def5a871e 100644 --- a/Tests/TechTalk.SpecFlow.Specs/Features/Contexts/ScenarioInfo/Description.feature +++ b/Tests/TechTalk.SpecFlow.Specs/Features/Contexts/ScenarioInfo/Description.feature @@ -7,12 +7,12 @@ Scenario: Check Scenario description is not empty using TechTalk.SpecFlow; [Binding] - public class DescriptionTestsBinding + public class DescriptionTestsBinding : Steps { [Then(@"Check ""(.*)"" match with scenario description in context")] public void ThenCheckMatchWithScenarioDescriptionInContext(string desc) { - var testValue = ScenarioContext.Current.ScenarioInfo.Description; + var testValue = ScenarioContext.ScenarioInfo.Description; if (testValue != desc) throw new Exception("Scenario Description is incorrectly parsed"); } } @@ -39,12 +39,12 @@ Scenario: Check Scenario description is null if empty using TechTalk.SpecFlow; [Binding] - public class DescriptionTestsBinding + public class DescriptionTestsBinding : Steps { [Then(@"Check that scenario description is null in context")] public void ThenCheckThatScenarioDescriptionIsNullInContext() { - var testValue = ScenarioContext.Current.ScenarioInfo.Description; + var testValue = ScenarioContext.ScenarioInfo.Description; if (testValue != null) throw new Exception("Scenario Description is incorrectly parsed"); } } diff --git a/Tests/TechTalk.SpecFlow.Specs/Features/Execution/InAppDomainParallelExecution.feature b/Tests/TechTalk.SpecFlow.Specs/Features/Execution/InAppDomainParallelExecution.feature index 7ea5812a4..b57db7c74 100644 --- a/Tests/TechTalk.SpecFlow.Specs/Features/Execution/InAppDomainParallelExecution.feature +++ b/Tests/TechTalk.SpecFlow.Specs/Features/Execution/InAppDomainParallelExecution.feature @@ -1,6 +1,5 @@ @xUnit @NUnit3 @MSTest -#parallel execution doesn't work currently with MsTest v2 - +@focus Feature: In-AppDomain Parallel Execution Background: @@ -15,10 +14,12 @@ Background: public class TraceSteps { private readonly ITraceListener _traceListener; + private readonly ITestRunner _testRunner; - public TraceSteps(ITraceListener traceListener) + public TraceSteps(ITraceListener traceListener, ITestRunner testRunner) { _traceListener = traceListener; + _testRunner = testRunner; } public static int startIndex = 0; @@ -27,7 +28,7 @@ Background: void WhenIDoSomething() { var currentStartIndex = System.Threading.Interlocked.Increment(ref startIndex); - _traceListener.WriteTestOutput($"Start index: {currentStartIndex}"); + _traceListener.WriteTestOutput($"Start index: {currentStartIndex}, Worker: {_testRunner.TestWorkerId}"); System.Threading.Tasks.Task.Delay(500).Wait(); var afterStartIndex = startIndex; if (afterStartIndex == currentStartIndex) diff --git a/Tests/TechTalk.SpecFlow.Specs/Features/Execution/TaggedExamples.feature b/Tests/TechTalk.SpecFlow.Specs/Features/Execution/TaggedExamples.feature index 075d68787..0fc5fd3d2 100644 --- a/Tests/TechTalk.SpecFlow.Specs/Features/Execution/TaggedExamples.feature +++ b/Tests/TechTalk.SpecFlow.Specs/Features/Execution/TaggedExamples.feature @@ -77,7 +77,7 @@ Scenario: Scenario Outline Examples can be tagged [BeforeScenario] public void BeforeScenario() { - var scenarioTags = ScenarioContext.Current.ScenarioInfo.Tags; + var scenarioTags = _scenarioContext.ScenarioInfo.Tags; if (scenarioTags.Length == 0) { diff --git a/Tests/TechTalk.SpecFlow.Specs/Features/UnitTestProviderSpecific/MsTest/TestContext.feature b/Tests/TechTalk.SpecFlow.Specs/Features/UnitTestProviderSpecific/MsTest/TestContext.feature index 26e818879..53261291f 100644 --- a/Tests/TechTalk.SpecFlow.Specs/Features/UnitTestProviderSpecific/MsTest/TestContext.feature +++ b/Tests/TechTalk.SpecFlow.Specs/Features/UnitTestProviderSpecific/MsTest/TestContext.feature @@ -12,7 +12,7 @@ Scenario: Should be able to access TestContext in Steps [When(@"I do something")] public void WhenIDoSomething() { - System.Console.WriteLine(ScenarioContext.Current.ScenarioContainer.Resolve().TestName); + System.Console.WriteLine(_scenarioContext.ScenarioContainer.Resolve().TestName); } """ When I execute the tests diff --git a/Tests/TechTalk.SpecFlow.Specs/StepDefinitions/BindingSteps.cs b/Tests/TechTalk.SpecFlow.Specs/StepDefinitions/BindingSteps.cs index 273681820..13b5100aa 100644 --- a/Tests/TechTalk.SpecFlow.Specs/StepDefinitions/BindingSteps.cs +++ b/Tests/TechTalk.SpecFlow.Specs/StepDefinitions/BindingSteps.cs @@ -51,7 +51,7 @@ public void GivenAllStepsAreBoundAndPass(ScenarioBlock scenarioBlock) [Given(@"all '(.*)' steps are bound and are pending")] public void GivenAllStepsAreBoundAndArePending(ScenarioBlock scenarioBlock) { - _projectsDriver.AddStepBinding(scenarioBlock.ToString(), ".*", "ScenarioContext.Current.Pending();", "ScenarioContext.Current.Pending()"); + _projectsDriver.AddStepBinding(scenarioBlock.ToString(), ".*", "throw new PendingStepException();", "ScenarioContext.Current.Pending()"); } [Given(@"the following step definition in the project '(.*)'")] diff --git a/Tests/TechTalk.SpecFlow.Specs/TechTalk.SpecFlow.Specs.csproj b/Tests/TechTalk.SpecFlow.Specs/TechTalk.SpecFlow.Specs.csproj index b56501f3b..3fc892d16 100644 --- a/Tests/TechTalk.SpecFlow.Specs/TechTalk.SpecFlow.Specs.csproj +++ b/Tests/TechTalk.SpecFlow.Specs/TechTalk.SpecFlow.Specs.csproj @@ -7,7 +7,7 @@ $(SpecFlow_Specs_TFM) TechTalk.SpecFlow.Specs $(SpecFlow_KeyFile) - $(SpecFlow_EnableStrongNameSigning) + false $(SpecFlow_PublicSign) TechTalk.SpecFlow.Specs true diff --git a/Tests/TechTalk.SpecFlow.Specs/xUnit.AssemblyHooks.cs b/Tests/TechTalk.SpecFlow.Specs/xUnit.AssemblyHooks.cs index aacfa720a..47ef8a511 100644 --- a/Tests/TechTalk.SpecFlow.Specs/xUnit.AssemblyHooks.cs +++ b/Tests/TechTalk.SpecFlow.Specs/xUnit.AssemblyHooks.cs @@ -2,27 +2,27 @@ #pragma warning disable using System.CodeDom.Compiler; +using System.Threading.Tasks; using global::System.Runtime.CompilerServices; [assembly: global::Xunit.TestFramework("TechTalk.SpecFlow.xUnit.SpecFlowPlugin.XunitTestFrameworkWithAssemblyFixture", "TechTalk.SpecFlow.xUnit.SpecFlowPlugin")] [assembly: global::TechTalk.SpecFlow.xUnit.SpecFlowPlugin.AssemblyFixture(typeof(global::TechTalk_SpecFlow_Specs_XUnitAssemblyFixture))] [GeneratedCode("SpecFlow", "")] -public class TechTalk_SpecFlow_Specs_XUnitAssemblyFixture : global::System.IDisposable +public class TechTalk_SpecFlow_Specs_XUnitAssemblyFixture : global::Xunit.IAsyncLifetime { - private readonly global::System.Reflection.Assembly _currentAssembly; - [MethodImpl(MethodImplOptions.NoInlining)] - public TechTalk_SpecFlow_Specs_XUnitAssemblyFixture() + public async Task InitializeAsync() { - _currentAssembly = typeof(TechTalk_SpecFlow_Specs_XUnitAssemblyFixture).Assembly; - global::TechTalk.SpecFlow.TestRunnerManager.OnTestRunStart(_currentAssembly); + var currentAssembly = typeof(TechTalk_SpecFlow_Specs_XUnitAssemblyFixture).Assembly; + await global::TechTalk.SpecFlow.TestRunnerManager.OnTestRunStartAsync(currentAssembly); } [MethodImpl(MethodImplOptions.NoInlining)] - public void Dispose() + public async Task DisposeAsync() { - global::TechTalk.SpecFlow.TestRunnerManager.OnTestRunEnd(_currentAssembly); + var currentAssembly = typeof(TechTalk_SpecFlow_Specs_XUnitAssemblyFixture).Assembly; + await global::TechTalk.SpecFlow.TestRunnerManager.OnTestRunEndAsync(currentAssembly); } } #pragma warning restore diff --git a/Tests/TechTalk.SpecFlow.Specs/xUnitConfiguration.cs b/Tests/TechTalk.SpecFlow.Specs/xUnitConfiguration.cs new file mode 100644 index 000000000..b928bc0e0 --- /dev/null +++ b/Tests/TechTalk.SpecFlow.Specs/xUnitConfiguration.cs @@ -0,0 +1,3 @@ +using Xunit; + +[assembly: CollectionBehavior(DisableTestParallelization = false)]