From 1502e3f51d1e3953b1d559223dc4ab62a2f77bde Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sun, 14 Jun 2026 01:41:21 -0400 Subject: [PATCH] =?UTF-8?q?test(alarms):=20alarmmgr=20observation=20probe?= =?UTF-8?q?=20=E2=80=94=20log=20wnwrap=20transitions=20over=2035s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Probes/AlarmmgrObserveProbe.cs | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 src/ZB.MOM.WW.MxGateway.Worker.Tests/Probes/AlarmmgrObserveProbe.cs diff --git a/src/ZB.MOM.WW.MxGateway.Worker.Tests/Probes/AlarmmgrObserveProbe.cs b/src/ZB.MOM.WW.MxGateway.Worker.Tests/Probes/AlarmmgrObserveProbe.cs new file mode 100644 index 0000000..ca1b9b7 --- /dev/null +++ b/src/ZB.MOM.WW.MxGateway.Worker.Tests/Probes/AlarmmgrObserveProbe.cs @@ -0,0 +1,131 @@ +// AlarmmgrObserveProbe.cs +// +// Independent oracle for whether the dev-rig alarms actually toggle. Subscribes +// via the native alarmmgr route (WnWrapAlarmConsumer / wwAlarmConsumerClass), +// polls for ~35s, and logs every alarm transition it observes (Raise / Clear / +// Ack). If TestMachine_001.TestAlarm001 is being flipped on a ~10s cadence this +// will show alternating Raise/Clear; if nothing toggles it shows only the +// initial active snapshot. Runs only with MXGATEWAY_RUN_LIVE_MXACCESS_TESTS=1. +// +// net48/x86: no init-only props, records, or index/range operators. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using WNWRAPCONSUMERLib; +using ZB.MOM.WW.MxGateway.Worker.MxAccess; +using ZB.MOM.WW.MxGateway.Worker.Tests.TestSupport; +using Xunit.Abstractions; + +namespace ZB.MOM.WW.MxGateway.Worker.Tests.Probes; + +/// +/// Observation probe that reports alarm transitions seen through the native +/// alarmmgr (wnwrap) consumer, to verify whether the rig's alarms toggle. +/// +public sealed class AlarmmgrObserveProbe +{ + private static readonly string SubscriptionExpression = + string.Format(@"\\{0}\Galaxy!DEV", Environment.MachineName); + + private readonly ITestOutputHelper output; + private readonly ConcurrentQueue log = new ConcurrentQueue(); + private readonly Stopwatch elapsed = Stopwatch.StartNew(); + + /// Initializes a new instance of the AlarmmgrObserveProbe class. + /// Test output helper for logging. + public AlarmmgrObserveProbe(ITestOutputHelper output) + { + this.output = output; + } + + /// + /// Subscribes via the alarmmgr consumer and logs every transition seen + /// over a ~35s window plus the final active-alarm snapshot. + /// + [LiveMxAccessFact] + public void Observe_AlarmTransitions_ViaAlarmmgr() + { + Exception? threadException = null; + ManualResetEventSlim done = new ManualResetEventSlim(false); + Thread thread = new Thread(() => + { + try { Run(); } + catch (Exception ex) { threadException = ex; } + finally { done.Set(); } + }); + thread.IsBackground = false; + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + done.Wait(); + thread.Join(); + + List lines = new List(); + string? line; + while (log.TryDequeue(out line)) + { + output.WriteLine(line); + lines.Add(line); + } + + string logPath = Environment.GetEnvironmentVariable("PROBE_LOG") + ?? System.IO.Path.Combine(System.IO.Path.GetTempPath(), "alarmmgr-observe.log"); + try { System.IO.File.WriteAllLines(logPath, lines); } + catch (Exception writeEx) { output.WriteLine("log write failed: " + writeEx.Message); } + + if (threadException != null) + { + throw threadException; + } + } + + private void Run() + { + Log("=== alarmmgr (wnwrap) observation ==="); + Log("Subscription: " + SubscriptionExpression); + + using WnWrapAlarmConsumer consumer = new WnWrapAlarmConsumer( + new wwAlarmConsumerClass(), maxAlarmsPerFetch: 1024); + + int transitionCount = 0; + consumer.AlarmTransitionEmitted += (_, e) => + { + Interlocked.Increment(ref transitionCount); + Log(string.Format( + "transition: Tag='{0}' State={1} Prev={2} Group='{3}' Provider='{4}'", + e.Record.TagName, e.Record.State, e.PreviousState, + e.Record.Group, e.Record.ProviderName)); + }; + + consumer.Subscribe(SubscriptionExpression); + Log("Subscribed. Polling every 1s for 35s ..."); + + DateTime deadline = DateTime.UtcNow + TimeSpan.FromSeconds(35); + int polls = 0; + while (DateTime.UtcNow < deadline) + { + consumer.PollOnce(); + polls++; + Thread.Sleep(1000); + } + + Log(string.Format("Polling done. polls={0} transitions={1}", polls, transitionCount)); + + IReadOnlyList snapshot = consumer.SnapshotActiveAlarms(); + Log(string.Format("Final active-alarm snapshot count={0}:", snapshot.Count)); + foreach (MxAlarmSnapshotRecord r in snapshot) + { + Log(string.Format(" active: Tag='{0}' State={1} Group='{2}'", + r.TagName, r.State, r.Group)); + } + + Log("=== observation complete ==="); + } + + private void Log(string line) + { + log.Enqueue(string.Format("[t={0:F1}s] {1}", elapsed.Elapsed.TotalSeconds, line)); + } +}