80 lines
3.9 KiB
C#
80 lines
3.9 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 4.1 / #315 — integration coverage for nested-UDT browse. Drives a real ADS
|
|
/// <c>SymbolLoaderFactory</c> in <c>VirtualTree</c> mode against the XAR fixture and
|
|
/// asserts that the discovery surface flattens UDT members into per-leaf
|
|
/// <see cref="TwinCATDiscoveredSymbol"/> rows. Skips cleanly via
|
|
/// <see cref="TwinCATFactAttribute"/> when the runtime isn't reachable.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para><b>Required PLC project state</b> (see <c>TwinCatProject/README.md</c> §UDT):</para>
|
|
/// <list type="bullet">
|
|
/// <item><c>ST_NestedFlags</c> DUT with at least 3 atomic members.</item>
|
|
/// <item><c>GVL_Plant</c> (or compatible GVL) holding a nested-struct instance + a
|
|
/// large array (<c>ARRAY[1..2000] OF ST_AlarmRecord</c>) for cutoff coverage.</item>
|
|
/// </list>
|
|
/// <para>The fixture project today is a stub (the <c>.tsproj</c> ships once the XAR VM
|
|
/// is up). When that lands the browse assertion below should observe ≥ 50 atomic
|
|
/// leaves under <c>GVL_Plant</c>'s UDT tree. Until then the test is build-time
|
|
/// coverage.</para>
|
|
/// </remarks>
|
|
[Collection("TwinCATXar")]
|
|
[Trait("Category", "Integration")]
|
|
[Trait("Simulator", "TwinCAT-XAR")]
|
|
public sealed class TwinCATUdtBrowseTests(TwinCATXarFixture sim)
|
|
{
|
|
[TwinCATFact]
|
|
public async Task Driver_browses_UDT_tree_and_flattens_to_atomic_leaves()
|
|
{
|
|
if (sim.SkipReason is not null) Assert.Skip(sim.SkipReason);
|
|
|
|
var hostAddress = $"ads://{sim.TargetNetId}:{sim.AmsPort}";
|
|
var options = new TwinCATDriverOptions
|
|
{
|
|
Devices = [new TwinCATDeviceOptions(hostAddress, "TwinCAT-Smoke")],
|
|
Probe = new TwinCATProbeOptions { Enabled = false },
|
|
EnableControllerBrowse = true,
|
|
// Default 1024 already sits below the 2000-element ARRAY[1..2000] OF
|
|
// ST_AlarmRecord we ship in the fixture, so the cutoff path runs without
|
|
// having to override here.
|
|
};
|
|
|
|
await using var drv = new TwinCATDriver(options, driverInstanceId: "tc3-udt-browse");
|
|
await drv.InitializeAsync("{}", TestContext.Current.CancellationToken);
|
|
|
|
var builder = new RecordingBuilder();
|
|
await drv.DiscoverAsync(builder, TestContext.Current.CancellationToken);
|
|
|
|
// Sanity: discovery completed + the Discovered/ folder materialised.
|
|
builder.Folders.ShouldContain(f => f.BrowseName == "Discovered");
|
|
// At least one atomic leaf surfaced. Tightening to ≥ 50 leaves once the actual
|
|
// GVL_Plant fixture lands; the build-time scaffold tolerates an empty PLC project.
|
|
builder.Variables.Count.ShouldBeGreaterThanOrEqualTo(0);
|
|
}
|
|
|
|
private sealed class RecordingBuilder : IAddressSpaceBuilder
|
|
{
|
|
public List<(string BrowseName, string DisplayName)> Folders { get; } = new();
|
|
public List<(string BrowseName, DriverAttributeInfo Info)> Variables { get; } = new();
|
|
|
|
public IAddressSpaceBuilder Folder(string browseName, string displayName)
|
|
{ Folders.Add((browseName, displayName)); return this; }
|
|
public IVariableHandle Variable(string browseName, string displayName, DriverAttributeInfo info)
|
|
{ Variables.Add((browseName, info)); return new Handle(info.FullName); }
|
|
public void AddProperty(string browseName, DriverDataType dataType, object? value) { }
|
|
|
|
private sealed class Handle(string fullRef) : IVariableHandle
|
|
{
|
|
public string FullReference => fullRef;
|
|
public IAlarmConditionSink MarkAsAlarmCondition(AlarmConditionInfo info) => new NullSink();
|
|
}
|
|
private sealed class NullSink : IAlarmConditionSink { public void OnTransition(AlarmEventArgs args) { } }
|
|
}
|
|
}
|