Files
lmxopcua/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests/AbLegacyReinitializeReleasesStateTests.cs
T
Joseph Doherty 1ea1da8236 refactor(runtime): delete the dead in-place ApplyDelta seam; config change = respawn only (#665)
Since #516 a DriverConfig change is stop + respawn, so DriverSpawnPlan.ToApplyDelta
was always empty and the in-place path had no producer. #663 and #664 were both
latent bugs in it. This deletes it:
- host: ApplyChildDelta, _pendingDelta, HandleApplyResult and HandleDeltaApplied
- plan: DriverSpawnPlan.ToApplyDelta
- actor: the ApplyDelta, ApplyResult and DeltaApplied messages, the Connected
  reinit handler and the mid-connect AdoptConfigDuringInit
- tests: the actor tests that drove the seam

IDriver.ReinitializeAsync and each driver's implementation and unit tests stay.
Comments and docs that cited AdoptConfigDuringInit as the reason a driver must
release prior state at init now cite the IDriver contract and overlapping
retries, which still apply.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 11:23:16 -04:00

51 lines
2.1 KiB
C#

using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.PlcFamilies;
namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests;
/// <summary>
/// #630 — <c>InitializeAsync</c> on a live driver with no teardown first (the <c>IDriver</c> contract
/// allows it) overwrote <c>_devices[host]</c> and orphaned the previous state's
/// probe loop + runtime. It now releases them first.
/// </summary>
[Trait("Category", "Unit")]
public sealed class AbLegacyReinitializeReleasesStateTests
{
/// <summary>Initialize twice: the first state's probe loop has stopped and its probe runtime is disposed.</summary>
[Fact]
public async Task SecondInitialize_StopsFirstStatesLoop_AndDisposesItsRuntime()
{
const string host = "ab://10.0.0.5/1,0";
var factory = new FakeAbLegacyTagFactory();
var drv = new AbLegacyDriver(new AbLegacyDriverOptions
{
Devices = [new AbLegacyDeviceOptions(host, AbLegacyPlcFamily.Slc500)],
Probe = new AbLegacyProbeOptions { Enabled = true, Interval = TimeSpan.FromMilliseconds(20), ProbeAddress = "S:0" },
}, "drv-reinit", factory);
await drv.InitializeAsync("{}", CancellationToken.None);
var first = drv.GetDeviceState(host)!;
// Positive control: the first state's probe loop is running and has created + initialised its runtime.
var deadline = DateTime.UtcNow.AddSeconds(5);
while (!(factory.Tags.TryGetValue("S:0", out var t) && t.InitializeCount > 0))
{
if (DateTime.UtcNow > deadline) throw new TimeoutException("probe never started");
await Task.Delay(10);
}
var firstTag = factory.Tags["S:0"];
var firstLoop = first.ProbeTask;
firstLoop.ShouldNotBeNull();
firstLoop!.IsCompleted.ShouldBeFalse();
firstTag.Disposed.ShouldBeFalse();
await drv.InitializeAsync("{}", CancellationToken.None);
firstLoop.IsCompleted.ShouldBeTrue(); // joined
firstTag.Disposed.ShouldBeTrue();
drv.GetDeviceState(host).ShouldNotBeSameAs(first);
await drv.ShutdownAsync(CancellationToken.None);
}
}