111 lines
4.0 KiB
C#
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 });
|
|
}
|
|
}
|