Files
lmxopcua/tests/Client/ZB.MOM.WW.OtOpcUa.Client.Shared.Tests/Helpers/VariantBoundaryTests.cs
T

127 lines
5.2 KiB
C#

using Opc.Ua;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Client.Shared.Helpers;
namespace ZB.MOM.WW.OtOpcUa.Client.Shared.Tests.Helpers;
public class VariantBoundaryTests
{
public static IEnumerable<object?[]> Payloads()
{
foreach (var value in new object?[] { null, true, (sbyte)-1, (byte)1, (short)-2, (ushort)2,
-3, 3u, -4L, 4UL, 1.25f, 2.5, "", "text", Guid.Parse("de305d54-75b4-431b-adb2-eb6b9e546014"),
new DateTime(2026, 11, 10, 0, 0, 0, DateTimeKind.Utc), new byte[] { 1, 2 },
new[] { 1 }, new[] { 1, 2 }, new[] { "", "text" } })
yield return [value];
foreach (var value in new object[] { true, (sbyte)-1, (byte)1, (short)-2, (ushort)2,
-3, 3u, -4L, 4UL, 1.25f, 2.5, "text", Guid.Empty,
new DateTime(2026, 11, 10, 0, 0, 0, DateTimeKind.Utc) })
{
foreach (var count in new[] { 0, 1, 3 })
{
var array = Array.CreateInstance(value.GetType(), count);
for (var i = 0; i < count; i++) array.SetValue(value, i);
yield return [array];
}
}
}
[Theory]
[MemberData(nameof(Payloads))]
public void RoundTrip_PreservesClrTypeAndValue(object? expected)
{
var actual = ValueConverter.ToClrObject(ValueConverter.ToVariant(expected));
if (expected == null) actual.ShouldBeNull();
else
{
actual.ShouldNotBeNull();
actual.GetType().ShouldBe(expected.GetType());
actual.ShouldBe(expected);
}
}
[Fact]
public void RetainedArrays_AreDetachedFromCaller()
{
var bytes = new byte[] { 1, 2 };
var numbers = new[] { 3, 4 };
var byteValue = ValueConverter.ToVariant(bytes);
var numericValue = ValueConverter.ToVariant(numbers);
bytes[0] = 99;
numbers[0] = 99;
ValueConverter.ToClrObject(byteValue).ShouldBe(new byte[] { 1, 2 });
ValueConverter.ToClrObject(numericValue).ShouldBe(new[] { 3, 4 });
}
[Fact]
public void ByteString_NullAndEmptyRemainDistinct()
{
ValueConverter.ToClrObject(Variant.From(default(ByteString))).ShouldBeNull();
ValueConverter.ToClrObject(Variant.From(ByteString.Empty)).ShouldBe(Array.Empty<byte>());
ValueConverter.ToClrObject(Variant.Null).ShouldBeNull();
ValueConverter.ToClrObject(Variant.From(0)).ShouldBe(0);
}
[Fact]
public void NullWrapperInputs_RemainNullAfterRetention()
{
ValueConverter.ToClrObject(ValueConverter.ToVariant(default(ByteString))).ShouldBeNull();
ValueConverter.ToClrObject(ValueConverter.ToVariant(default(ArrayOf<int>))).ShouldBeNull();
ValueConverter.ToVariant(default(ArrayOf<byte>)).TypeInfo.ValueRank.ShouldBe(ValueRanks.OneDimension);
ValueConverter.ToClrObject(ValueConverter.ToVariant(Variant.From(default(ByteString)))).ShouldBeNull();
}
[Fact]
public void ReadOnlyWrappers_AreDetachedFromMutableBackingBuffers()
{
var bytes = new byte[] { 1, 2 };
var numbers = new[] { 3, 4 };
var retainedBytes = ValueConverter.ToVariant(new ByteString(bytes));
var retainedNumbers = ValueConverter.ToVariant(new ArrayOf<int>(numbers));
bytes[0] = 99;
numbers[0] = 99;
ValueConverter.ToClrObject(retainedBytes).ShouldBe(new byte[] { 1, 2 });
ValueConverter.ToClrObject(retainedNumbers).ShouldBe(new[] { 3, 4 });
}
[Fact]
public void RetainedByteStringArray_CopiesElementBuffersAndPreservesNullEmpty()
{
var bytes = new byte[] { 1, 2 };
var value = ValueConverter.ToVariant(new ByteString[] { default, ByteString.Empty, new(bytes) });
bytes[0] = 99;
var actual = ValueConverter.ToClrObject(value).ShouldBeOfType<byte[][]>();
actual[0].ShouldBeNull();
actual[1].ShouldBeEmpty();
actual[2].ShouldBe(new byte[] { 1, 2 });
}
[Fact]
public void ByteArrayAndByteString_KeepWireRank()
{
ValueConverter.ToVariant(new byte[] { 1 }).TypeInfo.BuiltInType.ShouldBe(BuiltInType.ByteString);
ValueConverter.ToVariant(new ArrayOf<byte>(new byte[] { 1 })).TypeInfo.BuiltInType.ShouldBe(BuiltInType.Byte);
ValueConverter.ToVariant(new ArrayOf<byte>(new byte[] { 1 })).TypeInfo.ValueRank.ShouldBe(ValueRanks.OneDimension);
}
[Fact]
public void NodeIds_KeepOpaqueBytesAndLiteralStringIdentifiers()
{
var opaque = NodeId.Parse("ns=2;b=AQID");
ValueConverter.ToClrObject(ValueConverter.ToVariant(opaque)).ShouldBe(opaque);
var literal = new NodeId("ns=2;s=literal", 4);
var result = ValueConverter.ToClrObject(ValueConverter.ToVariant(literal)).ShouldBeOfType<NodeId>();
result.NamespaceIndex.ShouldBe((ushort)4);
result.TryGetValue(out string? identifier).ShouldBeTrue();
identifier.ShouldBe("ns=2;s=literal");
}
[Fact]
public void UnsupportedShape_IsRejected()
{
Should.Throw<ServiceResultException>(() => ValueConverter.ToVariant(new int[2, 2]));
Should.Throw<ServiceResultException>(() => ValueConverter.ToVariant(new object()));
}
}