Skip to content

Commit

Permalink
Capture ExecutionContext after every binding invoke (#126)
Browse files Browse the repository at this point in the history
  • Loading branch information
obligaron authored May 13, 2024
1 parent 0aecb34 commit c9e4984
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 18 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
20 changes: 11 additions & 9 deletions Reqnroll/Bindings/BindingDelegateInvoker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,18 @@ public virtual async Task<object> 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<object> InvokeInExecutionContext(ExecutionContext executionContext, Func<Task<object>> callback)
Expand Down
31 changes: 22 additions & 9 deletions Tests/Reqnroll.RuntimeTests/Bindings/BindingInvokerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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<StepDefClassWithAsyncLocal>();
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<StepDefClassWithAsyncLocal>();
stepDefClass.LoadedValue.Should().Be("42", $"Error was not propagated");
}

#endregion

#region Exception Handling related tests - regression tests for SF2649
Expand Down

0 comments on commit c9e4984

Please sign in to comment.