TryParse now rejects three classes of malformed PCCC address: - Sub-element + bit-index together (e.g. T4:0.ACC/2) — never valid in PCCC - File number on I/O/S system files (e.g. I3:0, S2:1) — single-letter only - Sub-element on non-T/C/R files (e.g. B3:0.DN, N7:0.FOO) — only Timer, Counter, and Control files carry structured elements New helper predicates IsNoFileNumberLetter / IsSubElementFileLetter keep the parser's intent clear. Regression tests added in AbLegacyAddressTests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
106 lines
3.9 KiB
C#
106 lines
3.9 KiB
C#
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.AbLegacy;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests;
|
|
|
|
[Trait("Category", "Unit")]
|
|
public sealed class AbLegacyAddressTests
|
|
{
|
|
[Theory]
|
|
[InlineData("N7:0", "N", 7, 0, null, null)]
|
|
[InlineData("N7:15", "N", 7, 15, null, null)]
|
|
[InlineData("F8:5", "F", 8, 5, null, null)]
|
|
[InlineData("B3:0/0", "B", 3, 0, 0, null)]
|
|
[InlineData("B3:2/7", "B", 3, 2, 7, null)]
|
|
[InlineData("ST9:0", "ST", 9, 0, null, null)]
|
|
[InlineData("L9:3", "L", 9, 3, null, null)]
|
|
[InlineData("I:0/0", "I", null, 0, 0, null)]
|
|
[InlineData("O:1/2", "O", null, 1, 2, null)]
|
|
[InlineData("S:1", "S", null, 1, null, null)]
|
|
[InlineData("T4:0.ACC", "T", 4, 0, null, "ACC")]
|
|
[InlineData("T4:0.PRE", "T", 4, 0, null, "PRE")]
|
|
[InlineData("C5:2.CU", "C", 5, 2, null, "CU")]
|
|
[InlineData("R6:0.LEN", "R", 6, 0, null, "LEN")]
|
|
[InlineData("N7:0/3", "N", 7, 0, 3, null)]
|
|
public void TryParse_accepts_valid_pccc_addresses(string input, string letter, int? file, int word, int? bit, string? sub)
|
|
{
|
|
var a = AbLegacyAddress.TryParse(input);
|
|
a.ShouldNotBeNull();
|
|
a.FileLetter.ShouldBe(letter);
|
|
a.FileNumber.ShouldBe(file);
|
|
a.WordNumber.ShouldBe(word);
|
|
a.BitIndex.ShouldBe(bit);
|
|
a.SubElement.ShouldBe(sub);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(null)]
|
|
[InlineData("")]
|
|
[InlineData(" ")]
|
|
[InlineData("N7")] // missing :word
|
|
[InlineData(":0")] // missing file
|
|
[InlineData("X7:0")] // unknown file letter
|
|
[InlineData("N7:-1")] // negative word
|
|
[InlineData("N7:abc")] // non-numeric word
|
|
[InlineData("N7:0/-1")] // negative bit
|
|
[InlineData("N7:0/32")] // bit out of range
|
|
[InlineData("Nabc:0")] // non-numeric file number
|
|
public void TryParse_rejects_invalid_forms(string? input)
|
|
{
|
|
AbLegacyAddress.TryParse(input).ShouldBeNull();
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("N7:0")]
|
|
[InlineData("F8:5")]
|
|
[InlineData("B3:0/0")]
|
|
[InlineData("ST9:0")]
|
|
[InlineData("T4:0.ACC")]
|
|
[InlineData("I:0/0")]
|
|
[InlineData("S:1")]
|
|
public void ToLibplctagName_roundtrips(string input)
|
|
{
|
|
var a = AbLegacyAddress.TryParse(input);
|
|
a.ShouldNotBeNull();
|
|
a.ToLibplctagName().ShouldBe(input);
|
|
}
|
|
|
|
// ---- Driver.AbLegacy-003: Parser tightening ----
|
|
|
|
[Theory]
|
|
[InlineData("T4:0.ACC/2")] // sub-element + bit index — never valid in PCCC
|
|
[InlineData("C5:0.PRE/3")]
|
|
public void TryParse_rejects_subelement_plus_bitindex(string input) =>
|
|
AbLegacyAddress.TryParse(input).ShouldBeNull();
|
|
|
|
[Theory]
|
|
[InlineData("I3:0")] // I is a system file — no file number allowed
|
|
[InlineData("O2:1")]
|
|
[InlineData("S2:1")]
|
|
public void TryParse_rejects_file_number_on_IOS_files(string input) =>
|
|
AbLegacyAddress.TryParse(input).ShouldBeNull();
|
|
|
|
[Theory]
|
|
[InlineData("B3:0.DN")] // B (bit) file has no structured elements
|
|
[InlineData("N7:0.FOO")] // N (integer) file has no structured elements
|
|
[InlineData("F8:0.ACC")] // F (float) file has no structured elements
|
|
[InlineData("L9:0.PRE")] // L (long) file has no structured elements
|
|
public void TryParse_rejects_subelement_on_non_structured_file(string input) =>
|
|
AbLegacyAddress.TryParse(input).ShouldBeNull();
|
|
|
|
[Theory]
|
|
[InlineData("T4:0.ACC")] // T, C, R are the only structured-element files
|
|
[InlineData("C5:0.PRE")]
|
|
[InlineData("R6:0.LEN")]
|
|
public void TryParse_accepts_subelement_only_on_TCR_files(string input) =>
|
|
AbLegacyAddress.TryParse(input).ShouldNotBeNull();
|
|
|
|
[Theory]
|
|
[InlineData("I:0/0")] // I/O/S without file number are valid
|
|
[InlineData("O:1/2")]
|
|
[InlineData("S:1")]
|
|
public void TryParse_accepts_IOS_without_file_number(string input) =>
|
|
AbLegacyAddress.TryParse(input).ShouldNotBeNull();
|
|
}
|