using Shouldly; using Xunit; namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Tests; /// /// #630 (same pattern) — InitializeAsync on a live driver overwrote _devices[host] and /// orphaned the previous state's probe loop + client. It now releases them first. /// [Trait("Category", "Unit")] public sealed class TwinCATReinitializeReleasesStateTests { /// Initialize twice: the first state's probe loop has finished and its client is disposed. [Fact] public async Task SecondInitialize_StopsFirstStatesLoop_AndDisposesItsClient() { const string host = "ads://5.23.91.23.1.1:851"; var factory = new FakeTwinCATClientFactory(); var drv = new TwinCATDriver(new TwinCATDriverOptions { Devices = [new TwinCATDeviceOptions(host)], Probe = new TwinCATProbeOptions { Enabled = true, Interval = TimeSpan.FromMilliseconds(20), Timeout = TimeSpan.FromSeconds(1) }, }, "drv-reinit", factory); await drv.InitializeAsync("{}", CancellationToken.None); var first = drv.GetDeviceState(host)!; // Positive control: the first probe loop is running and holds a connected client. var deadline = DateTime.UtcNow.AddSeconds(5); while (!(factory.Clients.Count > 0 && factory.Clients[0].IsConnected)) { if (DateTime.UtcNow > deadline) throw new TimeoutException("probe never connected"); await Task.Delay(10); } var firstClient = factory.Clients[0]; var firstLoop = first.ProbeTask; firstLoop.ShouldNotBeNull(); firstLoop!.IsCompleted.ShouldBeFalse(); await drv.InitializeAsync("{}", CancellationToken.None); firstLoop.IsCompleted.ShouldBeTrue(); // joined firstClient.DisposeCount.ShouldBeGreaterThanOrEqualTo(1); firstClient.IsConnected.ShouldBeFalse(); drv.GetDeviceState(host).ShouldNotBeSameAs(first); await drv.ShutdownAsync(CancellationToken.None); } }