fix(r2-01): extend reconnect fakes with token-honouring hang + probe-fault modes (task 0)
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
{
|
||||
"id": 0,
|
||||
"subject": "Extend reconnect-test fakes with token-honouring hang + probe-fault modes (FakeS7Plc/FakeS7PlcFactory)",
|
||||
"status": "pending",
|
||||
"status": "completed",
|
||||
"blockedBy": []
|
||||
},
|
||||
{
|
||||
|
||||
@@ -143,6 +143,21 @@ public sealed class S7DriverReconnectTests
|
||||
/// <summary>When set, the NEXT <see cref="Create"/>d connection throws this on OpenAsync (once).</summary>
|
||||
public Exception? NextOpenThrows { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of subsequently-created connections whose <see cref="FakeS7Plc.OpenAsync"/>
|
||||
/// hangs until its token is cancelled (simulating the unreachable-but-not-refusing host
|
||||
/// — pulled cable / firewall DROP — that STAB-14 needs and <c>docker restart</c> can't
|
||||
/// produce). Each <see cref="Create"/> decrements it while positive.
|
||||
/// </summary>
|
||||
public int OpenHangsUntilCancelledCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When set, copied onto every created connection's persistent
|
||||
/// <see cref="FakeS7Plc.ReadStatusThrows"/> so a probe fails on every tick. Clear it
|
||||
/// after init to arm connection #1 only.
|
||||
/// </summary>
|
||||
public Exception? ReadStatusThrowsForNewConnections { get; set; }
|
||||
|
||||
public IS7Plc Create(S7CpuType cpuType, string host, int port, short rack, short slot, TimeSpan timeout)
|
||||
{
|
||||
var plc = new FakeS7Plc();
|
||||
@@ -151,6 +166,13 @@ public sealed class S7DriverReconnectTests
|
||||
plc.OpenThrowsOnce = ex;
|
||||
NextOpenThrows = null;
|
||||
}
|
||||
if (OpenHangsUntilCancelledCount > 0)
|
||||
{
|
||||
plc.OpenHangsUntilCancelled = true;
|
||||
OpenHangsUntilCancelledCount--;
|
||||
}
|
||||
if (ReadStatusThrowsForNewConnections is { } rex)
|
||||
plc.ReadStatusThrows = rex;
|
||||
Created.Add(plc);
|
||||
return plc;
|
||||
}
|
||||
@@ -164,45 +186,76 @@ public sealed class S7DriverReconnectTests
|
||||
/// <summary>Thrown once by <see cref="OpenAsync"/>, then cleared.</summary>
|
||||
public Exception? OpenThrowsOnce { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When true, <see cref="OpenAsync"/> hangs until its token is cancelled, then throws
|
||||
/// <see cref="TaskCanceledException"/> — the connect-timeout shape (an unreachable host
|
||||
/// whose SYNs are dropped) the live rig can't cheaply produce (STAB-14).
|
||||
/// </summary>
|
||||
public bool OpenHangsUntilCancelled { get; set; }
|
||||
|
||||
/// <summary>Thrown once by the next <see cref="ReadAsync"/>, then cleared.</summary>
|
||||
public Exception? NextReadThrows { get; set; }
|
||||
|
||||
/// <summary>When true, <see cref="ReadAsync"/> hangs until its token is cancelled (STAB-15 cancel-mid-I/O).</summary>
|
||||
public bool ReadHangsUntilCancelled { get; set; }
|
||||
|
||||
/// <summary>When true, <see cref="WriteAsync"/> hangs until its token is cancelled (STAB-15 cancel-mid-I/O).</summary>
|
||||
public bool WriteHangsUntilCancelled { get; set; }
|
||||
|
||||
/// <summary>Thrown by EVERY <see cref="ReadStatusAsync"/> (persistent, not one-shot) — arms a probe fault (STAB-15b).</summary>
|
||||
public Exception? ReadStatusThrows { get; set; }
|
||||
|
||||
/// <summary>When true, <see cref="ReadStatusAsync"/> hangs until its token is cancelled (probe-timeout, STAB-15b).</summary>
|
||||
public bool ReadStatusHangsUntilCancelled { get; set; }
|
||||
|
||||
/// <summary>Boxed value returned by a good read (UInt16 tag → ushort).</summary>
|
||||
public object? ReadValue { get; set; } = (ushort)42;
|
||||
|
||||
public Task OpenAsync(CancellationToken cancellationToken)
|
||||
public async Task OpenAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (OpenHangsUntilCancelled)
|
||||
await Task.Delay(System.Threading.Timeout.Infinite, cancellationToken).ConfigureAwait(false);
|
||||
if (OpenThrowsOnce is { } ex)
|
||||
{
|
||||
OpenThrowsOnce = null;
|
||||
return Task.FromException(ex);
|
||||
throw ex;
|
||||
}
|
||||
IsConnected = true;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Close() => IsConnected = false;
|
||||
|
||||
public Task<object?> ReadAsync(string address, CancellationToken cancellationToken)
|
||||
public async Task<object?> ReadAsync(string address, CancellationToken cancellationToken)
|
||||
{
|
||||
if (ReadHangsUntilCancelled)
|
||||
await Task.Delay(System.Threading.Timeout.Infinite, cancellationToken).ConfigureAwait(false);
|
||||
if (NextReadThrows is { } ex)
|
||||
{
|
||||
NextReadThrows = null;
|
||||
return Task.FromException<object?>(ex);
|
||||
throw ex;
|
||||
}
|
||||
return Task.FromResult(ReadValue);
|
||||
return ReadValue;
|
||||
}
|
||||
|
||||
public Task<byte[]> ReadBytesAsync(S7NetDataType area, int db, int startByteAdr, int count, CancellationToken cancellationToken) =>
|
||||
throw new NotSupportedException("FakeS7Plc: block reads not needed for the reconnect tests");
|
||||
|
||||
public Task WriteAsync(string address, object value, CancellationToken cancellationToken) =>
|
||||
Task.CompletedTask;
|
||||
public async Task WriteAsync(string address, object value, CancellationToken cancellationToken)
|
||||
{
|
||||
if (WriteHangsUntilCancelled)
|
||||
await Task.Delay(System.Threading.Timeout.Infinite, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public Task WriteBytesAsync(S7NetDataType area, int db, int startByteAdr, byte[] value, CancellationToken cancellationToken) =>
|
||||
Task.CompletedTask;
|
||||
|
||||
public Task ReadStatusAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
public async Task ReadStatusAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (ReadStatusHangsUntilCancelled)
|
||||
await Task.Delay(System.Threading.Timeout.Infinite, cancellationToken).ConfigureAwait(false);
|
||||
if (ReadStatusThrows is { } ex)
|
||||
throw ex;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user