Files
lmxopcua/tests/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests/AbCipArrayReadPlannerTests.cs
2026-04-25 13:03:45 -04:00

111 lines
4.0 KiB
C#

using Shouldly;
using Xunit;
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests;
[Trait("Category", "Unit")]
public sealed class AbCipArrayReadPlannerTests
{
private const string Device = "ab://10.0.0.5/1,0";
private static AbCipTagCreateParams BaseParams(string tagName) => new(
Gateway: "10.0.0.5",
Port: 44818,
CipPath: "1,0",
LibplctagPlcAttribute: "controllogix",
TagName: tagName,
Timeout: TimeSpan.FromSeconds(5));
[Fact]
public void TryBuild_emits_single_tag_create_with_element_count()
{
var def = new AbCipTagDefinition("DataSlice", Device, "Data[0..15]", AbCipDataType.DInt);
var parsed = AbCipTagPath.TryParse(def.TagPath)!;
var plan = AbCipArrayReadPlanner.TryBuild(def, parsed, BaseParams("Data[0..15]"));
plan.ShouldNotBeNull();
plan.ElementType.ShouldBe(AbCipDataType.DInt);
plan.Stride.ShouldBe(4);
plan.Slice.Count.ShouldBe(16);
plan.CreateParams.ElementCount.ShouldBe(16);
// Anchored at the slice start; libplctag reads N consecutive elements from there.
plan.CreateParams.TagName.ShouldBe("Data[0]");
}
[Fact]
public void TryBuild_returns_null_when_path_has_no_slice()
{
var def = new AbCipTagDefinition("Plain", Device, "Data[3]", AbCipDataType.DInt);
var parsed = AbCipTagPath.TryParse(def.TagPath)!;
AbCipArrayReadPlanner.TryBuild(def, parsed, BaseParams("Data[3]")).ShouldBeNull();
}
[Theory]
[InlineData(AbCipDataType.Bool)]
[InlineData(AbCipDataType.String)]
[InlineData(AbCipDataType.Structure)]
public void TryBuild_returns_null_for_unsupported_element_types(AbCipDataType type)
{
var def = new AbCipTagDefinition("Slice", Device, "Data[0..3]", type);
var parsed = AbCipTagPath.TryParse(def.TagPath)!;
AbCipArrayReadPlanner.TryBuild(def, parsed, BaseParams("Data[0..3]")).ShouldBeNull();
}
[Theory]
[InlineData(AbCipDataType.SInt, 1)]
[InlineData(AbCipDataType.Int, 2)]
[InlineData(AbCipDataType.DInt, 4)]
[InlineData(AbCipDataType.Real, 4)]
[InlineData(AbCipDataType.LInt, 8)]
[InlineData(AbCipDataType.LReal, 8)]
public void TryBuild_uses_natural_stride_per_element_type(AbCipDataType type, int expectedStride)
{
var def = new AbCipTagDefinition("Slice", Device, "Data[0..3]", type);
var parsed = AbCipTagPath.TryParse(def.TagPath)!;
var plan = AbCipArrayReadPlanner.TryBuild(def, parsed, BaseParams("Data[0..3]"))!;
plan.Stride.ShouldBe(expectedStride);
}
[Fact]
public void Decode_walks_buffer_at_element_stride()
{
var def = new AbCipTagDefinition("DataSlice", Device, "Data[0..3]", AbCipDataType.DInt);
var parsed = AbCipTagPath.TryParse(def.TagPath)!;
var plan = AbCipArrayReadPlanner.TryBuild(def, parsed, BaseParams("Data[0..3]"))!;
var fake = new FakeAbCipTag(plan.CreateParams);
// Stride == 4 for DInt, so offsets 0/4/8/12 hold the four element values.
fake.ValuesByOffset[0] = 100;
fake.ValuesByOffset[4] = 200;
fake.ValuesByOffset[8] = 300;
fake.ValuesByOffset[12] = 400;
var decoded = AbCipArrayReadPlanner.Decode(plan, fake);
decoded.Length.ShouldBe(4);
decoded.ShouldBe(new object?[] { 100, 200, 300, 400 });
}
[Fact]
public void Decode_preserves_slice_count_for_real_arrays()
{
var def = new AbCipTagDefinition("FloatSlice", Device, "Floats[2..5]", AbCipDataType.Real);
var parsed = AbCipTagPath.TryParse(def.TagPath)!;
var plan = AbCipArrayReadPlanner.TryBuild(def, parsed, BaseParams("Floats[2]"))!;
var fake = new FakeAbCipTag(plan.CreateParams);
fake.ValuesByOffset[0] = 1.5f;
fake.ValuesByOffset[4] = 2.5f;
fake.ValuesByOffset[8] = 3.5f;
fake.ValuesByOffset[12] = 4.5f;
var decoded = AbCipArrayReadPlanner.Decode(plan, fake);
decoded.ShouldBe(new object?[] { 1.5f, 2.5f, 3.5f, 4.5f });
}
}