test(s7): wait out the clock tick before asserting LastSuccessfulRead advanced (#589)
The driver stamps DateTime.UtcNow at init and again per read pass; under CPU contention both land in the same tick, so the strict greater-than assertions flaked (reproduced 1/40 under load, sibling test A_cancelled_pass_still_flushes... failed identically with equal timestamps). Test-only race; no production defect. Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -451,7 +451,7 @@ public sealed class S7SpanReadParityTests
|
||||
|
||||
// The connect stamped a LastSuccessfulRead; the pass must move it PAST that value, so a
|
||||
// frozen timestamp is distinguishable from a merely non-null one.
|
||||
var before = driver.GetHealth().LastSuccessfulRead;
|
||||
var before = BaselineStamp(driver);
|
||||
before.ShouldNotBeNull();
|
||||
|
||||
var results = await driver.ReadAsync(ScatteredRefs(3), ct);
|
||||
@@ -502,7 +502,7 @@ public sealed class S7SpanReadParityTests
|
||||
using var driver = new S7Driver(Options(tags, maxGap: 16), "s7-span-decode-fault", factory);
|
||||
await driver.InitializeAsync("{}", ct);
|
||||
|
||||
var before = driver.GetHealth().LastSuccessfulRead;
|
||||
var before = BaselineStamp(driver);
|
||||
var results = await driver.ReadAsync(["BadStr", "GoodWord"], ct);
|
||||
|
||||
// ONE block read served both members — this really is the span path, not two per-tag reads.
|
||||
@@ -548,7 +548,7 @@ public sealed class S7SpanReadParityTests
|
||||
using var driver = new S7Driver(Options(NarrowTags(), maxGap: -1), "s7-access-denied", factory);
|
||||
await driver.InitializeAsync("{}", ct);
|
||||
|
||||
var before = driver.GetHealth().LastSuccessfulRead;
|
||||
var before = BaselineStamp(driver);
|
||||
var results = await driver.ReadAsync(["A", "B", "C"], ct);
|
||||
|
||||
results[0].StatusCode.ShouldNotBe(0u);
|
||||
@@ -589,7 +589,7 @@ public sealed class S7SpanReadParityTests
|
||||
using var driver = new S7Driver(Options(NarrowTags(), maxGap: -1), "s7-escalation-message", factory);
|
||||
await driver.InitializeAsync("{}", ct);
|
||||
|
||||
var before = driver.GetHealth().LastSuccessfulRead;
|
||||
var before = BaselineStamp(driver);
|
||||
var results = await driver.ReadAsync(["A", "B", "C"], ct);
|
||||
|
||||
results[0].StatusCode.ShouldBe(StatusBadDeviceFailure);
|
||||
@@ -632,7 +632,7 @@ public sealed class S7SpanReadParityTests
|
||||
using var driver = new S7Driver(Options(tags, maxGap: -1), "s7-cancel-flush", factory);
|
||||
await driver.InitializeAsync("{}", ct);
|
||||
|
||||
var before = driver.GetHealth().LastSuccessfulRead;
|
||||
var before = BaselineStamp(driver);
|
||||
|
||||
using var cts = new CancellationTokenSource();
|
||||
var reading = driver.ReadAsync(["Narrow", "Wide"], cts.Token);
|
||||
@@ -802,6 +802,21 @@ public sealed class S7SpanReadParityTests
|
||||
// ── Fixture ──────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Driver options over the given tags with the span-grouping knobs under test.</summary>
|
||||
/// <summary>
|
||||
/// The post-initialise <c>LastSuccessfulRead</c> stamp, taken only once the wall clock has
|
||||
/// moved past it. The driver stamps <c>DateTime.UtcNow</c> at init and again at the read
|
||||
/// pass; under CPU contention both can land in the same clock tick, so a strict
|
||||
/// "<c>LastSuccessfulRead</c> advanced" assertion flaked (#589). Waiting out the tick makes
|
||||
/// any later stamp strictly greater without a fixed delay.
|
||||
/// </summary>
|
||||
private static DateTime? BaselineStamp(S7Driver driver)
|
||||
{
|
||||
var stamp = driver.GetHealth().LastSuccessfulRead;
|
||||
if (stamp is { } s)
|
||||
SpinWait.SpinUntil(() => DateTime.UtcNow > s);
|
||||
return stamp;
|
||||
}
|
||||
|
||||
private static S7DriverOptions Options(IReadOnlyList<RawTagEntry> tags, int maxGap) => new()
|
||||
{
|
||||
Host = "192.0.2.1",
|
||||
|
||||
Reference in New Issue
Block a user