147 lines
4.6 KiB
C#
147 lines
4.6 KiB
C#
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.AbCip;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests;
|
|
|
|
[Trait("Category", "Unit")]
|
|
public sealed class AbCipTagPathTests
|
|
{
|
|
[Fact]
|
|
public void Controller_scope_single_segment()
|
|
{
|
|
var p = AbCipTagPath.TryParse("Motor1_Speed");
|
|
p.ShouldNotBeNull();
|
|
p.ProgramScope.ShouldBeNull();
|
|
p.Segments.Count.ShouldBe(1);
|
|
p.Segments[0].Name.ShouldBe("Motor1_Speed");
|
|
p.Segments[0].Subscripts.ShouldBeEmpty();
|
|
p.BitIndex.ShouldBeNull();
|
|
p.ToLibplctagName().ShouldBe("Motor1_Speed");
|
|
}
|
|
|
|
[Fact]
|
|
public void Program_scope_parses()
|
|
{
|
|
var p = AbCipTagPath.TryParse("Program:MainProgram.StepIndex");
|
|
p.ShouldNotBeNull();
|
|
p.ProgramScope.ShouldBe("MainProgram");
|
|
p.Segments.Single().Name.ShouldBe("StepIndex");
|
|
p.ToLibplctagName().ShouldBe("Program:MainProgram.StepIndex");
|
|
}
|
|
|
|
[Fact]
|
|
public void Structured_member_access_splits_segments()
|
|
{
|
|
var p = AbCipTagPath.TryParse("Motor1.Speed.Setpoint");
|
|
p.ShouldNotBeNull();
|
|
p.Segments.Select(s => s.Name).ShouldBe(["Motor1", "Speed", "Setpoint"]);
|
|
p.ToLibplctagName().ShouldBe("Motor1.Speed.Setpoint");
|
|
}
|
|
|
|
[Fact]
|
|
public void Single_dim_array_subscript()
|
|
{
|
|
var p = AbCipTagPath.TryParse("Data[7]");
|
|
p.ShouldNotBeNull();
|
|
p.Segments.Single().Name.ShouldBe("Data");
|
|
p.Segments.Single().Subscripts.ShouldBe([7]);
|
|
p.ToLibplctagName().ShouldBe("Data[7]");
|
|
}
|
|
|
|
[Fact]
|
|
public void Multi_dim_array_subscript()
|
|
{
|
|
var p = AbCipTagPath.TryParse("Matrix[1,2,3]");
|
|
p.ShouldNotBeNull();
|
|
p.Segments.Single().Subscripts.ShouldBe([1, 2, 3]);
|
|
p.ToLibplctagName().ShouldBe("Matrix[1,2,3]");
|
|
}
|
|
|
|
[Fact]
|
|
public void Bit_in_dint_captured_as_bit_index()
|
|
{
|
|
var p = AbCipTagPath.TryParse("Flags.3");
|
|
p.ShouldNotBeNull();
|
|
p.Segments.Single().Name.ShouldBe("Flags");
|
|
p.BitIndex.ShouldBe(3);
|
|
p.ToLibplctagName().ShouldBe("Flags.3");
|
|
}
|
|
|
|
[Fact]
|
|
public void Bit_in_dint_after_member()
|
|
{
|
|
var p = AbCipTagPath.TryParse("Motor.Status.12");
|
|
p.ShouldNotBeNull();
|
|
p.Segments.Select(s => s.Name).ShouldBe(["Motor", "Status"]);
|
|
p.BitIndex.ShouldBe(12);
|
|
p.ToLibplctagName().ShouldBe("Motor.Status.12");
|
|
}
|
|
|
|
[Fact]
|
|
public void Bit_index_32_rejected_out_of_range()
|
|
{
|
|
// 32 exceeds the DINT bit width — treated as a member name rather than bit selector,
|
|
// which fails ident validation and returns null.
|
|
AbCipTagPath.TryParse("Flags.32").ShouldBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void Program_scope_with_members_and_subscript_and_bit()
|
|
{
|
|
var p = AbCipTagPath.TryParse("Program:MainProgram.Motors[0].Status.5");
|
|
p.ShouldNotBeNull();
|
|
p.ProgramScope.ShouldBe("MainProgram");
|
|
p.Segments.Select(s => s.Name).ShouldBe(["Motors", "Status"]);
|
|
p.Segments[0].Subscripts.ShouldBe([0]);
|
|
p.BitIndex.ShouldBe(5);
|
|
p.ToLibplctagName().ShouldBe("Program:MainProgram.Motors[0].Status.5");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(null)]
|
|
[InlineData("")]
|
|
[InlineData(" ")]
|
|
[InlineData("Program:")] // empty scope
|
|
[InlineData("Program:MP")] // no body after scope
|
|
[InlineData("1InvalidStart")] // ident starts with digit
|
|
[InlineData("Bad Name")] // space in ident
|
|
[InlineData("Motor[]")] // empty subscript
|
|
[InlineData("Motor[-1]")] // negative subscript
|
|
[InlineData("Motor[a]")] // non-numeric subscript
|
|
[InlineData("Motor[")] // unbalanced bracket
|
|
[InlineData("Motor.")] // trailing dot
|
|
[InlineData(".Motor")] // leading dot
|
|
public void Invalid_shapes_return_null(string? input)
|
|
{
|
|
AbCipTagPath.TryParse(input).ShouldBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void Ident_with_underscore_accepted()
|
|
{
|
|
AbCipTagPath.TryParse("_private_tag")!.Segments.Single().Name.ShouldBe("_private_tag");
|
|
}
|
|
|
|
[Fact]
|
|
public void ToLibplctagName_recomposes_round_trip()
|
|
{
|
|
var cases = new[]
|
|
{
|
|
"Motor1_Speed",
|
|
"Program:Main.Counter",
|
|
"Array[5]",
|
|
"Matrix[1,2]",
|
|
"Obj.Member.Sub",
|
|
"Flags.0",
|
|
"Program:P.Obj[2].Flags.15",
|
|
};
|
|
foreach (var c in cases)
|
|
{
|
|
var parsed = AbCipTagPath.TryParse(c);
|
|
parsed.ShouldNotBeNull(c);
|
|
parsed.ToLibplctagName().ShouldBe(c);
|
|
}
|
|
}
|
|
}
|