77 lines
3.5 KiB
C#
77 lines
3.5 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// PR abcip-3.2 — wall-clock comparison of Symbolic vs Logical reads on a running
|
|
/// <c>ab_server</c> (or a real ControlLogix). Skipped when <c>ab_server</c> isn't
|
|
/// reachable, same gating rule as <see cref="AbCipReadSmokeTests"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>This is a <em>scaffold</em>: 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 <c>docs/drivers/AbCip-Performance.md</c> §"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.</para>
|
|
///
|
|
/// <para>Marked <c>[Trait("Category", "Bench")]</c> so a future <c>--filter</c> rule can
|
|
/// opt out of bench tests in CI runs that only want the smoke set.</para>
|
|
/// </remarks>
|
|
[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<TimeSpan> 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;
|
|
}
|
|
}
|