diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Series/WireBackendTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Series/WireBackendTests.cs index 19ef82d0..6b2f619e 100644 --- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Series/WireBackendTests.cs +++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Series/WireBackendTests.cs @@ -71,7 +71,7 @@ public sealed class WireBackendTests await driver.InitializeAsync("{}", ct); await WaitFor(() => - driver.GetDeviceState(DeviceHost) is { FixedTreeCache: not null }, TimeSpan.FromSeconds(5)); + driver.GetDeviceState(DeviceHost) is { FixedTreeCache: not null }, WaitTimeout); var state = driver.GetDeviceState(DeviceHost); state.ShouldNotBeNull(); @@ -82,7 +82,7 @@ public sealed class WireBackendTests await WaitFor(() => state.LastFixedSnapshots.ContainsKey($"{DeviceHost}/Axes/X/AbsolutePosition"), - TimeSpan.FromSeconds(3)); + WaitTimeout); state.LastFixedSnapshots[$"{DeviceHost}/Axes/X/AbsolutePosition"].ShouldBe(123456); } } @@ -136,7 +136,7 @@ public sealed class WireBackendTests await WaitFor(() => driver.GetDeviceState(DeviceHost) is { LastProgramInfo: not null }, - TimeSpan.FromSeconds(5)); + WaitTimeout); var snapshots = await driver.ReadAsync( [$"{DeviceHost}/Program/Name", @@ -194,7 +194,7 @@ public sealed class WireBackendTests { var state = driver.GetDeviceState(DeviceHost); return state is not null && state.LastTimers.Count == 4; - }, TimeSpan.FromSeconds(5)); + }, WaitTimeout); var snapshots = await driver.ReadAsync( [$"{DeviceHost}/Timers/PowerOnSeconds", @@ -265,8 +265,11 @@ public sealed class WireBackendTests { var state = driver.GetDeviceState(DeviceHost); return state?.FixedTreeCache is { Capabilities.SpindleLoad: true, Capabilities.SpindleMotorSpeed: true } - && state.LastSpindleLoads.Count >= 2; - }, TimeSpan.FromSeconds(5)); + && state.LastSpindleLoads.Count >= 2 + // Motor speeds are cached after a SECOND wire round-trip (FocasDriver spindle poll), so the loads + // arriving first does not mean the speeds have — waiting on loads alone raced under load (#678.3). + && state.LastSpindleMotorSpeeds.Count >= 2; + }, WaitTimeout); var snapshots = await driver.ReadAsync( [$"{DeviceHost}/Spindle/S1/Load", @@ -282,6 +285,10 @@ public sealed class WireBackendTests } } + /// Generous: the sim fixture answers fast, but a loaded CI host (e.g. a concurrent docker build) does not. + private static readonly TimeSpan WaitTimeout = TimeSpan.FromSeconds(15); + + /// Polls ; FAILS the test with a clear message on timeout rather than returning silently. private static async Task WaitFor(Func pred, TimeSpan timeout) { var deadline = DateTime.UtcNow + timeout; @@ -290,5 +297,7 @@ public sealed class WireBackendTests if (pred()) return; await Task.Delay(50); } + if (pred()) return; + Assert.Fail($"Condition not met within {timeout.TotalSeconds:0.#}s waiting on the FOCAS wire backend."); } }