Files
lmxopcua/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests/AbCipUdtMemberLayoutTests.cs
T
Joseph Doherty a914b73d57 review(Driver.AbCip): fix declared UDT array members read as scalar (Medium)
Re-review at 7286d320. AbCip-016 (Medium): two cooperating defects made a declared array
member (e.g. REAL[4]) read one scalar/null — fan-out dropped ElementCount/IsArray, and
UdtMemberLayout.TryBuild ignored array members (mis-placing later members). Fix: thread
array shape through fan-out + opt whole-UDT grouping out when any member is an array + TDD.
AbCip-017 (severity-read StatusCode, Low) deferred.
2026-06-19 11:34:34 -04:00

103 lines
4.0 KiB
C#

using Shouldly;
using Xunit;
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests;
[Trait("Category", "Unit")]
public sealed class AbCipUdtMemberLayoutTests
{
/// <summary>Verifies that packed atomic types get natural alignment offsets.</summary>
[Fact]
public void Packed_Atomics_Get_Natural_Alignment_Offsets()
{
// DInt (4 align) + Real (4) + Int (2) + LInt (8 — forces 2-byte pad before it)
var members = new[]
{
new AbCipStructureMember("A", AbCipDataType.DInt),
new AbCipStructureMember("B", AbCipDataType.Real),
new AbCipStructureMember("C", AbCipDataType.Int),
new AbCipStructureMember("D", AbCipDataType.LInt),
};
var offsets = AbCipUdtMemberLayout.TryBuild(members);
offsets.ShouldNotBeNull();
offsets!["A"].ShouldBe(0);
offsets["B"].ShouldBe(4);
offsets["C"].ShouldBe(8);
// cursor at 10 after Int; LInt needs 8-byte alignment → pad to 16
offsets["D"].ShouldBe(16);
}
/// <summary>Verifies that signed integer types are packed without padding.</summary>
[Fact]
public void SInt_Packed_Without_Padding()
{
var members = new[]
{
new AbCipStructureMember("X", AbCipDataType.SInt),
new AbCipStructureMember("Y", AbCipDataType.SInt),
new AbCipStructureMember("Z", AbCipDataType.SInt),
};
var offsets = AbCipUdtMemberLayout.TryBuild(members);
offsets!["X"].ShouldBe(0);
offsets["Y"].ShouldBe(1);
offsets["Z"].ShouldBe(2);
}
/// <summary>Verifies that layout returns null when a member is a Bool type.</summary>
[Fact]
public void Returns_Null_When_Member_Is_Bool()
{
// BOOL storage in Logix UDTs is packed into a hidden host byte; declaration-only
// layout can't place it. Grouping opts out; per-tag read path handles the member.
var members = new[]
{
new AbCipStructureMember("A", AbCipDataType.DInt),
new AbCipStructureMember("Flag", AbCipDataType.Bool),
};
AbCipUdtMemberLayout.TryBuild(members).ShouldBeNull();
}
/// <summary>Verifies that layout returns null when a member is a String or Structure type.</summary>
[Fact]
public void Returns_Null_When_Member_Is_String_Or_Structure()
{
AbCipUdtMemberLayout.TryBuild(
new[] { new AbCipStructureMember("Name", AbCipDataType.String) }).ShouldBeNull();
AbCipUdtMemberLayout.TryBuild(
new[] { new AbCipStructureMember("Nested", AbCipDataType.Structure) }).ShouldBeNull();
}
/// <summary>Verifies that layout returns null when the member list is empty.</summary>
[Fact]
public void Returns_Null_On_Empty_Members()
{
AbCipUdtMemberLayout.TryBuild(Array.Empty<AbCipStructureMember>()).ShouldBeNull();
}
/// <summary>
/// Driver.AbCip-016 — declaration-only layout returns null when any member is a 1-D array.
/// The whole-UDT grouped read path decodes one scalar per member at its offset
/// (<c>DecodeValueAt</c>) and cannot return an array, and the scalar-size cursor advance
/// would mis-place every member after the array. Opting the whole group out sends array
/// members through the per-tag read path, which reads them as typed arrays.
/// </summary>
[Fact]
public void Returns_Null_When_A_Member_Is_An_Array()
{
// Explicit IsArray flag (even a 1-element array).
AbCipUdtMemberLayout.TryBuild(new[]
{
new AbCipStructureMember("A", AbCipDataType.DInt),
new AbCipStructureMember("Buf", AbCipDataType.Real, IsArray: true, ElementCount: 1),
}).ShouldBeNull();
// Legacy ElementCount > 1 with the flag unset.
AbCipUdtMemberLayout.TryBuild(new[]
{
new AbCipStructureMember("A", AbCipDataType.DInt),
new AbCipStructureMember("Setpoints", AbCipDataType.Real, ElementCount: 4),
}).ShouldBeNull();
}
}