84 lines
3.5 KiB
C#
84 lines
3.5 KiB
C#
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.1 (#313) — per-tag <c>MaxDelay</c> coalescing observed on the wire. Subscribes
|
|
/// to <c>GVL_Fixture.nCounter</c> (the cycle-incrementing fixture counter) with a
|
|
/// 500 ms <c>MaxDelayMs</c>; over a 1 s observation window the runtime should batch
|
|
/// the per-cycle changes into ≤ 3 callbacks rather than one-per-PLC-cycle (~100 with a
|
|
/// 10 ms task) which is what <c>MaxDelay=0</c> would deliver.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>Skipped via <see cref="TwinCATFactAttribute"/> when the XAR VM isn't reachable
|
|
/// / env vars aren't set. Build-only proof in CI; full runtime cover happens on the
|
|
/// XAR-equipped lab box.</para>
|
|
///
|
|
/// <para>The 1 s window + ≤ 3 events tolerance is generous on purpose — the runtime's
|
|
/// coalescer can still fire mid-window when the cycle accumulates a value-change burst,
|
|
/// and the test mostly cares that <i>coalescing happens at all</i> (i.e. it's
|
|
/// dramatically less than the no-coalescing baseline).</para>
|
|
/// </remarks>
|
|
[Collection("TwinCATXar")]
|
|
[Trait("Category", "Integration")]
|
|
[Trait("Simulator", "TwinCAT-XAR")]
|
|
public sealed class TwinCATMaxDelayTests(TwinCATXarFixture sim)
|
|
{
|
|
[TwinCATFact]
|
|
public async Task Driver_coalesces_notifications_at_max_delay()
|
|
{
|
|
if (sim.SkipReason is not null) Assert.Skip(sim.SkipReason);
|
|
|
|
var options = BuildOptions(sim);
|
|
await using var drv = new TwinCATDriver(options, driverInstanceId: "tc3-maxdelay");
|
|
await drv.InitializeAsync("{}", TestContext.Current.CancellationToken);
|
|
|
|
var observed = new List<DateTime>();
|
|
drv.OnDataChange += (_, _) =>
|
|
{
|
|
lock (observed) observed.Add(DateTime.UtcNow);
|
|
};
|
|
|
|
var handle = await drv.SubscribeAsync(
|
|
["Counter"], TimeSpan.FromMilliseconds(50),
|
|
TestContext.Current.CancellationToken);
|
|
|
|
// Observe for 1 s. With MAIN incrementing nCounter every 10 ms PLC cycle, the
|
|
// un-coalesced rate would be ~100 events; with MaxDelay=500 ms we expect ≤ 3
|
|
// (initial fire + at most two coalesce-window flushes).
|
|
await Task.Delay(TimeSpan.FromMilliseconds(1000), TestContext.Current.CancellationToken);
|
|
|
|
await drv.UnsubscribeAsync(handle, TestContext.Current.CancellationToken);
|
|
|
|
int count;
|
|
lock (observed) count = observed.Count;
|
|
count.ShouldBeLessThanOrEqualTo(
|
|
3,
|
|
$"MaxDelayMs=500 must coalesce nCounter changes — observed {count} events in 1 s");
|
|
}
|
|
|
|
private static TwinCATDriverOptions BuildOptions(TwinCATXarFixture sim) => new()
|
|
{
|
|
Devices = [
|
|
new TwinCATDeviceOptions(
|
|
HostAddress: $"ads://{sim.TargetNetId}:{sim.AmsPort}",
|
|
DeviceName: "XAR-VM"),
|
|
],
|
|
Tags = [
|
|
new TwinCATTagDefinition(
|
|
Name: "Counter",
|
|
DeviceHostAddress: $"ads://{sim.TargetNetId}:{sim.AmsPort}",
|
|
SymbolPath: "GVL_Fixture.nCounter",
|
|
DataType: TwinCATDataType.DInt,
|
|
MaxDelayMs: 500),
|
|
],
|
|
UseNativeNotifications = true,
|
|
Timeout = TimeSpan.FromSeconds(5),
|
|
// Disable probe so it doesn't race with the 1 s observation window.
|
|
Probe = new TwinCATProbeOptions { Enabled = false },
|
|
};
|
|
}
|