fix(abcip): explicit IsArray flag so 1-element arrays read as arrays (review I-1)
This commit is contained in:
@@ -181,22 +181,111 @@ public sealed class AbCipArrayTests
|
||||
factory.Tags["Recipe"].CreationParams.ElementCount.ShouldBe(4);
|
||||
}
|
||||
|
||||
/// <summary>The parser threads arrayLength into the transient definition's ElementCount.</summary>
|
||||
/// <summary>The parser threads arrayLength into the transient definition's ElementCount and sets IsArray.</summary>
|
||||
[Fact]
|
||||
public void Parser_threads_arrayLength_into_ElementCount()
|
||||
{
|
||||
var json = """{"tagPath":"Recipe","dataType":"DInt","isArray":true,"arrayLength":8}""";
|
||||
AbCipEquipmentTagParser.TryParse(json, out var def).ShouldBeTrue();
|
||||
def!.ElementCount.ShouldBe(8);
|
||||
def.IsArray.ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>A non-array equipment ref defaults ElementCount to 1 (scalar).</summary>
|
||||
/// <summary>A non-array equipment ref defaults ElementCount to 1 (scalar) and IsArray false.</summary>
|
||||
[Fact]
|
||||
public void Parser_defaults_ElementCount_to_one_when_not_an_array()
|
||||
{
|
||||
var json = """{"tagPath":"Recipe","dataType":"DInt"}""";
|
||||
AbCipEquipmentTagParser.TryParse(json, out var def).ShouldBeTrue();
|
||||
def!.ElementCount.ShouldBe(1);
|
||||
def.IsArray.ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Review finding I-1 — a 1-element array (<c>isArray:true, arrayLength:1</c>) is a valid
|
||||
/// 1-element array, NOT a scalar: the parser sets <see cref="AbCipTagDefinition.IsArray"/>
|
||||
/// true and <see cref="AbCipTagDefinition.ElementCount"/> 1.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Parser_treats_isArray_with_arrayLength_one_as_a_one_element_array()
|
||||
{
|
||||
var json = """{"tagPath":"Recipe","dataType":"DInt","isArray":true,"arrayLength":1}""";
|
||||
AbCipEquipmentTagParser.TryParse(json, out var def).ShouldBeTrue();
|
||||
def!.IsArray.ShouldBeTrue();
|
||||
def.ElementCount.ShouldBe(1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Review finding I-1 — <c>isArray:true, arrayLength:1</c> must DISCOVER as a [1] array node
|
||||
/// (IsArray + ArrayDim 1), matching the foundation's materialisation, not as a scalar.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Equipment_ref_isArray_arrayLength_one_discovers_as_one_element_array()
|
||||
{
|
||||
var json = """{"deviceHostAddress":"ab://10.0.0.5/1,0","tagPath":"Recipe","dataType":"DInt","isArray":true,"arrayLength":1}""";
|
||||
AbCipEquipmentTagParser.TryParse(json, out var def).ShouldBeTrue();
|
||||
|
||||
var builder = new RecordingBuilder();
|
||||
var (drv, _) = NewDriver(def!);
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
|
||||
await drv.DiscoverAsync(builder, CancellationToken.None);
|
||||
|
||||
var arr = builder.Variables.Single().Info;
|
||||
arr.IsArray.ShouldBeTrue();
|
||||
arr.ArrayDim.ShouldBe(1u);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Review finding I-1 — the I-1 case: an <c>isArray:true, arrayLength:1</c> equipment tag
|
||||
/// reads a 1-ELEMENT typed array, NOT a scalar. On current code (gate <c>ElementCount > 1</c>)
|
||||
/// this reads a scalar; the explicit IsArray flag fixes it.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Equipment_ref_isArray_arrayLength_one_reads_as_one_element_array()
|
||||
{
|
||||
var json = """{"deviceHostAddress":"ab://10.0.0.5/1,0","tagPath":"Recipe","dataType":"DInt","isArray":true,"arrayLength":1}""";
|
||||
var factory = new FakeAbCipTagFactory();
|
||||
var opts = new AbCipDriverOptions
|
||||
{
|
||||
Devices = [new AbCipDeviceOptions("ab://10.0.0.5/1,0")],
|
||||
Tags = [],
|
||||
};
|
||||
var drv = new AbCipDriver(opts, "abcip-eq-array1", factory);
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
factory.Customise = p => new ArrayFakeAbCipTag(p, new int[] { 99 });
|
||||
|
||||
var snapshots = await drv.ReadAsync([json], CancellationToken.None);
|
||||
|
||||
snapshots.Single().StatusCode.ShouldBe(AbCipStatusMapper.Good);
|
||||
var value = snapshots.Single().Value.ShouldBeOfType<int[]>();
|
||||
value.ShouldBe([99]);
|
||||
// A 1-element array still threads elem_count 1 to libplctag.
|
||||
factory.Tags["Recipe"].CreationParams.ElementCount.ShouldBe(1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Regression — a genuinely scalar equipment ref (<c>isArray:false</c>) reads a boxed
|
||||
/// scalar via <see cref="IAbCipTagRuntime.DecodeValue"/>, never an array.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Equipment_ref_isArray_false_reads_as_scalar()
|
||||
{
|
||||
var json = """{"deviceHostAddress":"ab://10.0.0.5/1,0","tagPath":"Speed","dataType":"DInt","isArray":false}""";
|
||||
var factory = new FakeAbCipTagFactory();
|
||||
var opts = new AbCipDriverOptions
|
||||
{
|
||||
Devices = [new AbCipDeviceOptions("ab://10.0.0.5/1,0")],
|
||||
Tags = [],
|
||||
};
|
||||
var drv = new AbCipDriver(opts, "abcip-eq-scalar", factory);
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
factory.Customise = p => new FakeAbCipTag(p) { Value = 4200 };
|
||||
|
||||
var snapshots = await drv.ReadAsync([json], CancellationToken.None);
|
||||
|
||||
snapshots.Single().Value.ShouldBe(4200);
|
||||
snapshots.Single().Value.ShouldNotBeOfType<int[]>();
|
||||
}
|
||||
|
||||
// ---- helpers ----
|
||||
|
||||
Reference in New Issue
Block a user