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;
}
}