diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Series/WireBackendCoverageTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Series/WireBackendCoverageTests.cs
index 4cbf460e..ae0055b3 100644
--- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Series/WireBackendCoverageTests.cs
+++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Series/WireBackendCoverageTests.cs
@@ -141,6 +141,86 @@ public sealed class WireBackendCoverageTests
}
}
+ ///
+ /// #740 / R07:H13 — re-initialising a live driver (the fixed-tree loop running, reads in flight over the real
+ /// wire) swaps the device table instead of clearing it under its readers: no reader throws, reads keep
+ /// returning Good, and shutdown leaves no session behind (a fresh driver can still connect afterwards).
+ ///
+ [Fact]
+ public async Task Reinitialise_under_live_reads_swaps_the_device_table()
+ {
+ if (_fx.SkipReason is not null) Assert.Skip(_fx.SkipReason);
+ var ct = TestContext.Current.CancellationToken;
+
+ await _fx.LoadProfileAsync("FWLIB64", ct);
+ await _fx.PatchStateAsync(new
+ {
+ macros = new Dictionary { ["500"] = new { value = 42000, @decimal = 3 } },
+ }, ct);
+
+ var drv = new FocasDriver(new FocasDriverOptions
+ {
+ Devices = [new FocasDeviceOptions(DeviceHost)],
+ RawTags = FocasRawTags.Entries(
+ new FocasTagDefinition("Macro500", DeviceHost, "MACRO:500", FocasDataType.Float64, Writable: false)),
+ Probe = new FocasProbeOptions { Enabled = false },
+ FixedTree = new FocasFixedTreeOptions
+ {
+ Enabled = true,
+ PollInterval = TimeSpan.FromMilliseconds(50),
+ ProgramPollInterval = TimeSpan.Zero,
+ TimerPollInterval = TimeSpan.Zero,
+ },
+ }, driverInstanceId: "wire-reinit-swap", clientFactory: new WireFocasClientFactory());
+
+ await using (drv)
+ {
+ await drv.InitializeAsync("{}", ct);
+ using var stop = CancellationTokenSource.CreateLinkedTokenSource(ct);
+ var errors = new ConcurrentQueue();
+ var good = 0;
+ var reader = Task.Run(async () =>
+ {
+ while (!stop.IsCancellationRequested)
+ {
+ try
+ {
+ _ = drv.GetHostStatuses();
+ var s = await drv.ReadAsync(["Macro500", $"{DeviceHost}/Identity/SeriesNumber"], CancellationToken.None);
+ if (s[0].StatusCode == FocasStatusMapper.Good) Interlocked.Increment(ref good);
+ }
+ catch (Exception ex) { errors.Enqueue(ex); }
+ }
+ }, CancellationToken.None);
+
+ for (var i = 0; i < 5; i++)
+ {
+ await Task.Delay(150, ct);
+ await drv.InitializeAsync("{}", ct);
+ }
+ await Task.Delay(300, ct);
+ stop.Cancel();
+ await reader;
+
+ errors.ShouldBeEmpty();
+ good.ShouldBeGreaterThan(0, "positive control: reads went over the wire between re-initialises");
+ }
+
+ // A fresh driver still connects and reads Good after the re-initialise churn and the shutdown.
+ var after = new FocasDriver(new FocasDriverOptions
+ {
+ Devices = [new FocasDeviceOptions(DeviceHost)],
+ RawTags = FocasRawTags.Entries(
+ new FocasTagDefinition("Macro500", DeviceHost, "MACRO:500", FocasDataType.Float64, Writable: false)),
+ Probe = new FocasProbeOptions { Enabled = false },
+ }, driverInstanceId: "wire-reinit-after", clientFactory: new WireFocasClientFactory());
+ await using (after)
+ {
+ await after.InitializeAsync("{}", ct);
+ (await after.ReadAsync(["Macro500"], ct))[0].StatusCode.ShouldBe(FocasStatusMapper.Good);
+ }
+ }
+
/// Verifies that discover emits device folder and tag variables.
[Fact]
public async Task Discover_emits_device_folder_and_tag_variables()