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:
@@ -250,7 +250,9 @@ public sealed class FocasAlarmProjectionTests
|
||||
{
|
||||
var (drv, factory) = NewDriver(Tag(ServoPath, "ALARM:Servo"), Tag(AnyPath, "ALARM:*"));
|
||||
await InitAndSubscribeForTicksAsync(drv, factory, ServoPath, AnyPath);
|
||||
var events = Capture(drv);
|
||||
// Transitions only: each resubscribe starts a real loop whose (deliberately failing) read can annotate a
|
||||
// quality-only BadNoCommunication event before PauseAsync lands - that raced the exact-list asserts (CI run 911).
|
||||
var events = Capture(drv, transitionsOnly: true);
|
||||
Tick(drv, Servo401());
|
||||
events.Count.ShouldBe(2);
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ public sealed class ModbusSocketLifecycleTests
|
||||
[Fact]
|
||||
public async Task Connect_to_dead_port_surfaces_socket_failure()
|
||||
{
|
||||
var life = new ModbusSocketLifecycle("127.0.0.1", 1, TimeSpan.FromMilliseconds(300),
|
||||
var life = new ModbusSocketLifecycle("127.0.0.1", 1, TimeSpan.FromSeconds(30), // loopback RST is immediate; a tight timeout races it under load
|
||||
autoReconnect: false);
|
||||
await Should.ThrowAsync<System.Net.Sockets.SocketException>(
|
||||
life.ConnectAsync(TestContext.Current.CancellationToken));
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user