test(alarms): drivable-alarm discovery scan across candidate alarm tags

This commit is contained in:
Joseph Doherty
2026-06-13 11:46:35 -04:00
parent 0b3b938af5
commit 1681c933a0
@@ -335,6 +335,97 @@ public sealed class AlarmSubtagLiveSmokeTests
}
}
/// <summary>
/// Discovery run: scans candidate alarm attributes for one whose value
/// actually responds to an external write (so it can drive a full
/// Raise/Clear lifecycle). Logs each candidate's before/after value and
/// InAlarm; does not assert. Runs only with
/// <c>MXGATEWAY_RUN_LIVE_MXACCESS_TESTS=1</c>. Side effect: flips any
/// writable candidate.
/// </summary>
[LiveMxAccessFact]
public void LiveProbe_FindDrivableAlarm()
{
Exception? threadException = null;
ManualResetEventSlim done = new ManualResetEventSlim(false);
Thread thread = new Thread(() =>
{
try { RunFindDrivable(); }
catch (Exception ex) { threadException = ex; }
finally { done.Set(); }
});
thread.IsBackground = false;
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
done.Wait();
thread.Join();
List<string> lines = new List<string>();
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(), "alarm-find-drivable.log");
try { System.IO.File.WriteAllLines(logPath, lines); } catch { /* best effort */ }
if (threadException != null) { throw threadException; }
}
private void RunFindDrivable()
{
string[] candidates = new string[]
{
"TestMachine_001.TestAlarm002",
"TestMachine_001.TestAlarm003",
"TestMachine_002.TestAlarm001",
"TestMachine_003.TestAlarm001",
};
Log("=== Find a drivable alarm ===");
MxAccessComObjectFactory factory = new MxAccessComObjectFactory();
using LmxSubtagAlarmSource source = new LmxSubtagAlarmSource(factory, clientName: null);
Dictionary<string, object?> latest =
new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase);
source.ValueChanged += (_, c) =>
{
latest[c.ItemAddress] = c.Value;
Log(string.Format("raw: {0} = {1}", c.ItemAddress, c.Value ?? "<null>"));
};
List<string> toAdvise = new List<string>();
foreach (string cand in candidates)
{
toAdvise.Add(cand);
toAdvise.Add(cand + ".InAlarm");
}
source.Advise(toAdvise);
Log("Advised candidates. Pumping ...");
PumpFor(TimeSpan.FromSeconds(7));
foreach (string cand in candidates)
{
object? before;
latest.TryGetValue(cand, out before);
bool beforeBool = before is bool bb && bb;
Log(string.Format("[{0}] before: value={1} InAlarm={2}",
cand, before ?? "<none>", latest.ContainsKey(cand + ".InAlarm") ? latest[cand + ".InAlarm"] : "<none>"));
Log(string.Format("[{0}] writing {1} ...", cand, !beforeBool));
try { source.Write(cand, !beforeBool); }
catch (Exception ex) { Log(string.Format("[{0}] write threw: {1}", cand, ex.Message)); continue; }
PumpFor(TimeSpan.FromSeconds(5));
object? after;
latest.TryGetValue(cand, out after);
bool changed = !Equals(before, after);
Log(string.Format("[{0}] after: value={1} InAlarm={2} -> {3}",
cand, after ?? "<none>",
latest.ContainsKey(cand + ".InAlarm") ? latest[cand + ".InAlarm"] : "<none>",
changed ? "DRIVABLE (value changed)" : "not drivable (value held)"));
}
Log("=== Find-drivable scan complete ===");
}
private void RunSelfDriven()
{
// The discrete alarm's source attribute. Writing its boolean value