@@ -0,0 +1,76 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user