test(archreview #10): cover actor-context preservation through the REAL CapabilityInvoker
The pass-through NullDriverCapabilityInvoker returns the call-site task directly, so it never exercises the real invoker's internal `pipeline.ExecuteAsync(...).ConfigureAwait(false)`. Adds a test-only Core reference to Runtime.Tests (the PRODUCTION Runtime assembly stays Polly-free via the seam) and a discovery test that drives DriverInstanceActor through a real CapabilityInvoker over a genuinely-yielding ITagDiscovery driver — proving the invoker's internal ConfigureAwait(false) does NOT leak to the actor's own await, so the post-await Context.Parent.Tell still runs (DiscoveredNodesReady arrives). Runtime.Tests 358, Core.Tests 238.
This commit is contained in:
+33
@@ -2,6 +2,7 @@ using Akka.Actor;
|
|||||||
using Shouldly;
|
using Shouldly;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Core.Resilience;
|
||||||
using ZB.MOM.WW.OtOpcUa.Runtime.Drivers;
|
using ZB.MOM.WW.OtOpcUa.Runtime.Drivers;
|
||||||
using ZB.MOM.WW.OtOpcUa.Runtime.Tests.Harness;
|
using ZB.MOM.WW.OtOpcUa.Runtime.Tests.Harness;
|
||||||
|
|
||||||
@@ -144,6 +145,38 @@ public sealed class DriverInstanceActorDiscoveryTests : RuntimeActorTestBase
|
|||||||
published.DriverInstanceId.ShouldBe(driver.DriverInstanceId);
|
published.DriverInstanceId.ShouldBe(driver.DriverInstanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Arch-review #10: the SAME async-discovery context-preservation guarantee, but driven through the
|
||||||
|
/// REAL <see cref="CapabilityInvoker"/> — whose Polly pipeline is awaited with
|
||||||
|
/// <c>ConfigureAwait(false)</c> internally. This is the one path the pass-through
|
||||||
|
/// <c>NullDriverCapabilityInvoker</c> (used by the sibling test) cannot cover: it returns the
|
||||||
|
/// call-site task directly, so it never exercises the invoker's internal off-context await. If that
|
||||||
|
/// internal <c>ConfigureAwait(false)</c> leaked to the actor's own <c>await _invoker.ExecuteAsync(…)</c>,
|
||||||
|
/// the post-await <c>Context.Parent.Tell</c> in <c>HandleRediscoverAsync</c> would fault with
|
||||||
|
/// "no active ActorContext" and no message would arrive. Its arrival proves the actor context survives
|
||||||
|
/// the real resilience pipeline.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void Async_discovery_through_real_CapabilityInvoker_preserves_actor_context()
|
||||||
|
{
|
||||||
|
var pipelineBuilder = new DriverResiliencePipelineBuilder(); // real Polly pipeline, tier-A defaults
|
||||||
|
var invoker = new CapabilityInvoker(
|
||||||
|
pipelineBuilder,
|
||||||
|
driverInstanceId: "yielding-discoverable",
|
||||||
|
optionsAccessor: () => new DriverResilienceOptions { Tier = DriverTier.A },
|
||||||
|
driverType: "Stub");
|
||||||
|
var driver = new YieldingDiscoverableStubDriver();
|
||||||
|
var parent = CreateTestProbe();
|
||||||
|
var actor = parent.ChildActorOf(DriverInstanceActor.Props(
|
||||||
|
driver, rediscoverInterval: TimeSpan.FromMilliseconds(20), invoker: invoker));
|
||||||
|
|
||||||
|
actor.Tell(new DriverInstanceActor.InitializeRequested("{}"));
|
||||||
|
|
||||||
|
var published = parent.ExpectMsg<DriverInstanceActor.DiscoveredNodesReady>(TimeSpan.FromSeconds(5));
|
||||||
|
published.Nodes.Count.ShouldBe(3);
|
||||||
|
published.DriverInstanceId.ShouldBe(driver.DriverInstanceId);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The attempt cap bounds a discovered set that never stabilises: a driver whose set keeps GROWING
|
/// The attempt cap bounds a discovered set that never stabilises: a driver whose set keeps GROWING
|
||||||
/// (1,2,3,…) never repeats its signature, so the loop is stopped only by
|
/// (1,2,3,…) never repeats its signature, so the loop is stopped only by
|
||||||
|
|||||||
@@ -22,6 +22,10 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\..\src\Server\ZB.MOM.WW.OtOpcUa.Runtime\ZB.MOM.WW.OtOpcUa.Runtime.csproj"/>
|
<ProjectReference Include="..\..\..\src\Server\ZB.MOM.WW.OtOpcUa.Runtime\ZB.MOM.WW.OtOpcUa.Runtime.csproj"/>
|
||||||
|
<!-- TEST-ONLY reference to Core (Polly): lets the resilience-wiring guards construct the REAL
|
||||||
|
CapabilityInvoker. The PRODUCTION Runtime assembly stays Polly-free (consumes the
|
||||||
|
IDriverCapabilityInvoker seam) — this reference is in the test project only. -->
|
||||||
|
<ProjectReference Include="..\..\..\src\Core\ZB.MOM.WW.OtOpcUa.Core\ZB.MOM.WW.OtOpcUa.Core.csproj"/>
|
||||||
<ProjectReference Include="..\..\..\src\Core\ZB.MOM.WW.OtOpcUa.Configuration\ZB.MOM.WW.OtOpcUa.Configuration.csproj"/>
|
<ProjectReference Include="..\..\..\src\Core\ZB.MOM.WW.OtOpcUa.Configuration\ZB.MOM.WW.OtOpcUa.Configuration.csproj"/>
|
||||||
<ProjectReference Include="..\..\..\src\Core\ZB.MOM.WW.OtOpcUa.Commons\ZB.MOM.WW.OtOpcUa.Commons.csproj"/>
|
<ProjectReference Include="..\..\..\src\Core\ZB.MOM.WW.OtOpcUa.Commons\ZB.MOM.WW.OtOpcUa.Commons.csproj"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
Reference in New Issue
Block a user