merge: libplctag empty-path fix (null Path for an empty CIP route)

This commit is contained in:
Joseph Doherty
2026-10-07 19:06:36 -04:00
9 changed files with 135 additions and 9 deletions
@@ -32,7 +32,9 @@ public sealed class AbCipReadSmokeTests
// smoke test was missed.
// Micro800 has no backplane: its cip-path is empty (the compose service runs
// ab_server --plc=Micro800 with no --path, which refuses a routed request). Every
// other family reaches slot 0 through `1,0`.
// other family reaches slot 0 through `1,0`. Both transports are expected to pass
// for Micro800: the libplctag adapters map the empty route to a null `path` attribute
// (LibplctagPath.Normalize) because libplctag rejects "" with ErrorBadData.
var cipPath = profile.Family == AbCipPlcFamily.Micro800 ? "" : "1,0";
var deviceUri = $"ab://{fixture.Host}:{fixture.Port}/{cipPath}";
var drv = new AbCipDriver(new AbCipDriverOptions
@@ -0,0 +1,51 @@
using Shouldly;
using Xunit;
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests;
/// <summary>
/// libplctag rejects an empty-string <c>path</c> with <c>ErrorBadData</c>; "no route" (Micro800, or any
/// unrouted device authored as <c>ab://host:44818/</c>) must reach it as null. Pre-dates the native transport.
/// </summary>
public sealed class LibplctagEmptyPathTests
{
private static AbCipTagCreateParams Params(string cipPath) =>
new(Gateway: "10.100.0.35", Port: 44818, CipPath: cipPath, LibplctagPlcAttribute: "micro800",
TagName: "TestDINT", Timeout: TimeSpan.FromSeconds(5));
[Theory]
[InlineData("", null)]
[InlineData(" ", null)]
[InlineData(null, null)]
[InlineData("1,0", "1,0")]
public void Normalize_maps_blank_to_null_and_keeps_a_route(string? input, string? expected) =>
LibplctagPath.Normalize(input).ShouldBe(expected);
[Fact]
public void The_data_tag_omits_the_path_for_an_empty_route()
{
using var tag = LibplctagTagRuntime.BuildTag(Params(""));
tag.Path.ShouldBeNull();
}
[Fact]
public void The_data_tag_keeps_a_real_route()
{
using var tag = LibplctagTagRuntime.BuildTag(Params("1,0"));
tag.Path.ShouldBe("1,0");
}
[Fact]
public void The_symbol_list_tag_omits_the_path_for_an_empty_route()
{
using var tag = LibplctagListingReader.BuildSymbolListTag(Params(""));
tag.Path.ShouldBeNull();
}
[Fact]
public void The_template_tag_omits_the_path_for_an_empty_route()
{
using var tag = LibplctagTemplateReader.BuildTemplateTag(Params(""), templateInstanceId: 1);
tag.Path.ShouldBeNull();
}
}
@@ -0,0 +1,37 @@
using Shouldly;
using Xunit;
namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests;
/// <summary>
/// libplctag rejects an empty-string <c>path</c> with <c>ErrorBadData</c>; an unrouted MicroLogix
/// (<c>ab://host:44818/</c>) must reach it as null.
/// </summary>
public sealed class LibplctagEmptyPathTests
{
private static AbLegacyTagCreateParams Params(string cipPath) =>
new(Gateway: "10.100.0.35", Port: 44818, CipPath: cipPath, LibplctagPlcAttribute: "micrologix",
TagName: "N7:0", Timeout: TimeSpan.FromSeconds(5));
[Theory]
[InlineData("", null)]
[InlineData(" ", null)]
[InlineData(null, null)]
[InlineData("1,0", "1,0")]
public void Normalize_maps_blank_to_null_and_keeps_a_route(string? input, string? expected) =>
LibplctagPath.Normalize(input).ShouldBe(expected);
[Fact]
public void The_data_tag_omits_the_path_for_an_empty_route()
{
using var tag = LibplctagLegacyTagRuntime.BuildTag(Params(""));
tag.Path.ShouldBeNull();
}
[Fact]
public void The_data_tag_keeps_a_real_route()
{
using var tag = LibplctagLegacyTagRuntime.BuildTag(Params("1,0"));
tag.Path.ShouldBe("1,0");
}
}