feat(otopcua): DriverInstanceActor honors RediscoverPolicy (Never/Once/UntilStable) (follow-up B)

This commit is contained in:
Joseph Doherty
2026-06-26 12:32:28 -04:00
parent efbdaf853c
commit ce34816a50
2 changed files with 90 additions and 4 deletions
@@ -171,6 +171,56 @@ public sealed class DriverInstanceActorDiscoveryTests : RuntimeActorTestBase
driver.DiscoverCount.ShouldBe(3);
}
/// <summary>
/// A driver whose <see cref="ITagDiscovery.RediscoverPolicy"/> is
/// <see cref="DiscoveryRediscoverPolicy.Never"/> opts out of post-connect discovery entirely: the
/// Connected entry's discovery kick returns before scheduling the first tick, so the driver is never
/// asked to discover and the parent receives no <see cref="DriverInstanceActor.DiscoveredNodesReady"/>.
/// </summary>
[Fact]
public void Discovery_policy_Never_runs_no_passes_and_publishes_nothing()
{
var driver = new DiscoverableStubDriver(DiscoveryRediscoverPolicy.Never);
var parent = CreateTestProbe();
var actor = parent.ChildActorOf(DriverInstanceActor.Props(
driver, rediscoverInterval: TimeSpan.FromMilliseconds(20)));
actor.Tell(new DriverInstanceActor.InitializeRequested("{}"));
// Connect happened (the discovery decision is made on the Connected entry)...
AwaitCondition(() => driver.InitializeCount > 0, TimeSpan.FromSeconds(2));
// ...but policy=Never ⇒ no discovery pass is ever run and nothing is published.
parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300));
driver.DiscoverCount.ShouldBe(0);
}
/// <summary>
/// A driver whose <see cref="ITagDiscovery.RediscoverPolicy"/> is
/// <see cref="DiscoveryRediscoverPolicy.Once"/> runs EXACTLY one post-connect pass even when its
/// discovered set would keep growing forever — under <c>UntilStable</c> the never-repeating signature
/// would retry to the attempt cap. Exactly one <see cref="DriverInstanceActor.DiscoveredNodesReady"/>
/// is published and no further <c>RediscoverTick</c> is scheduled.
/// </summary>
[Fact]
public void Discovery_policy_Once_publishes_exactly_one_pass_even_when_set_keeps_growing()
{
var driver = new GrowingDiscoverableStubDriver(DiscoveryRediscoverPolicy.Once);
var parent = CreateTestProbe();
var actor = parent.ChildActorOf(DriverInstanceActor.Props(
driver, rediscoverInterval: TimeSpan.FromMilliseconds(20)));
actor.Tell(new DriverInstanceActor.InitializeRequested("{}"));
// Exactly one pass is published (the first, growing set → 1 node)...
var only = parent.ExpectMsg<DriverInstanceActor.DiscoveredNodesReady>(TimeSpan.FromSeconds(2));
only.Nodes.Count.ShouldBe(1);
only.DriverInstanceId.ShouldBe(driver.DriverInstanceId);
// ...and NO second tick is scheduled, even though the set would keep growing under UntilStable.
parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300));
driver.DiscoverCount.ShouldBe(1);
}
/// <summary>
/// The per-pass discovery timeout is injectable via <see cref="DriverInstanceActor.Props"/> so tests
/// can control it without real-time delays. The default constant must be 30 seconds (behaviour-preserving).
@@ -206,6 +256,15 @@ public sealed class DriverInstanceActorDiscoveryTests : RuntimeActorTestBase
{
private int _passCount;
/// <summary>Constructs the fake reporting the given <see cref="DiscoveryRediscoverPolicy"/>;
/// defaults to <see cref="DiscoveryRediscoverPolicy.UntilStable"/> (the interface default) so the
/// existing UntilStable tests are unaffected.</summary>
public DiscoverableStubDriver(DiscoveryRediscoverPolicy policy = DiscoveryRediscoverPolicy.UntilStable)
=> RediscoverPolicy = policy;
/// <summary>The post-connect re-discovery policy this fake reports to the actor.</summary>
public DiscoveryRediscoverPolicy RediscoverPolicy { get; }
/// <summary>Number of <see cref="DiscoverAsync"/> passes the actor has driven.</summary>
public int DiscoverCount => Volatile.Read(ref _passCount);
@@ -266,6 +325,16 @@ public sealed class DriverInstanceActorDiscoveryTests : RuntimeActorTestBase
{
private int _passCount;
/// <summary>Constructs the fake reporting the given <see cref="DiscoveryRediscoverPolicy"/>;
/// defaults to <see cref="DiscoveryRediscoverPolicy.UntilStable"/> (the interface default) so the
/// existing attempt-cap test is unaffected. With <see cref="DiscoveryRediscoverPolicy.Once"/> the
/// ever-growing set proves the actor stops after a single pass (UntilStable would keep retrying).</summary>
public GrowingDiscoverableStubDriver(DiscoveryRediscoverPolicy policy = DiscoveryRediscoverPolicy.UntilStable)
=> RediscoverPolicy = policy;
/// <summary>The post-connect re-discovery policy this fake reports to the actor.</summary>
public DiscoveryRediscoverPolicy RediscoverPolicy { get; }
/// <summary>Number of <see cref="DiscoverAsync"/> passes the actor has driven.</summary>
public int DiscoverCount => Volatile.Read(ref _passCount);