From 3aff447984801aeae7542e6db7063f0164aea942 Mon Sep 17 00:00:00 2001 From: obligaron Date: Fri, 10 May 2024 22:23:27 +0200 Subject: [PATCH] Capture ExecutionContext after every binding invoke --- CHANGELOG.md | 1 + Reqnroll/Bindings/BindingDelegateInvoker.cs | 20 ++++++------ .../Bindings/BindingInvokerTests.cs | 31 +++++++++++++------ 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c589f6cb..bacad49b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * Fix: Adding @ignore to an Examples block generates invalid code for NUnit v3+ (#103) * Fix: #111 @ignore attribute is not inherited to the scenarios from Rule * Support for JSON files added to SpecFlow.ExternalData +* Fix: #120 Capture ExecutionContext after every binding invoke # v1.0.1 - 2024-02-16 diff --git a/Reqnroll/Bindings/BindingDelegateInvoker.cs b/Reqnroll/Bindings/BindingDelegateInvoker.cs index bfbf10da1..3e9f770e5 100644 --- a/Reqnroll/Bindings/BindingDelegateInvoker.cs +++ b/Reqnroll/Bindings/BindingDelegateInvoker.cs @@ -22,16 +22,18 @@ public virtual async Task InvokeDelegateAsync(Delegate bindingDelegate, // The ExecutionContext only flows down, so async binding methods cannot directly change it, but even if all binding method // is async the constructor of the binding classes are run in sync, so they should be able to change the ExecutionContext. // (The binding classes are created as part of the 'bindingDelegate' this method receives. - - try - { - return await InvokeInExecutionContext(executionContext?.Value, () => CreateDelegateInvocationTask(bindingDelegate, invokeArgs)); - } - finally + return await InvokeInExecutionContext(executionContext?.Value, () => { - if (executionContext != null) - executionContext.Value = ExecutionContext.Capture(); - } + try + { + return CreateDelegateInvocationTask(bindingDelegate, invokeArgs); + } + finally + { + if (executionContext != null) + executionContext.Value = ExecutionContext.Capture(); + } + }); } private Task InvokeInExecutionContext(ExecutionContext executionContext, Func> callback) diff --git a/Tests/Reqnroll.RuntimeTests/Bindings/BindingInvokerTests.cs b/Tests/Reqnroll.RuntimeTests/Bindings/BindingInvokerTests.cs index af6bbfbb4..e965c7f1f 100644 --- a/Tests/Reqnroll.RuntimeTests/Bindings/BindingInvokerTests.cs +++ b/Tests/Reqnroll.RuntimeTests/Bindings/BindingInvokerTests.cs @@ -283,35 +283,35 @@ class StepDefClassWithAsyncLocal public string LoadedValue { get; set; } // ReSharper disable once UnusedMember.Local - public void SetAsyncLocal_Sync(AsyncLocalType asyncLocalType) + public void SetAsyncLocal_Sync(AsyncLocalType asyncLocalType, string content) { switch (asyncLocalType) { case AsyncLocalType.Uninitialized: - _uninitializedAsyncLocal.Value = "42"; + _uninitializedAsyncLocal.Value = content; break; case AsyncLocalType.CtorInitialized: - _ctorInitializedAsyncLocal.Value = "42"; + _ctorInitializedAsyncLocal.Value = content; break; case AsyncLocalType.Boxed: - _boxedAsyncLocal.Value!.Value = "42"; + _boxedAsyncLocal.Value!.Value = content; break; } } // ReSharper disable once UnusedMember.Local - public async Task SetAsyncLocal_Async(AsyncLocalType asyncLocalType) + public async Task SetAsyncLocal_Async(AsyncLocalType asyncLocalType, string content) { switch (asyncLocalType) { case AsyncLocalType.Uninitialized: - _uninitializedAsyncLocal.Value = "42"; + _uninitializedAsyncLocal.Value = content; break; case AsyncLocalType.CtorInitialized: - _ctorInitializedAsyncLocal.Value = "42"; + _ctorInitializedAsyncLocal.Value = content; break; case AsyncLocalType.Boxed: - _boxedAsyncLocal.Value!.Value = "42"; + _boxedAsyncLocal.Value!.Value = content; break; } await Task.Delay(1); @@ -352,13 +352,26 @@ public async Task ExecutionContext_is_flowing_down_correctly_to_step_definitions var contextManager = CreateContextManagerWith(); if (setAs != null) - await InvokeBindingAsync(sut, contextManager, typeof(StepDefClassWithAsyncLocal), "SetAsyncLocal_" + setAs, asyncLocalType); + await InvokeBindingAsync(sut, contextManager, typeof(StepDefClassWithAsyncLocal), "SetAsyncLocal_" + setAs, asyncLocalType, "42"); await InvokeBindingAsync(sut, contextManager, typeof(StepDefClassWithAsyncLocal), nameof(StepDefClassWithAsyncLocal.GetAsyncLocal_Async), asyncLocalType, expectedResult); var stepDefClass = contextManager.ScenarioContext.ScenarioContainer.Resolve(); stepDefClass.LoadedValue.Should().Be(expectedResult, $"Error was not propagated for {description}"); } + [Fact] + public async Task ExecutionContext_can_be_changed_multiple_times() + { + var sut = CreateSut(); + var contextManager = CreateContextManagerWith(); + + await InvokeBindingAsync(sut, contextManager, typeof(StepDefClassWithAsyncLocal), "SetAsyncLocal_Sync", AsyncLocalType.Uninitialized, "14"); + await InvokeBindingAsync(sut, contextManager, typeof(StepDefClassWithAsyncLocal), "SetAsyncLocal_Sync", AsyncLocalType.Uninitialized, "42"); + await InvokeBindingAsync(sut, contextManager, typeof(StepDefClassWithAsyncLocal), nameof(StepDefClassWithAsyncLocal.GetAsyncLocal_Async), AsyncLocalType.Uninitialized, "42"); + var stepDefClass = contextManager.ScenarioContext.ScenarioContainer.Resolve(); + stepDefClass.LoadedValue.Should().Be("42", $"Error was not propagated"); + } + #endregion #region Exception Handling related tests - regression tests for SF2649