From 484f3d3d2d3b53ffcce74aeb71002400b45e8b84 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 10 Nov 2026 13:14:28 -0500 Subject: [PATCH] test(opcua): latch rejected profiling budgets through bounded observation --- .../Sdk2LifecycleSoakTests.cs | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests/Sdk2LifecycleSoakTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests/Sdk2LifecycleSoakTests.cs index e3fc36e3..560ad263 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests/Sdk2LifecycleSoakTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests/Sdk2LifecycleSoakTests.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.Net; using System.Net.Sockets; using System.Reflection; +using System.Runtime.ExceptionServices; using System.Text; using System.Text.Json; using Opc.Ua; @@ -64,6 +65,7 @@ public sealed class Sdk2LifecycleSoakTests var cycles = 0; var restarts = 0; Sample? baseline = null; + ExceptionDispatchInfo? firstRejectedBudget = null; var retained = new Queue(); try { @@ -113,9 +115,22 @@ public sealed class Sdk2LifecycleSoakTests sample.CachedCertificateStores.ShouldBe(baseline.CachedCertificateStores); sample.Variables.ShouldBe(baseline.Variables); sample.Folders.ShouldBe(baseline.Folders); sample.Conditions.ShouldBe(baseline.Conditions); - sample.Descriptors.ShouldBeLessThanOrEqualTo(baseline.Descriptors + 32); - sample.ManagedBytes.ShouldBeLessThanOrEqualTo(baseline.ManagedBytes + (64L << 20)); - sample.RssBytes.ShouldBeLessThanOrEqualTo(baseline.RssBytes + (128L << 20)); + if (firstRejectedBudget is null) + { + try + { + sample.Descriptors.ShouldBeLessThanOrEqualTo(baseline.Descriptors + 32); + sample.ManagedBytes.ShouldBeLessThanOrEqualTo(baseline.ManagedBytes + (64L << 20)); + sample.RssBytes.ShouldBeLessThanOrEqualTo(baseline.RssBytes + (128L << 20)); + } + catch (ShouldAssertException error) when (mode == "profile") + { + // Investigative sampling may continue after rejection, but can never turn it into a pass. + firstRejectedBudget = ExceptionDispatchInfo.Capture(error); + await LogAsync(evidence, new { kind = "budget-rejected", cycle = cycles, + seconds = watch.Elapsed.TotalSeconds, resources = sample, baseline }); + } + } retained.Enqueue(sample); if (retained.Count > 24) retained.Dequeue(); if (retained.Count == 24) @@ -136,6 +151,9 @@ public sealed class Sdk2LifecycleSoakTests await LogAsync(evidence, new { kind = "restart", cycles, restarts, seconds = restart.Elapsed.TotalSeconds }); } } + // Preserve the first assertion and its original stack even if later RSS samples decrease. + // This happens before any completion record; finally still releases the owned rig and client. + firstRejectedBudget?.Throw(); cycles.ShouldBeGreaterThanOrEqualTo(4, "diagnostic mode must exercise at least one actual restart"); restarts.ShouldBeGreaterThan(0); watch.Elapsed.TotalSeconds.ShouldBeGreaterThanOrEqualTo(seconds);