Files
lmxopcua/tests/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.IntegrationTests/TwinCATDiagnosticsIntegrationTests.cs
2026-04-26 01:59:56 -04:00

79 lines
3.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Driver.TwinCAT;
namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.IntegrationTests;
/// <summary>
/// PR 3.2 / issue #314 — wire-level coverage for the cycle-time / jitter / online-change
/// diagnostics surfaced through the probe loop. Skipped via <see cref="TwinCATFactAttribute"/>
/// when the XAR VM isn't reachable. The four well-known system symbols
/// (<c>TwinCAT_SystemInfoVarList._AppInfo.OnlineChangeCnt</c>, <c>_AppInfo.AppName</c>,
/// <c>_TaskInfo[1].CycleTime</c>, <c>_TaskInfo[1].LastExecTime</c>) are always exported by
/// a live TC3 PLC runtime — no extra fixture state required beyond the existing
/// <c>GVL_Fixture</c> / <c>MAIN</c> setup documented in <c>TwinCatProject/README.md</c>.
/// </summary>
[Collection("TwinCATXar")]
[Trait("Category", "Integration")]
[Trait("Simulator", "TwinCAT-XAR")]
public sealed class TwinCATDiagnosticsIntegrationTests(TwinCATXarFixture sim)
{
[TwinCATFact]
public async Task Probe_loop_surfaces_cycle_time_and_online_change_count()
{
if (sim.SkipReason is not null) Assert.Skip(sim.SkipReason);
// Probe interval kept tight (250 ms) so the test doesn't have to wait the default 5 s
// between probe ticks. The four system symbols are scalar UDINTs / a STRING(80), so
// the diagnostics sample completes well within the 250 ms budget on any healthy
// runtime.
var options = new TwinCATDriverOptions
{
Devices = [
new TwinCATDeviceOptions(
HostAddress: $"ads://{sim.TargetNetId}:{sim.AmsPort}",
DeviceName: "XAR-VM"),
],
// No Tags — diagnostics doesn't need any user-declared symbols.
Tags = [],
UseNativeNotifications = false,
Timeout = TimeSpan.FromSeconds(5),
Probe = new TwinCATProbeOptions
{
Enabled = true,
Interval = TimeSpan.FromMilliseconds(250),
},
};
await using var drv = new TwinCATDriver(options, driverInstanceId: "tc3-diag-probe");
await drv.InitializeAsync("{}", TestContext.Current.CancellationToken);
// Wait up to 5 s for the probe loop to fire at least once + populate the snapshot.
// 250 ms cycle × 20 attempts ≈ 5 s budget — generous enough for AMS handshake on a
// freshly-restarted runtime.
var hostAddress = $"ads://{sim.TargetNetId}:{sim.AmsPort}";
TwinCATDeviceDiagnostics? snap = null;
for (var i = 0; i < 20 && snap is null; i++)
{
await Task.Delay(250, TestContext.Current.CancellationToken);
snap = drv.GetDeviceDiagnostics(hostAddress);
}
snap.ShouldNotBeNull(
"probe loop must populate TwinCATDeviceDiagnostics within ~5 s on a reachable XAR runtime");
snap.CycleTimeMs.ShouldBeGreaterThan(0,
"TwinCAT_SystemInfoVarList._TaskInfo[1].CycleTime is non-zero on every running PLC task");
snap.OnlineChangeCnt.ShouldBeGreaterThanOrEqualTo(0u,
"OnlineChangeCnt is a UDINT counter — non-negative by type, present on every TC3 runtime");
// AppName is best-effort but on a project-loaded runtime it should be non-empty.
snap.AppName.ShouldNotBeNull("running PLC project always reports an AppName");
// The cross-driver DriverHealth.Diagnostics dictionary should also reflect the same
// values so the driver-diagnostics RPC has a consistent surface.
var dict = drv.GetHealth().DiagnosticsOrEmpty;
dict["TwinCAT.CycleTimeMs"].ShouldBe(snap.CycleTimeMs);
dict["TwinCAT.OnlineChangeCnt"].ShouldBe(snap.OnlineChangeCnt);
}
}