192 lines
9.0 KiB
C#
192 lines
9.0 KiB
C#
using Shouldly;
|
|
using Xunit;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.S7.Tests;
|
|
|
|
/// <summary>
|
|
/// Unit tests for the S7 wide-type (8-byte numeric) byte-buffer codec: the pure
|
|
/// <see cref="S7Driver.DecodeScalarBlock"/> / <see cref="S7Driver.EncodeScalarBlock"/>
|
|
/// helpers and <see cref="S7Driver.ScalarByteWidth"/>. These decode/encode an
|
|
/// Int64/UInt64/LReal (Float64) scalar from a contiguous big-endian byte block — the
|
|
/// network I/O half (<c>Plc.ReadBytesAsync</c>/<c>WriteBytesAsync</c>) has no in-process
|
|
/// fake so only the codec is unit-proven (mirrors <see cref="S7ArrayReadTests"/>).
|
|
/// String/DateTime decode is a deferred stub here (T3/T4); this file pins the
|
|
/// NotSupportedException contract those land against.
|
|
/// </summary>
|
|
[Trait("Category", "Unit")]
|
|
public sealed class S7ScalarBlockTests
|
|
{
|
|
// ── Helpers ──────────────────────────────────────────────────────────────────────────
|
|
|
|
// Wide scalars are byte-anchored: DB{n}.DBB{offset}, parser yields S7Size.Byte.
|
|
private static S7TagDefinition Tag(S7DataType dt, int stringLength = 254) =>
|
|
new("WideTag", "DB1.DBB0", dt, StringLength: stringLength);
|
|
|
|
private static S7ParsedAddress Addr() =>
|
|
new(S7Area.DataBlock, DbNumber: 1, S7Size.Byte, ByteOffset: 0, BitOffset: 0);
|
|
|
|
// S7 is big-endian: most-significant byte first.
|
|
private static byte[] BeUInt64(ulong v)
|
|
{
|
|
var b = new byte[8];
|
|
for (var i = 0; i < 8; i++)
|
|
b[i] = (byte)(v >> (56 - i * 8));
|
|
return b;
|
|
}
|
|
|
|
// ── ScalarByteWidth ───────────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>Verifies the 8-byte numeric widths and the String/DateTime widths.</summary>
|
|
[Theory]
|
|
[InlineData(S7DataType.Int64, 8)]
|
|
[InlineData(S7DataType.UInt64, 8)]
|
|
[InlineData(S7DataType.Float64, 8)]
|
|
[InlineData(S7DataType.DateTime, 8)]
|
|
public void ScalarByteWidth_fixed_width_types(S7DataType dt, int expected)
|
|
=> S7Driver.ScalarByteWidth(Tag(dt)).ShouldBe(expected);
|
|
|
|
/// <summary>Verifies String width is StringLength + 2 (S7 STRING header: max-len + actual-len).</summary>
|
|
[Fact]
|
|
public void ScalarByteWidth_String_is_length_plus_two()
|
|
=> S7Driver.ScalarByteWidth(Tag(S7DataType.String, stringLength: 10)).ShouldBe(12);
|
|
|
|
// ── DecodeScalarBlock — Int64 ─────────────────────────────────────────────────────────
|
|
|
|
/// <summary>Verifies an Int64 block decodes from big-endian bytes.</summary>
|
|
[Fact]
|
|
public void DecodeScalarBlock_Int64_reads_big_endian()
|
|
{
|
|
var block = BeUInt64(0x0123456789ABCDEFUL);
|
|
// First byte is the most-significant byte (0x01) — proves big-endian, not little-endian.
|
|
block[0].ShouldBe((byte)0x01);
|
|
|
|
var result = S7Driver.DecodeScalarBlock(Tag(S7DataType.Int64), Addr(), block);
|
|
result.ShouldBeOfType<long>().ShouldBe(0x0123456789ABCDEFL);
|
|
}
|
|
|
|
/// <summary>Verifies a negative Int64 decodes correctly (two's complement, high bit set).</summary>
|
|
[Fact]
|
|
public void DecodeScalarBlock_Int64_negative()
|
|
{
|
|
var block = BeUInt64(unchecked((ulong)-2L)); // 0xFFFF_FFFF_FFFF_FFFE
|
|
var result = S7Driver.DecodeScalarBlock(Tag(S7DataType.Int64), Addr(), block);
|
|
result.ShouldBeOfType<long>().ShouldBe(-2L);
|
|
}
|
|
|
|
// ── DecodeScalarBlock — UInt64 ────────────────────────────────────────────────────────
|
|
|
|
/// <summary>Verifies a UInt64 block decodes a value larger than long.MaxValue.</summary>
|
|
[Fact]
|
|
public void DecodeScalarBlock_UInt64_reads_value_above_long_max()
|
|
{
|
|
var block = BeUInt64(ulong.MaxValue); // 0xFFFF_FFFF_FFFF_FFFF
|
|
var result = S7Driver.DecodeScalarBlock(Tag(S7DataType.UInt64), Addr(), block);
|
|
result.ShouldBeOfType<ulong>().ShouldBe(ulong.MaxValue);
|
|
}
|
|
|
|
// ── DecodeScalarBlock — Float64 (LReal) ───────────────────────────────────────────────
|
|
|
|
/// <summary>Verifies a Float64 (LReal) block decodes from IEEE-754 big-endian.</summary>
|
|
[Fact]
|
|
public void DecodeScalarBlock_Float64_reads_ieee754_big_endian()
|
|
{
|
|
var bits = unchecked((ulong)BitConverter.DoubleToInt64Bits(Math.PI));
|
|
var block = BeUInt64(bits);
|
|
var result = S7Driver.DecodeScalarBlock(Tag(S7DataType.Float64), Addr(), block);
|
|
result.ShouldBeOfType<double>().ShouldBe(Math.PI, tolerance: 1e-12);
|
|
}
|
|
|
|
// ── EncodeScalarBlock — big-endian byte production ────────────────────────────────────
|
|
|
|
/// <summary>Verifies Int64 encodes to big-endian bytes (MSB first).</summary>
|
|
[Fact]
|
|
public void EncodeScalarBlock_Int64_writes_big_endian()
|
|
{
|
|
var bytes = S7Driver.EncodeScalarBlock(Tag(S7DataType.Int64), 0x0123456789ABCDEFL);
|
|
bytes.Length.ShouldBe(8);
|
|
bytes.ShouldBe(BeUInt64(0x0123456789ABCDEFUL));
|
|
bytes[0].ShouldBe((byte)0x01); // MSB first — little-endian regression guard.
|
|
}
|
|
|
|
/// <summary>Verifies UInt64 encodes to big-endian bytes.</summary>
|
|
[Fact]
|
|
public void EncodeScalarBlock_UInt64_writes_big_endian()
|
|
{
|
|
var bytes = S7Driver.EncodeScalarBlock(Tag(S7DataType.UInt64), ulong.MaxValue);
|
|
bytes.ShouldBe(BeUInt64(ulong.MaxValue));
|
|
}
|
|
|
|
/// <summary>Verifies Float64 encodes to IEEE-754 big-endian bytes.</summary>
|
|
[Fact]
|
|
public void EncodeScalarBlock_Float64_writes_ieee754_big_endian()
|
|
{
|
|
var bytes = S7Driver.EncodeScalarBlock(Tag(S7DataType.Float64), Math.PI);
|
|
bytes.ShouldBe(BeUInt64(unchecked((ulong)BitConverter.DoubleToInt64Bits(Math.PI))));
|
|
}
|
|
|
|
// ── Round-trip identity (encode → decode) ─────────────────────────────────────────────
|
|
|
|
/// <summary>Verifies Int64 round-trips through encode→decode for positive, negative and edge values.</summary>
|
|
[Theory]
|
|
[InlineData(0L)]
|
|
[InlineData(1L)]
|
|
[InlineData(-1L)]
|
|
[InlineData(long.MaxValue)]
|
|
[InlineData(long.MinValue)]
|
|
[InlineData(0x0123456789ABCDEFL)]
|
|
public void Int64_round_trips(long value)
|
|
{
|
|
var tag = Tag(S7DataType.Int64);
|
|
var decoded = S7Driver.DecodeScalarBlock(tag, Addr(), S7Driver.EncodeScalarBlock(tag, value));
|
|
decoded.ShouldBeOfType<long>().ShouldBe(value);
|
|
}
|
|
|
|
/// <summary>Verifies UInt64 round-trips, including a large value above long.MaxValue.</summary>
|
|
[Theory]
|
|
[InlineData(0UL)]
|
|
[InlineData(70_000UL)]
|
|
[InlineData(ulong.MaxValue)]
|
|
[InlineData(0x8000_0000_0000_0001UL)]
|
|
public void UInt64_round_trips(ulong value)
|
|
{
|
|
var tag = Tag(S7DataType.UInt64);
|
|
var decoded = S7Driver.DecodeScalarBlock(tag, Addr(), S7Driver.EncodeScalarBlock(tag, value));
|
|
decoded.ShouldBeOfType<ulong>().ShouldBe(value);
|
|
}
|
|
|
|
/// <summary>Verifies Float64 (LReal) round-trips for representative doubles.</summary>
|
|
[Theory]
|
|
[InlineData(0.0)]
|
|
[InlineData(3.141592653589793)]
|
|
[InlineData(-2.5e-300)]
|
|
[InlineData(1.7976931348623157e308)]
|
|
public void Float64_round_trips(double value)
|
|
{
|
|
var tag = Tag(S7DataType.Float64);
|
|
var decoded = S7Driver.DecodeScalarBlock(tag, Addr(), S7Driver.EncodeScalarBlock(tag, value));
|
|
decoded.ShouldBeOfType<double>().ShouldBe(value);
|
|
}
|
|
|
|
// ── Deferred-stub contract: String/DateTime throw NotSupportedException ────────────────
|
|
|
|
/// <summary>Verifies String/DateTime decode is a deferred stub (NotSupportedException — T3/T4).</summary>
|
|
/// <param name="dt">The not-yet-implemented wide type.</param>
|
|
[Theory]
|
|
[InlineData(S7DataType.String)]
|
|
[InlineData(S7DataType.DateTime)]
|
|
public void DecodeScalarBlock_String_or_DateTime_throws_NotSupported(S7DataType dt)
|
|
{
|
|
var tag = Tag(dt, stringLength: 10);
|
|
var block = new byte[S7Driver.ScalarByteWidth(tag)];
|
|
Should.Throw<NotSupportedException>(() => S7Driver.DecodeScalarBlock(tag, Addr(), block));
|
|
}
|
|
|
|
/// <summary>Verifies String/DateTime encode is a deferred stub (NotSupportedException — T3/T4).</summary>
|
|
/// <param name="dt">The not-yet-implemented wide type.</param>
|
|
[Theory]
|
|
[InlineData(S7DataType.String)]
|
|
[InlineData(S7DataType.DateTime)]
|
|
public void EncodeScalarBlock_String_or_DateTime_throws_NotSupported(S7DataType dt)
|
|
=> Should.Throw<NotSupportedException>(() => S7Driver.EncodeScalarBlock(Tag(dt), "x"));
|
|
}
|