test(driver-s7): resolve Medium code-review finding (Driver.S7-014)
Add S7TypeMappingTests.cs covering ReinterpretRawValue and BoxValueForWrite — 26 tests verifying every implemented type round-trip (Bool/Byte/UInt16/Int16/ UInt32/Int32/Float32), two's-complement reinterpret semantics (ushort→short, uint→int), unsupported-type NotSupportedException, and overflow edge cases. These methods were factored out as internal static in the S7-002/S7-008 commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,173 @@
|
|||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace ZB.MOM.WW.OtOpcUa.Driver.S7.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unit tests for <see cref="S7Driver.ReinterpretRawValue"/> and
|
||||||
|
/// <see cref="S7Driver.BoxValueForWrite"/> — the pure read/write type-reinterpret
|
||||||
|
/// helpers factored out of <c>ReadOneAsync</c> / <c>WriteOneAsync</c> so they can be
|
||||||
|
/// exercised without a live PLC (Driver.S7-014).
|
||||||
|
/// </summary>
|
||||||
|
[Trait("Category", "Unit")]
|
||||||
|
public sealed class S7TypeMappingTests
|
||||||
|
{
|
||||||
|
// ── Helpers ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private static S7TagDefinition Tag(string name, S7DataType dt) =>
|
||||||
|
new(name, "DB1.DBW0", dt);
|
||||||
|
|
||||||
|
private static S7ParsedAddress Addr(S7Size size) =>
|
||||||
|
new(S7Area.DataBlock, DbNumber: 1, size, ByteOffset: 0, BitOffset: 0);
|
||||||
|
|
||||||
|
// ── ReinterpretRawValue — implemented types ───────────────────────────────────────────
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReinterpretRawValue_Bool_returns_bool()
|
||||||
|
{
|
||||||
|
var tag = new S7TagDefinition("B", "DB1.DBX0.0", S7DataType.Bool);
|
||||||
|
var addr = new S7ParsedAddress(S7Area.DataBlock, 1, S7Size.Bit, 0, 0);
|
||||||
|
S7Driver.ReinterpretRawValue(tag, addr, true).ShouldBe(true);
|
||||||
|
S7Driver.ReinterpretRawValue(tag, addr, false).ShouldBe(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReinterpretRawValue_Byte_returns_byte()
|
||||||
|
{
|
||||||
|
var tag = new S7TagDefinition("By", "DB1.DBB0", S7DataType.Byte);
|
||||||
|
var addr = Addr(S7Size.Byte);
|
||||||
|
S7Driver.ReinterpretRawValue(tag, addr, (byte)42).ShouldBe((byte)42);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReinterpretRawValue_UInt16_returns_ushort()
|
||||||
|
{
|
||||||
|
S7Driver.ReinterpretRawValue(Tag("U16", S7DataType.UInt16), Addr(S7Size.Word), (ushort)1000)
|
||||||
|
.ShouldBe((ushort)1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReinterpretRawValue_Int16_reinterprets_ushort_as_signed()
|
||||||
|
{
|
||||||
|
// 0xFFFF as ushort == -1 as short (two's complement reinterpret, not Convert).
|
||||||
|
var result = S7Driver.ReinterpretRawValue(Tag("I16", S7DataType.Int16), Addr(S7Size.Word), (ushort)0xFFFF);
|
||||||
|
result.ShouldBe((short)-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReinterpretRawValue_UInt32_returns_uint()
|
||||||
|
{
|
||||||
|
S7Driver.ReinterpretRawValue(Tag("U32", S7DataType.UInt32), Addr(S7Size.DWord), (uint)70_000u)
|
||||||
|
.ShouldBe(70_000u);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReinterpretRawValue_Int32_reinterprets_uint_as_signed()
|
||||||
|
{
|
||||||
|
// 0xFFFFFFFF as uint == -1 as int.
|
||||||
|
var result = S7Driver.ReinterpretRawValue(Tag("I32", S7DataType.Int32), Addr(S7Size.DWord), 0xFFFF_FFFFu);
|
||||||
|
result.ShouldBe(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReinterpretRawValue_Float32_converts_uint_bits_to_float()
|
||||||
|
{
|
||||||
|
float expected = 3.14f;
|
||||||
|
uint bits = BitConverter.SingleToUInt32Bits(expected);
|
||||||
|
var result = S7Driver.ReinterpretRawValue(Tag("F32", S7DataType.Float32), Addr(S7Size.DWord), bits);
|
||||||
|
((float)result).ShouldBe(expected, tolerance: 1e-6f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── ReinterpretRawValue — unsupported types throw NotSupportedException ───────────────
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(S7DataType.Int64)]
|
||||||
|
[InlineData(S7DataType.UInt64)]
|
||||||
|
[InlineData(S7DataType.Float64)]
|
||||||
|
[InlineData(S7DataType.String)]
|
||||||
|
[InlineData(S7DataType.DateTime)]
|
||||||
|
public void ReinterpretRawValue_unsupported_type_throws_NotSupportedException(S7DataType dt)
|
||||||
|
{
|
||||||
|
Should.Throw<NotSupportedException>(() =>
|
||||||
|
S7Driver.ReinterpretRawValue(Tag("x", dt), Addr(S7Size.DWord), (uint)0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── BoxValueForWrite — implemented types ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BoxValueForWrite_Bool_converts_value_to_bool()
|
||||||
|
{
|
||||||
|
S7Driver.BoxValueForWrite(S7DataType.Bool, true).ShouldBe(true);
|
||||||
|
S7Driver.BoxValueForWrite(S7DataType.Bool, false).ShouldBe(false);
|
||||||
|
S7Driver.BoxValueForWrite(S7DataType.Bool, 1).ShouldBe(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BoxValueForWrite_Byte_converts_to_byte()
|
||||||
|
{
|
||||||
|
S7Driver.BoxValueForWrite(S7DataType.Byte, (byte)200).ShouldBe((byte)200);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BoxValueForWrite_UInt16_converts_to_ushort()
|
||||||
|
{
|
||||||
|
S7Driver.BoxValueForWrite(S7DataType.UInt16, (ushort)1234).ShouldBe((ushort)1234);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BoxValueForWrite_Int16_reinterprets_as_ushort_two_complement()
|
||||||
|
{
|
||||||
|
// -1 as short → 0xFFFF as ushort; S7.Net writes the ushort to the wire.
|
||||||
|
S7Driver.BoxValueForWrite(S7DataType.Int16, (short)-1).ShouldBe((ushort)0xFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BoxValueForWrite_UInt32_converts_to_uint()
|
||||||
|
{
|
||||||
|
S7Driver.BoxValueForWrite(S7DataType.UInt32, 70_000u).ShouldBe(70_000u);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BoxValueForWrite_Int32_reinterprets_as_uint_two_complement()
|
||||||
|
{
|
||||||
|
// -1 as int → 0xFFFFFFFF as uint.
|
||||||
|
S7Driver.BoxValueForWrite(S7DataType.Int32, -1).ShouldBe(0xFFFF_FFFFu);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BoxValueForWrite_Float32_encodes_as_ieee754_uint()
|
||||||
|
{
|
||||||
|
float f = 3.14f;
|
||||||
|
var expected = BitConverter.SingleToUInt32Bits(f);
|
||||||
|
S7Driver.BoxValueForWrite(S7DataType.Float32, f).ShouldBe(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── BoxValueForWrite — unsupported types throw NotSupportedException ─────────────────
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(S7DataType.Int64)]
|
||||||
|
[InlineData(S7DataType.UInt64)]
|
||||||
|
[InlineData(S7DataType.Float64)]
|
||||||
|
[InlineData(S7DataType.String)]
|
||||||
|
[InlineData(S7DataType.DateTime)]
|
||||||
|
public void BoxValueForWrite_unsupported_type_throws_NotSupportedException(S7DataType dt)
|
||||||
|
{
|
||||||
|
Should.Throw<NotSupportedException>(() =>
|
||||||
|
S7Driver.BoxValueForWrite(dt, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── BoxValueForWrite — overflow paths ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BoxValueForWrite_Byte_overflows_for_out_of_range_value()
|
||||||
|
{
|
||||||
|
// Convert.ToByte(256) throws OverflowException.
|
||||||
|
Should.Throw<OverflowException>(() => S7Driver.BoxValueForWrite(S7DataType.Byte, 256));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BoxValueForWrite_UInt16_overflows_for_out_of_range_value()
|
||||||
|
{
|
||||||
|
Should.Throw<OverflowException>(() => S7Driver.BoxValueForWrite(S7DataType.UInt16, 65_536));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user