test: deflake load-sensitive tests (lmxopcua#769)
v2-ci / unit-tests (push) Has been cancelled

- NodeManagerLockTimeBenchmark: wall-clock budget enforced only with OTOPCUA_BENCH=1; default run keeps the state-correctness asserts and logs timings.
- OpcUaClientDriverWire host-status: signal (semaphore) wait that only accepts a non-Running state observed after the server is disposed.
- AbCip PollLatencyComparison: retry once on a fixture connection reset; p95 criterion enforced only with AB_LAT_ENFORCE=1.
- ScriptRunGuardTests: orphan scripts held by a ctx.Released signal instead of a 250 ms wall-clock spin that could finish inside the 60 ms budget under load; stuck-orphan wall-clock rule disabled in the tests that pin the strike counting.
- DependencyMuxReplayTests: split the inside-grace positive control (1 h grace) from the release test (control asserted before unregister), removing the 200 ms grace race.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
Joseph Doherty
2026-11-10 05:39:22 -05:00
parent 000d7f5591
commit 11a5c1d4e2
6 changed files with 128 additions and 59 deletions
@@ -30,7 +30,21 @@ public sealed class PollLatencyComparisonTests(DifferentialRunFixture fixture)
var differential = await fixture.GetAsync();
using var cts = new CancellationTokenSource(RunBudget);
var r = await PollLatencyRun.ExecuteAsync(target, differential, cts.Token);
// lmxopcua#769: the shared ab_server fixture occasionally resets a connection under load ("connection reset by
// peer"). That is a fixture transport fault, not a latency finding, so the run is retried once when it surfaces
// as a thrown reset or a reported problem; any other failure (or a second reset) still fails the test.
LatencyResult r;
try { r = await PollLatencyRun.ExecuteAsync(target, differential, cts.Token); }
catch (Exception ex) when (IsFixtureReset(ex.ToString()))
{
Log($"Fixture transport reset on the first attempt, retrying once: {ex.Message}");
r = await PollLatencyRun.ExecuteAsync(target, differential, cts.Token);
}
if (r.Problems.Any(p => IsFixtureReset(p)))
{
Log($"Fixture transport reset reported as a problem on the first attempt, retrying once: {string.Join("; ", r.Problems)}");
r = await PollLatencyRun.ExecuteAsync(target, differential, cts.Token);
}
Log($"## Latency — {r.Target}");
Log($"Tags per pass: {r.Tags.Count} ({r.DistinctTags} distinct" +
@@ -86,7 +100,12 @@ public sealed class PollLatencyComparisonTests(DifferentialRunFixture fixture)
after.ShouldBeLessThanOrEqualTo(baseline, "the native session must be gone when the run reports");
// Review r1 M4: the latency setup's refused libplctag tags are orphan candidates too — checked after them.
LibplctagOrphanRelease.RootedCount.ShouldBe(0);
natP95.ShouldBeLessThanOrEqualTo(limit, $"native p95 must be ≤ libplctag p95 × {Tolerance:0.00}");
// The p95 comparison is a perf criterion against a shared fixture and a loaded host: it is reported always and
// enforced only when AB_LAT_ENFORCE=1 (like OTOPCUA_SCRIPT_PERF); the correctness checks above always run.
if (Environment.GetEnvironmentVariable("AB_LAT_ENFORCE") == "1")
natP95.ShouldBeLessThanOrEqualTo(limit, $"native p95 must be ≤ libplctag p95 × {Tolerance:0.00}");
else
Log($"(p95 criterion not enforced: set AB_LAT_ENFORCE=1 to fail on it; outcome above is {(natP95 <= limit ? "PASS" : "FAIL")})");
}
/// <summary>Offline: the tag pick takes atomic scalars only, and repeats a short set deliberately and visibly.</summary>
@@ -129,6 +148,10 @@ public sealed class PollLatencyComparisonTests(DifferentialRunFixture fixture)
private static string Rtt(ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Cip.ConnectionDiagnostics.ExchangeStats? s) =>
s is not { Count: > 0 } x ? "-" : $"{Ms(x.MinMs)}/{Ms(x.MeanMs)}/{Ms(x.MaxMs)}";
private static bool IsFixtureReset(string text) =>
text.Contains("reset by peer", StringComparison.OrdinalIgnoreCase)
|| text.Contains("connection reset", StringComparison.OrdinalIgnoreCase);
private static string Ms(double ms) => ms.ToString("0.0", CultureInfo.InvariantCulture);
private static void Log(string line) => TestContext.Current.TestOutputHelper?.WriteLine(line);