using Shouldly; using Xunit; namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests; [Trait("Category", "Unit")] public sealed class AbCipUdtMemberLayoutTests { [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); } [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); } [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(); } [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(); } [Fact] public void Returns_Null_On_Empty_Members() { AbCipUdtMemberLayout.TryBuild(Array.Empty()).ShouldBeNull(); } }