459cbb444e
I1: the U11 Bits[n] -> Bits[n/32] rewrite was unconditional. The native runtime now keeps the original element path until the first reply settles the layout: 0xD3 confirms DWORD addressing (bit n%32); any other reply type (e.g. a 0xC1 per-element BOOL array) or a refusal of the DWORD index falls back to Bits[n] and re-reads, and a write settles the layout by a read first, so it can never decode or write element n/32 of an element-addressed array. I2: libplctag ErrorNoMatch/ErrorBadDevice no longer fold into NotFound (they now read as BadConnection: comms, Degraded, LastSuccessfulRead preserved). ErrorNotImplemented - and the native runtime's own pre-send refusals - raise AbCipLocalRefusalException: BadNotSupported, no health, LastSuccessfulRead NOT advanced, no backoff record, not reachable for the probe or Test Connect. M1: the bit RMW's parent read/write status words follow the health contract (refusal: per-tag Bad + Warning; comms: evict the parent + Degraded). M2: device Unsupported read refusals return Refused (advance LastSuccessfulRead); device write refusals call RecordWriteAnswered (not the pre-send native type gate). M3: a good write clears a Degraded a write comms fault recorded (a read-side Degraded is still left for the read path). M4: a superseded init throws AbCipInitializationSupersededException (not an OCE); ShutdownAsync stamps Unknown only if no newer init published meanwhile. M5: the sync-Dispose timing assert is 2 s against a 4 s probe read; the unencodable-array test asserts health. Refs #737 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
97 lines
4.9 KiB
C#
97 lines
4.9 KiB
C#
using libplctag;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport;
|
|
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 AbCipStatusMapperTests
|
|
{
|
|
/// <summary>Verifies that transport status values are mapped to OPC UA status codes.</summary>
|
|
/// <param name="status">The transport status to map.</param>
|
|
/// <param name="expected">The expected OPC UA status code.</param>
|
|
[Theory]
|
|
[InlineData(AbStatus.Ok, AbCipStatusMapper.Good)]
|
|
[InlineData(AbStatus.Pending, AbCipStatusMapper.GoodMoreData)]
|
|
[InlineData(AbStatus.Timeout, AbCipStatusMapper.BadTimeout)]
|
|
[InlineData(AbStatus.NotFound, AbCipStatusMapper.BadNodeIdUnknown)]
|
|
[InlineData(AbStatus.NotAllowed, AbCipStatusMapper.BadNotWritable)]
|
|
[InlineData(AbStatus.OutOfBounds, AbCipStatusMapper.BadOutOfRange)]
|
|
[InlineData(AbStatus.TooLarge, AbCipStatusMapper.BadOutOfRange)]
|
|
[InlineData(AbStatus.TooSmall, AbCipStatusMapper.BadOutOfRange)]
|
|
[InlineData(AbStatus.Unsupported, AbCipStatusMapper.BadNotSupported)]
|
|
[InlineData(AbStatus.TypeMismatch, AbCipStatusMapper.BadTypeMismatch)]
|
|
[InlineData(AbStatus.DeviceStateConflict, AbCipStatusMapper.BadDeviceFailure)]
|
|
[InlineData(AbStatus.BadConnection, AbCipStatusMapper.BadCommunicationError)]
|
|
[InlineData(AbStatus.BadReply, AbCipStatusMapper.BadCommunicationError)]
|
|
[InlineData(AbStatus.Partial, AbCipStatusMapper.BadCommunicationError)]
|
|
[InlineData(AbStatus.Abort, AbCipStatusMapper.BadCommunicationError)]
|
|
public void MapStatus_maps_every_AbStatus(AbStatus status, uint expected)
|
|
{
|
|
AbCipStatusMapper.MapStatus(status).ShouldBe(expected);
|
|
// The int overload must agree - it is the seam IAbCipTagRuntime.GetStatus() drives.
|
|
AbCipStatusMapper.MapStatus((int)status).ShouldBe(expected);
|
|
}
|
|
|
|
/// <summary>Verifies that timeout is distinguished from generic communication error.</summary>
|
|
[Fact]
|
|
public void MapStatus_distinguishes_timeout_from_generic_comms_error()
|
|
{
|
|
AbCipStatusMapper.MapStatus((int)AbStatus.Timeout).ShouldBe(AbCipStatusMapper.BadTimeout);
|
|
AbCipStatusMapper.MapStatus((int)AbStatus.NotFound).ShouldBe(AbCipStatusMapper.BadNodeIdUnknown);
|
|
AbCipStatusMapper.MapStatus((int)AbStatus.NotFound).ShouldNotBe(AbCipStatusMapper.BadCommunicationError);
|
|
}
|
|
|
|
/// <summary>Verifies MapException maps transport exceptions by status and everything else to comms error.</summary>
|
|
[Fact]
|
|
public void MapException_uses_the_transport_status()
|
|
{
|
|
AbCipStatusMapper.MapException(new AbTransportException(AbStatus.NotFound, "x"))
|
|
.ShouldBe(AbCipStatusMapper.BadNodeIdUnknown);
|
|
AbCipStatusMapper.MapException(new InvalidOperationException("x"))
|
|
.ShouldBe(AbCipStatusMapper.BadCommunicationError);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Regression oracle: the OPC UA code the pre-AbStatus <c>MapLibplctagStatus</c> returned for every
|
|
/// libplctag member (table copied from the deleted switch) must equal
|
|
/// <c>MapStatus(ToAbStatus(member))</c>.
|
|
/// </summary>
|
|
/// <param name="status">A libplctag status.</param>
|
|
[Theory]
|
|
[MemberData(nameof(AllLibplctagStatuses))]
|
|
public void Translation_plus_MapStatus_matches_the_old_libplctag_mapping(Status status)
|
|
{
|
|
AbCipStatusMapper.MapStatus(LibplctagStatusTranslation.ToAbStatus(status)).ShouldBe(OldMapLibplctagStatus(status));
|
|
}
|
|
|
|
/// <summary>Every defined libplctag <see cref="Status"/> member.</summary>
|
|
/// <returns>One row per member.</returns>
|
|
public static IEnumerable<object[]> AllLibplctagStatuses() =>
|
|
Enum.GetValues<Status>().Select(s => new object[] { s });
|
|
|
|
private static uint OldMapLibplctagStatus(Status status) => status switch
|
|
{
|
|
Status.Ok => AbCipStatusMapper.Good,
|
|
Status.Pending => AbCipStatusMapper.GoodMoreData,
|
|
Status.ErrorTimeout => AbCipStatusMapper.BadTimeout,
|
|
Status.ErrorNotFound => AbCipStatusMapper.BadNodeIdUnknown, // #737 I2: NoMatch/BadDevice are comms
|
|
Status.ErrorNotAllowed => AbCipStatusMapper.BadNotWritable,
|
|
Status.ErrorOutOfBounds or Status.ErrorTooLarge or Status.ErrorTooSmall => AbCipStatusMapper.BadOutOfRange,
|
|
Status.ErrorUnsupported or Status.ErrorNotImplemented => AbCipStatusMapper.BadNotSupported,
|
|
_ => AbCipStatusMapper.BadCommunicationError, // incl. BadConnection/BadGateway/BadReply/Partial/Abort/...
|
|
};
|
|
|
|
/// <summary>A decode past the buffer end surfaces as BadOutOfRange once the adapter wraps it.</summary>
|
|
[Fact]
|
|
public void Wrapped_decode_OutOfBounds_maps_to_BadOutOfRange()
|
|
{
|
|
var ex = Should.Throw<AbTransportException>(() =>
|
|
LibplctagStatusTranslation.WrapSync<int>(() => throw new LibPlcTagException("ErrorOutOfBounds")));
|
|
|
|
AbCipStatusMapper.MapException(ex).ShouldBe(AbCipStatusMapper.BadOutOfRange);
|
|
}
|
|
}
|