test(s7): assert the stale handle is disposed, not just replaced, in the cancellation-reopen pins

This commit is contained in:
Joseph Doherty
2026-08-17 05:39:47 -04:00
parent 1979cf4119
commit 120e885b31
@@ -473,12 +473,13 @@ public sealed class S7SpanReadParityTests
await Should.ThrowAsync<OperationCanceledException>(async () => await reading);
// The reopen is the observable half of the contract: the desynced handle was discarded, so
// the next read builds a NEW connection rather than reusing it.
// The reopen is the observable half of the contract: the desynced handle was DISPOSED — not
// merely leaked behind a second connection — and the next read builds a new one.
factory.HangReadBytes = false;
var recovered = await driver.ReadAsync(refs, ct);
recovered.ShouldAllBe(s => s.StatusCode == 0u);
factory.CreatedPlcCount.ShouldBe(2);
factory.Plcs[0].Disposed.ShouldBeTrue();
}
/// <summary>
@@ -507,6 +508,7 @@ public sealed class S7SpanReadParityTests
var recovered = await driver.ReadAsync(refs, ct);
recovered.ShouldAllBe(s => s.StatusCode == 0u);
factory.CreatedPlcCount.ShouldBe(2);
factory.Plcs[0].Disposed.ShouldBeTrue();
}
// ── Configurability ──────────────────────────────────────────────────────────────────
@@ -640,18 +642,30 @@ public sealed class S7SpanReadParityTests
/// <summary>How many connections this factory has been asked to build — the reopen observable.</summary>
public int CreatedPlcCount;
/// <summary>
/// Every connection this factory built, in build order, so a test can assert what became
/// of the STALE one — a driver that leaked the old handle and merely opened a second
/// would satisfy <see cref="CreatedPlcCount"/> alone.
/// </summary>
public List<ImageS7Plc> Plcs { get; } = new();
/// <inheritdoc />
public IS7Plc Create(S7CpuType cpuType, string host, int port, short rack, short slot, TimeSpan timeout)
{
Interlocked.Increment(ref CreatedPlcCount);
return new ImageS7Plc(this);
var plc = new ImageS7Plc(this);
lock (Plcs) Plcs.Add(plc);
return plc;
}
private sealed class ImageS7Plc(ImageS7PlcFactory owner) : IS7Plc
public sealed class ImageS7Plc(ImageS7PlcFactory owner) : IS7Plc
{
/// <inheritdoc />
public bool IsConnected { get; private set; }
/// <summary>True once the driver has disposed this handle — the discard half of a reopen.</summary>
public bool Disposed { get; private set; }
/// <inheritdoc />
public Task OpenAsync(CancellationToken ct) { IsConnected = true; return Task.CompletedTask; }
@@ -771,7 +785,11 @@ public sealed class S7SpanReadParityTests
public Task ReadStatusAsync(CancellationToken ct) => Task.CompletedTask;
/// <inheritdoc />
public void Dispose() => IsConnected = false;
public void Dispose()
{
IsConnected = false;
Disposed = true;
}
}
}
}