using Microsoft.Extensions.Logging.Abstractions; using ZB.MOM.WW.ScadaBridge.HealthMonitoring; using ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts; namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests.Scripts; /// /// SiteRuntime-S2/UA5: the hosted reporter must lift the process-wide /// gauges onto the site health report via /// . Uses the real collector (NSubstitute is not /// referenced by this test project) and drives the shared scheduler busy so the /// pushed value is distinguishable from the idle default. /// public class ScriptSchedulerStatsReporterTests { [Fact] public async Task Reporter_PushesSchedulerStatsToCollector() { var options = new SiteRuntimeOptions { ScriptExecutionThreadCount = 1 }; var collector = new SiteHealthCollector(); var scheduler = ScriptExecutionScheduler.Shared(options); using var gate = new ManualResetEventSlim(false); // Occupy a worker thread so BusyThreadCount is a non-zero, distinguishable value. var blocking = Task.Factory.StartNew(() => gate.Wait(), CancellationToken.None, TaskCreationOptions.None, scheduler); await WaitUntilAsync(() => scheduler.BusyThreadCount >= 1); using var reporter = new ScriptSchedulerStatsReporter( collector, options, NullLogger.Instance, pollInterval: TimeSpan.FromMilliseconds(50)); await reporter.StartAsync(CancellationToken.None); try { await WaitUntilAsync(() => collector.CollectReport("site-1").ScriptBusyThreads >= 1); var report = collector.CollectReport("site-1"); Assert.True(report.ScriptBusyThreads >= 1); Assert.NotNull(report.ScriptOldestBusyAgeSeconds); // a script is in flight } finally { gate.Set(); await blocking; await reporter.StopAsync(CancellationToken.None); } } private static async Task WaitUntilAsync(Func condition) { for (var i = 0; i < 100 && !condition(); i++) await Task.Delay(50); Assert.True(condition(), "condition not met within timeout"); } }