test(alarms): live runtime-path resolution probe (LiveMxAccessFact) for alarm subtags
This commit is contained in:
@@ -34,6 +34,7 @@ using System.Runtime.InteropServices;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using ZB.MOM.WW.MxGateway.Contracts.Proto;
|
using ZB.MOM.WW.MxGateway.Contracts.Proto;
|
||||||
using ZB.MOM.WW.MxGateway.Worker.MxAccess;
|
using ZB.MOM.WW.MxGateway.Worker.MxAccess;
|
||||||
|
using ZB.MOM.WW.MxGateway.Worker.Tests.TestSupport;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace ZB.MOM.WW.MxGateway.Worker.Tests.Probes;
|
namespace ZB.MOM.WW.MxGateway.Worker.Tests.Probes;
|
||||||
@@ -146,6 +147,138 @@ public sealed class AlarmSubtagLiveSmokeTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runtime-path resolution probe. Unlike the full lifecycle test this
|
||||||
|
/// does NOT require an active alarm: it advises the four subtags and
|
||||||
|
/// observes the initial values MXAccess delivers on advise, then writes
|
||||||
|
/// the AckMsg subtag. It confirms the runtime item-reference path
|
||||||
|
/// (<c><Object>.<AlarmAttr>.<field></c>) resolves with
|
||||||
|
/// no intermediate alarm-condition segment — the last open item from the
|
||||||
|
/// design. Runs only when <c>MXGATEWAY_RUN_LIVE_MXACCESS_TESTS=1</c>.
|
||||||
|
/// Reports "inconclusive" (without failing) when no values are delivered,
|
||||||
|
/// e.g. the Galaxy engine for the test object is not running.
|
||||||
|
/// </summary>
|
||||||
|
[LiveMxAccessFact]
|
||||||
|
public void LiveProbe_AlarmSubtagsResolve_AndAckMsgWriteSucceeds()
|
||||||
|
{
|
||||||
|
Exception? threadException = null;
|
||||||
|
ManualResetEventSlim done = new ManualResetEventSlim(false);
|
||||||
|
Thread thread = new Thread(() =>
|
||||||
|
{
|
||||||
|
try { RunProbe(); }
|
||||||
|
catch (Exception ex) { threadException = ex; }
|
||||||
|
finally { done.Set(); }
|
||||||
|
});
|
||||||
|
thread.IsBackground = false;
|
||||||
|
thread.SetApartmentState(ApartmentState.STA);
|
||||||
|
thread.Start();
|
||||||
|
done.Wait();
|
||||||
|
thread.Join();
|
||||||
|
|
||||||
|
output.WriteLine(string.Format("Captured {0} log line(s):", log.Count));
|
||||||
|
string? line;
|
||||||
|
while (log.TryDequeue(out line))
|
||||||
|
{
|
||||||
|
output.WriteLine(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (threadException != null)
|
||||||
|
{
|
||||||
|
throw threadException;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RunProbe()
|
||||||
|
{
|
||||||
|
Log("=== Subtag runtime-path resolution probe ===");
|
||||||
|
Log("AlarmFullReference: " + AlarmFullReference);
|
||||||
|
Log("Subtag addresses under test:");
|
||||||
|
Log(" active = " + PlaceholderActiveSubtag);
|
||||||
|
Log(" acked = " + PlaceholderAckedSubtag);
|
||||||
|
Log(" ackMsg = " + PlaceholderAckCommentSubtag);
|
||||||
|
Log(" priority= " + PlaceholderPrioritySubtag);
|
||||||
|
|
||||||
|
AlarmSubtagTarget target = new AlarmSubtagTarget
|
||||||
|
{
|
||||||
|
AlarmFullReference = AlarmFullReference,
|
||||||
|
SourceObjectReference = AlarmTagName,
|
||||||
|
ActiveSubtag = PlaceholderActiveSubtag,
|
||||||
|
AckedSubtag = PlaceholderAckedSubtag,
|
||||||
|
AckCommentSubtag = PlaceholderAckCommentSubtag,
|
||||||
|
PrioritySubtag = PlaceholderPrioritySubtag,
|
||||||
|
};
|
||||||
|
List<AlarmSubtagTarget> watchList = new List<AlarmSubtagTarget> { target };
|
||||||
|
|
||||||
|
MxAccessComObjectFactory factory = new MxAccessComObjectFactory();
|
||||||
|
LmxSubtagAlarmSource source = new LmxSubtagAlarmSource(factory, clientName: null);
|
||||||
|
using SubtagAlarmConsumer consumer = new SubtagAlarmConsumer(source, watchList);
|
||||||
|
|
||||||
|
// Hook the RAW source so every advised subtag's value-change is seen,
|
||||||
|
// including the initial value MXAccess delivers on advise (the state
|
||||||
|
// machine itself only emits on active/acked transitions).
|
||||||
|
Dictionary<string, string> lastValues =
|
||||||
|
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
source.ValueChanged += (_, c) =>
|
||||||
|
{
|
||||||
|
string v = c.Value == null
|
||||||
|
? "<null>"
|
||||||
|
: string.Format("{0} ({1})", c.Value, c.Value.GetType().Name);
|
||||||
|
lastValues[c.ItemAddress] = v;
|
||||||
|
Log(string.Format("ValueChanged: {0} = {1}", c.ItemAddress, v));
|
||||||
|
};
|
||||||
|
|
||||||
|
Log("Subscribe (advise InAlarm/Acked/Priority) ...");
|
||||||
|
consumer.Subscribe(string.Format(@"\\{0}\Galaxy!{1}", Environment.MachineName, Group));
|
||||||
|
Log("Subscribe returned OK.");
|
||||||
|
|
||||||
|
// Pump the STA for ~6s to receive each advised subtag's initial value.
|
||||||
|
DateTime deadline = DateTime.UtcNow + TimeSpan.FromSeconds(6);
|
||||||
|
while (DateTime.UtcNow < deadline)
|
||||||
|
{
|
||||||
|
PumpMessages();
|
||||||
|
Thread.Sleep(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log(string.Format("Distinct subtags that delivered a value: {0}", lastValues.Count));
|
||||||
|
bool inAlarmResolved = lastValues.ContainsKey(PlaceholderActiveSubtag);
|
||||||
|
Log("InAlarm subtag delivered a value: " + inAlarmResolved);
|
||||||
|
|
||||||
|
// Decisive write: AckMsg. Returns 0 only if the address resolves and is writable.
|
||||||
|
Log("AcknowledgeByName (writes AckMsg subtag) ...");
|
||||||
|
int rc = consumer.AcknowledgeByName(
|
||||||
|
alarmName: AlarmTagName,
|
||||||
|
providerName: Provider,
|
||||||
|
groupName: Group,
|
||||||
|
ackComment: "subtag-resolution-probe",
|
||||||
|
ackOperatorName: Environment.UserName,
|
||||||
|
ackOperatorNode: Environment.MachineName,
|
||||||
|
ackOperatorDomain: Environment.UserDomainName ?? string.Empty,
|
||||||
|
ackOperatorFullName: Environment.UserName);
|
||||||
|
Log(string.Format("AcknowledgeByName(AckMsg write) -> rc={0}", rc));
|
||||||
|
|
||||||
|
DateTime settle = DateTime.UtcNow + TimeSpan.FromSeconds(2);
|
||||||
|
while (DateTime.UtcNow < settle)
|
||||||
|
{
|
||||||
|
PumpMessages();
|
||||||
|
Thread.Sleep(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastValues.Count == 0)
|
||||||
|
{
|
||||||
|
Log("INCONCLUSIVE: no subtag values delivered within the window. The Galaxy "
|
||||||
|
+ "engine hosting TestMachine_001 may be stopped, or the runtime path needs "
|
||||||
|
+ "an intermediate segment. Re-run with the engine running. (Field names "
|
||||||
|
+ "remain confirmed from the ZB AlarmExtension model.)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Live data arrived — assert the in-alarm subtag resolved and the ack write succeeded.
|
||||||
|
Assert.True(inAlarmResolved,
|
||||||
|
"InAlarm subtag delivered no value while other subtags did — the runtime path for InAlarm may differ.");
|
||||||
|
Assert.Equal(0, rc);
|
||||||
|
Log("CONFIRMED: runtime subtag path resolves and the AckMsg write succeeded.");
|
||||||
|
}
|
||||||
|
|
||||||
private void RunSmoke()
|
private void RunSmoke()
|
||||||
{
|
{
|
||||||
Log(string.Format("AlarmFullReference: {0}", AlarmFullReference));
|
Log(string.Format("AlarmFullReference: {0}", AlarmFullReference));
|
||||||
|
|||||||
Reference in New Issue
Block a user