test: deterministic CI-flake fixes (FOCAS alarm resubscribe, Modbus dead-port connect, S7 array poll)

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-08 21:37:51 -04:00
parent e7dd742541
commit f594b29a7a
3 changed files with 18 additions and 3 deletions
@@ -21,6 +21,9 @@ public sealed class S7PollEngineMigrationTests
private sealed class ConstantArrayFactory : IS7PlcFactory
{
public bool ReadThrows { get; set; }
private int _reads;
public int Reads => Volatile.Read(ref _reads);
public void CountRead() => Interlocked.Increment(ref _reads);
public IS7Plc Create(S7CpuType cpuType, string host, int port, short rack, short slot, TimeSpan timeout)
=> new ConstantArrayPlc(this);
}
@@ -39,6 +42,7 @@ public sealed class S7PollEngineMigrationTests
public Task<byte[]> ReadBytesAsync(S7NetDataType area, int db, int startByteAdr, int count, CancellationToken ct)
{
owner.CountRead();
if (owner.ReadThrows) throw new PlcException(ErrorCode.ConnectionError, "poll failure");
// Fresh array instance each call, identical contents: BE words 1, 2, 3.
var block = new byte[count];
@@ -81,7 +85,16 @@ public sealed class S7PollEngineMigrationTests
drv.OnDataChange += (_, e) => events.Enqueue(e);
var h = await drv.SubscribeAsync(["Arr"], TimeSpan.FromMilliseconds(100), TestContext.Current.CancellationToken);
await Task.Delay(500, TestContext.Current.CancellationToken); // several poll cycles
// Wait for the initial push (generous deadline: a loaded CI box can starve the first poll), then
// for several MORE reads to complete as the positive control that the absence below is meaningful.
var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(30);
while (events.IsEmpty && DateTime.UtcNow < deadline)
await Task.Delay(10, TestContext.Current.CancellationToken);
events.Count.ShouldBeGreaterThan(0, "the initial-data push never landed");
var readsAtPush = factory.Reads;
while (factory.Reads < readsAtPush + 4 && DateTime.UtcNow < deadline)
await Task.Delay(10, TestContext.Current.CancellationToken);
factory.Reads.ShouldBeGreaterThanOrEqualTo(readsAtPush + 4, "later poll cycles never ran");
await drv.UnsubscribeAsync(h, TestContext.Current.CancellationToken);
events.Count.ShouldBe(1); // only the initial-data push