using System.Diagnostics; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Driver.AbCip; namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests; /// /// PR abcip-3.2 — wall-clock comparison of Symbolic vs Logical reads on a running /// ab_server (or a real ControlLogix). Skipped when ab_server isn't /// reachable, same gating rule as . /// /// /// This is a scaffold: it builds + runs against the existing test fixture, /// but the libplctag .NET 1.5.x wrapper does not yet expose a public knob for instance-ID /// addressing (see docs/drivers/AbCip-Performance.md §"Addressing mode"). On a live /// fixture the two paths therefore measure the same wire behaviour today; the assertion /// just sanity-checks that both modes complete + produce well-formed snapshots, with timing /// emitted to the test output for inspection. When the wrapper exposes the attribute /// publicly (or libplctag native gains hot-update of cip_addr) the assertion can be /// tightened to require Logical < Symbolic on N-tag scans. /// /// Marked [Trait("Category", "Bench")] so a future --filter rule can /// opt out of bench tests in CI runs that only want the smoke set. /// [Trait("Category", "Bench")] [Trait("Requires", "AbServer")] public sealed class AbCipAddressingModeBenchTests { [AbServerFact] public async Task Symbolic_and_Logical_modes_both_read_seeded_DInt_and_emit_timing() { var profile = KnownProfiles.ControlLogix; var fixture = new AbServerFixture(profile); await fixture.InitializeAsync(); try { var deviceUri = $"ab://127.0.0.1:{fixture.Port}/1,0"; var symElapsed = await ReadOnceAsync(deviceUri, profile.Family, AddressingMode.Symbolic); var logElapsed = await ReadOnceAsync(deviceUri, profile.Family, AddressingMode.Logical); // Wall-clock timing is captured for human inspection; the assertion just confirms // both completed. The actual symbolic-vs-logical comparison is qualitative until // the libplctag wrapper exposes logical-segment addressing publicly — see class doc. Console.WriteLine($"Symbolic read elapsed: {symElapsed.TotalMilliseconds:F2} ms"); Console.WriteLine($"Logical read elapsed: {logElapsed.TotalMilliseconds:F2} ms"); symElapsed.ShouldBeGreaterThan(TimeSpan.Zero); logElapsed.ShouldBeGreaterThan(TimeSpan.Zero); } finally { await fixture.DisposeAsync(); } } private static async Task ReadOnceAsync(string deviceUri, AbCipPlcFamily family, AddressingMode mode) { var drv = new AbCipDriver(new AbCipDriverOptions { Devices = [new AbCipDeviceOptions(deviceUri, family, AddressingMode: mode)], Tags = [new AbCipTagDefinition("Counter", deviceUri, "TestDINT", AbCipDataType.DInt)], Timeout = TimeSpan.FromSeconds(5), }, $"drv-bench-{mode}"); await drv.InitializeAsync("{}", CancellationToken.None); var sw = Stopwatch.StartNew(); var snapshots = await drv.ReadAsync(["Counter"], CancellationToken.None); sw.Stop(); snapshots.Single().StatusCode.ShouldBe(AbCipStatusMapper.Good); await drv.ShutdownAsync(CancellationToken.None); return sw.Elapsed; } }