Auto: abcip-1.3 — array-slice read addressing

Closes #227
This commit is contained in:
Joseph Doherty
2026-04-25 13:03:45 -04:00
parent 29edd835a3
commit 767ac4aec5
8 changed files with 480 additions and 6 deletions

View File

@@ -165,6 +165,55 @@ public sealed class AbCipDriverReadTests
p.TagName.ShouldBe("Program:P.Counter");
}
[Fact]
public async Task Slice_tag_reads_one_array_and_decodes_n_elements()
{
// PR abcip-1.3 — `Data[0..3]` slice routes through AbCipArrayReadPlanner: one libplctag
// tag-create at TagName="Data[0]" with ElementCount=4, single PLC read, contiguous
// buffer decoded at element stride into one snapshot whose Value is an object?[].
var (drv, factory) = NewDriver(
new AbCipTagDefinition("DataSlice", "ab://10.0.0.5/1,0", "Data[0..3]", AbCipDataType.DInt));
await drv.InitializeAsync("{}", CancellationToken.None);
factory.Customise = p =>
{
var t = new FakeAbCipTag(p);
t.ValuesByOffset[0] = 10;
t.ValuesByOffset[4] = 20;
t.ValuesByOffset[8] = 30;
t.ValuesByOffset[12] = 40;
return t;
};
var snapshots = await drv.ReadAsync(["DataSlice"], CancellationToken.None);
snapshots.Single().StatusCode.ShouldBe(AbCipStatusMapper.Good);
var values = snapshots.Single().Value.ShouldBeOfType<object?[]>();
values.ShouldBe(new object?[] { 10, 20, 30, 40 });
// Exactly ONE libplctag tag was created — anchored at the slice start with
// ElementCount=4. Without the planner this would have been four scalar reads.
factory.Tags.Count.ShouldBe(1);
factory.Tags.ShouldContainKey("Data[0]");
factory.Tags["Data[0]"].CreationParams.ElementCount.ShouldBe(4);
factory.Tags["Data[0]"].ReadCount.ShouldBe(1);
}
[Fact]
public async Task Slice_tag_with_unsupported_element_type_returns_BadNotSupported()
{
// BOOL slices can't be laid out from the declaration alone (Logix packs BOOLs into a
// hidden host byte). The planner refuses; the driver surfaces BadNotSupported instead
// of attempting a best-effort decode.
var (drv, _) = NewDriver(
new AbCipTagDefinition("BoolSlice", "ab://10.0.0.5/1,0", "Flags[0..7]", AbCipDataType.Bool));
await drv.InitializeAsync("{}", CancellationToken.None);
var snapshots = await drv.ReadAsync(["BoolSlice"], CancellationToken.None);
snapshots.Single().StatusCode.ShouldBe(AbCipStatusMapper.BadNotSupported);
snapshots.Single().Value.ShouldBeNull();
}
[Fact]
public async Task Cancellation_propagates_from_read()
{