Moving structure as preparation for docs
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\AMWD.Protocols.Modbus.Serial\AMWD.Protocols.Modbus.Serial.csproj" />
|
||||
<ProjectReference Include="..\..\src\AMWD.Protocols.Modbus.Tcp\AMWD.Protocols.Modbus.Tcp.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,775 @@
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AMWD.Protocols.Modbus.Common.Contracts;
|
||||
|
||||
namespace AMWD.Protocols.Modbus.Tests.Common.Contracts
|
||||
{
|
||||
[TestClass]
|
||||
public class ModbusClientBaseTest
|
||||
{
|
||||
// Consts
|
||||
private const byte UNIT_ID = 42;
|
||||
private const ushort START_ADDRESS = 123;
|
||||
private const ushort READ_COUNT = 12;
|
||||
|
||||
// Mocks
|
||||
private Mock<IModbusConnection> _connection;
|
||||
private Mock<IModbusProtocol> _protocol;
|
||||
|
||||
// Responses
|
||||
private List<Coil> _readCoilsResponse;
|
||||
private List<DiscreteInput> _readDiscreteInputsResponse;
|
||||
private List<HoldingRegister> _readHoldingRegistersResponse;
|
||||
private List<InputRegister> _readInputRegistersResponse;
|
||||
private DeviceIdentificationRaw _firstDeviceIdentificationResponse;
|
||||
private Queue<DeviceIdentificationRaw> _deviceIdentificationResponseQueue;
|
||||
private Coil _writeSingleCoilResponse;
|
||||
private HoldingRegister _writeSingleHoldingRegisterResponse;
|
||||
private (ushort startAddress, ushort count) _writeMultipleCoilsResponse;
|
||||
private (ushort startAddress, ushort count) _writeMultipleHoldingRegistersResponse;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
_readCoilsResponse = [];
|
||||
_readDiscreteInputsResponse = [];
|
||||
_readHoldingRegistersResponse = [];
|
||||
_readInputRegistersResponse = [];
|
||||
|
||||
for (int i = 0; i < READ_COUNT; i++)
|
||||
{
|
||||
_readCoilsResponse.Add(new Coil
|
||||
{
|
||||
Address = (ushort)i,
|
||||
HighByte = (byte)((i % 2 == 0) ? 0xFF : 0x00)
|
||||
});
|
||||
_readDiscreteInputsResponse.Add(new DiscreteInput
|
||||
{
|
||||
Address = (ushort)i,
|
||||
HighByte = (byte)((i % 2 == 1) ? 0xFF : 0x00)
|
||||
});
|
||||
|
||||
_readHoldingRegistersResponse.Add(new HoldingRegister
|
||||
{
|
||||
Address = (ushort)i,
|
||||
HighByte = 0x00,
|
||||
LowByte = (byte)(i + 10)
|
||||
});
|
||||
_readInputRegistersResponse.Add(new InputRegister
|
||||
{
|
||||
Address = (ushort)i,
|
||||
HighByte = 0x00,
|
||||
LowByte = (byte)(i + 15)
|
||||
});
|
||||
}
|
||||
|
||||
_firstDeviceIdentificationResponse = new DeviceIdentificationRaw
|
||||
{
|
||||
AllowsIndividualAccess = true,
|
||||
MoreRequestsNeeded = false,
|
||||
NextObjectIdToRequest = 0x00,
|
||||
};
|
||||
_firstDeviceIdentificationResponse.Objects.Add(0x00, Encoding.UTF8.GetBytes("AM.WD"));
|
||||
_firstDeviceIdentificationResponse.Objects.Add(0x01, Encoding.UTF8.GetBytes("AMWD-MB"));
|
||||
_firstDeviceIdentificationResponse.Objects.Add(0x02, Encoding.UTF8.GetBytes("1.2.3"));
|
||||
_firstDeviceIdentificationResponse.Objects.Add(0x03, Encoding.UTF8.GetBytes("https://github.com/AM-WD/AMWD.Protocols.Modbus"));
|
||||
_firstDeviceIdentificationResponse.Objects.Add(0x04, Encoding.UTF8.GetBytes("AM.WD Modbus Library"));
|
||||
_firstDeviceIdentificationResponse.Objects.Add(0x05, Encoding.UTF8.GetBytes("UnitTests"));
|
||||
_firstDeviceIdentificationResponse.Objects.Add(0x06, Encoding.UTF8.GetBytes("Modbus Client Base Unit Test"));
|
||||
|
||||
_deviceIdentificationResponseQueue = new Queue<DeviceIdentificationRaw>();
|
||||
_deviceIdentificationResponseQueue.Enqueue(_firstDeviceIdentificationResponse);
|
||||
|
||||
_writeSingleCoilResponse = new Coil { Address = START_ADDRESS };
|
||||
_writeSingleHoldingRegisterResponse = new HoldingRegister { Address = START_ADDRESS, Value = 0x1234 };
|
||||
|
||||
_writeMultipleCoilsResponse = (START_ADDRESS, READ_COUNT);
|
||||
_writeMultipleHoldingRegistersResponse = (START_ADDRESS, READ_COUNT);
|
||||
}
|
||||
|
||||
#region Common/Connection/Assertions
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldPrettyPrint()
|
||||
{
|
||||
// Arrange
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
string str = client.ToString();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual("Modbus client using Moq protocol to connect via Mock", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowExceptionOnNullConnection()
|
||||
{
|
||||
// Arrange
|
||||
IModbusConnection connection = null;
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentNullException>(() => new ModbusClientBaseWrapper(connection));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(true)]
|
||||
[DataRow(false)]
|
||||
public void ShouldAlsoDisposeConnection(bool disposeConnection)
|
||||
{
|
||||
// Arrange
|
||||
var client = GetClient(disposeConnection);
|
||||
|
||||
// Act
|
||||
client.Dispose();
|
||||
|
||||
// Assert
|
||||
if (disposeConnection)
|
||||
_connection.Verify(c => c.Dispose(), Times.Once);
|
||||
|
||||
_connection.VerifyNoOtherCalls();
|
||||
|
||||
_protocol.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAllowDisposeMultipleTimes()
|
||||
{
|
||||
// Arrange
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
client.Dispose();
|
||||
client.Dispose();
|
||||
|
||||
// Assert
|
||||
_connection.Verify(c => c.Dispose(), Times.Once);
|
||||
_connection.VerifyNoOtherCalls();
|
||||
|
||||
_protocol.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldAssertDisposed()
|
||||
{
|
||||
// Arrange
|
||||
var client = GetClient();
|
||||
client.Dispose();
|
||||
|
||||
// Act + Assert
|
||||
await Assert.ThrowsExactlyAsync<ObjectDisposedException>(() => client.ReadCoilsAsync(UNIT_ID, START_ADDRESS, READ_COUNT));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldAssertProtocolSet()
|
||||
{
|
||||
// Arrange
|
||||
var client = GetClient();
|
||||
client.Protocol = null;
|
||||
|
||||
// Act + Assert
|
||||
await Assert.ThrowsExactlyAsync<ArgumentNullException>(() => client.ReadCoilsAsync(UNIT_ID, START_ADDRESS, READ_COUNT));
|
||||
}
|
||||
|
||||
#endregion Common/Connection/Assertions
|
||||
|
||||
#region Read
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldReadCoils()
|
||||
{
|
||||
// Arrange
|
||||
_readCoilsResponse.Add(new Coil());
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
var result = await client.ReadCoilsAsync(UNIT_ID, START_ADDRESS, READ_COUNT);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(READ_COUNT, result.Count);
|
||||
|
||||
for (int i = 0; i < READ_COUNT; i++)
|
||||
{
|
||||
Assert.AreEqual(START_ADDRESS + i, result[i].Address);
|
||||
Assert.AreEqual(i % 2 == 0, result[i].Value);
|
||||
}
|
||||
|
||||
_connection.Verify(c => c.InvokeAsync(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<Func<IReadOnlyList<byte>, bool>>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_connection.VerifyNoOtherCalls();
|
||||
|
||||
_protocol.Verify(p => p.SerializeReadCoils(UNIT_ID, START_ADDRESS, READ_COUNT), Times.Once);
|
||||
_protocol.Verify(p => p.ValidateResponse(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.Verify(p => p.DeserializeReadCoils(It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldReadDiscreteInputs()
|
||||
{
|
||||
// Arrange
|
||||
_readDiscreteInputsResponse.Add(new DiscreteInput());
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
var result = await client.ReadDiscreteInputsAsync(UNIT_ID, START_ADDRESS, READ_COUNT);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(READ_COUNT, result.Count);
|
||||
|
||||
for (int i = 0; i < READ_COUNT; i++)
|
||||
{
|
||||
Assert.AreEqual(START_ADDRESS + i, result[i].Address);
|
||||
Assert.AreEqual(i % 2 == 1, result[i].Value);
|
||||
}
|
||||
|
||||
_connection.Verify(c => c.InvokeAsync(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<Func<IReadOnlyList<byte>, bool>>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_connection.VerifyNoOtherCalls();
|
||||
|
||||
_protocol.Verify(p => p.SerializeReadDiscreteInputs(UNIT_ID, START_ADDRESS, READ_COUNT), Times.Once);
|
||||
_protocol.Verify(p => p.ValidateResponse(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.Verify(p => p.DeserializeReadDiscreteInputs(It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldReadHoldingRegisters()
|
||||
{
|
||||
// Arrange
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
var result = await client.ReadHoldingRegistersAsync(UNIT_ID, START_ADDRESS, READ_COUNT);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(READ_COUNT, result.Count);
|
||||
|
||||
for (int i = 0; i < READ_COUNT; i++)
|
||||
{
|
||||
Assert.AreEqual(START_ADDRESS + i, result[i].Address);
|
||||
Assert.AreEqual(i + 10, result[i].Value);
|
||||
}
|
||||
|
||||
_connection.Verify(c => c.InvokeAsync(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<Func<IReadOnlyList<byte>, bool>>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_connection.VerifyNoOtherCalls();
|
||||
|
||||
_protocol.Verify(p => p.SerializeReadHoldingRegisters(UNIT_ID, START_ADDRESS, READ_COUNT), Times.Once);
|
||||
_protocol.Verify(p => p.ValidateResponse(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.Verify(p => p.DeserializeReadHoldingRegisters(It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldReadInputRegisters()
|
||||
{
|
||||
// Arrange
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
var result = await client.ReadInputRegistersAsync(UNIT_ID, START_ADDRESS, READ_COUNT);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(READ_COUNT, result.Count);
|
||||
|
||||
for (int i = 0; i < READ_COUNT; i++)
|
||||
{
|
||||
Assert.AreEqual(START_ADDRESS + i, result[i].Address);
|
||||
Assert.AreEqual(i + 15, result[i].Value);
|
||||
}
|
||||
|
||||
_connection.Verify(c => c.InvokeAsync(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<Func<IReadOnlyList<byte>, bool>>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_connection.VerifyNoOtherCalls();
|
||||
|
||||
_protocol.Verify(p => p.SerializeReadInputRegisters(UNIT_ID, START_ADDRESS, READ_COUNT), Times.Once);
|
||||
_protocol.Verify(p => p.ValidateResponse(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.Verify(p => p.DeserializeReadInputRegisters(It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldReadDeviceIdentification()
|
||||
{
|
||||
// Arrange
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
var result = await client.ReadDeviceIdentificationAsync(UNIT_ID, ModbusDeviceIdentificationCategory.Basic, ModbusDeviceIdentificationObject.VendorName);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.IsTrue(result.IsIndividualAccessAllowed);
|
||||
Assert.AreEqual("AM.WD", result.VendorName);
|
||||
Assert.AreEqual("AMWD-MB", result.ProductCode);
|
||||
Assert.AreEqual("1.2.3", result.MajorMinorRevision);
|
||||
Assert.AreEqual("https://github.com/AM-WD/AMWD.Protocols.Modbus", result.VendorUrl);
|
||||
Assert.AreEqual("AM.WD Modbus Library", result.ProductName);
|
||||
Assert.AreEqual("UnitTests", result.ModelName);
|
||||
Assert.AreEqual("Modbus Client Base Unit Test", result.UserApplicationName);
|
||||
|
||||
Assert.AreEqual(0, result.ExtendedObjects.Count);
|
||||
|
||||
_connection.Verify(c => c.InvokeAsync(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<Func<IReadOnlyList<byte>, bool>>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_connection.VerifyNoOtherCalls();
|
||||
|
||||
_protocol.Verify(p => p.SerializeReadDeviceIdentification(UNIT_ID, ModbusDeviceIdentificationCategory.Basic, ModbusDeviceIdentificationObject.VendorName), Times.Once);
|
||||
_protocol.Verify(p => p.ValidateResponse(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.Verify(p => p.DeserializeReadDeviceIdentification(It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldReadDeviceIdentificationMultipleCycles()
|
||||
{
|
||||
// Arrange
|
||||
_firstDeviceIdentificationResponse.MoreRequestsNeeded = true;
|
||||
_firstDeviceIdentificationResponse.NextObjectIdToRequest = 0x07;
|
||||
_deviceIdentificationResponseQueue.Enqueue(new DeviceIdentificationRaw
|
||||
{
|
||||
AllowsIndividualAccess = true,
|
||||
MoreRequestsNeeded = false,
|
||||
NextObjectIdToRequest = 0x00,
|
||||
Objects = new Dictionary<byte, byte[]>
|
||||
{
|
||||
{ 0x07, new byte[] { 0x01, 0x02, 0x03 } },
|
||||
}
|
||||
});
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
var result = await client.ReadDeviceIdentificationAsync(UNIT_ID, ModbusDeviceIdentificationCategory.Extended, ModbusDeviceIdentificationObject.VendorName);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.IsTrue(result.IsIndividualAccessAllowed);
|
||||
Assert.AreEqual("AM.WD", result.VendorName);
|
||||
Assert.AreEqual("AMWD-MB", result.ProductCode);
|
||||
Assert.AreEqual("1.2.3", result.MajorMinorRevision);
|
||||
Assert.AreEqual("https://github.com/AM-WD/AMWD.Protocols.Modbus", result.VendorUrl);
|
||||
Assert.AreEqual("AM.WD Modbus Library", result.ProductName);
|
||||
Assert.AreEqual("UnitTests", result.ModelName);
|
||||
Assert.AreEqual("Modbus Client Base Unit Test", result.UserApplicationName);
|
||||
|
||||
Assert.AreEqual(1, result.ExtendedObjects.Count);
|
||||
Assert.AreEqual(0x07, result.ExtendedObjects.First().Key);
|
||||
CollectionAssert.AreEqual(new byte[] { 0x01, 0x02, 0x03 }, result.ExtendedObjects.First().Value);
|
||||
|
||||
_connection.Verify(c => c.InvokeAsync(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<Func<IReadOnlyList<byte>, bool>>(), It.IsAny<CancellationToken>()), Times.Exactly(2));
|
||||
_connection.VerifyNoOtherCalls();
|
||||
|
||||
_protocol.Verify(p => p.SerializeReadDeviceIdentification(UNIT_ID, ModbusDeviceIdentificationCategory.Extended, ModbusDeviceIdentificationObject.VendorName), Times.Once);
|
||||
_protocol.Verify(p => p.SerializeReadDeviceIdentification(UNIT_ID, ModbusDeviceIdentificationCategory.Extended, (ModbusDeviceIdentificationObject)0x07), Times.Once);
|
||||
_protocol.Verify(p => p.ValidateResponse(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<IReadOnlyList<byte>>()), Times.Exactly(2));
|
||||
_protocol.Verify(p => p.DeserializeReadDeviceIdentification(It.IsAny<IReadOnlyList<byte>>()), Times.Exactly(2));
|
||||
_protocol.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
#endregion Read
|
||||
|
||||
#region Write
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldWriteSingleCoil()
|
||||
{
|
||||
// Arrange
|
||||
var coil = new Coil
|
||||
{
|
||||
Address = START_ADDRESS,
|
||||
Value = false
|
||||
};
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
bool result = await client.WriteSingleCoilAsync(UNIT_ID, coil);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result);
|
||||
|
||||
_connection.Verify(c => c.InvokeAsync(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<Func<IReadOnlyList<byte>, bool>>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_connection.VerifyNoOtherCalls();
|
||||
|
||||
_protocol.Verify(p => p.SerializeWriteSingleCoil(UNIT_ID, coil), Times.Once);
|
||||
_protocol.Verify(p => p.ValidateResponse(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.Verify(p => p.DeserializeWriteSingleCoil(It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldFailWriteSingleCoilOnAddress()
|
||||
{
|
||||
// Arrange
|
||||
var coil = new Coil
|
||||
{
|
||||
Address = START_ADDRESS + 1,
|
||||
Value = false
|
||||
};
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
bool result = await client.WriteSingleCoilAsync(UNIT_ID, coil);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(result);
|
||||
|
||||
_connection.Verify(c => c.InvokeAsync(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<Func<IReadOnlyList<byte>, bool>>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_connection.VerifyNoOtherCalls();
|
||||
|
||||
_protocol.Verify(p => p.SerializeWriteSingleCoil(UNIT_ID, coil), Times.Once);
|
||||
_protocol.Verify(p => p.ValidateResponse(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.Verify(p => p.DeserializeWriteSingleCoil(It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldFailWriteSingleCoilOnValue()
|
||||
{
|
||||
// Arrange
|
||||
var coil = new Coil
|
||||
{
|
||||
Address = START_ADDRESS,
|
||||
Value = true
|
||||
};
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
bool result = await client.WriteSingleCoilAsync(UNIT_ID, coil);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(result);
|
||||
|
||||
_connection.Verify(c => c.InvokeAsync(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<Func<IReadOnlyList<byte>, bool>>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_connection.VerifyNoOtherCalls();
|
||||
|
||||
_protocol.Verify(p => p.SerializeWriteSingleCoil(UNIT_ID, coil), Times.Once);
|
||||
_protocol.Verify(p => p.ValidateResponse(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.Verify(p => p.DeserializeWriteSingleCoil(It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldWriteSingleHoldingRegister()
|
||||
{
|
||||
// Arrange
|
||||
var register = new HoldingRegister
|
||||
{
|
||||
Address = START_ADDRESS,
|
||||
Value = 0x1234
|
||||
};
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
bool result = await client.WriteSingleHoldingRegisterAsync(UNIT_ID, register);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result);
|
||||
|
||||
_connection.Verify(c => c.InvokeAsync(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<Func<IReadOnlyList<byte>, bool>>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_connection.VerifyNoOtherCalls();
|
||||
|
||||
_protocol.Verify(p => p.SerializeWriteSingleHoldingRegister(UNIT_ID, register), Times.Once);
|
||||
_protocol.Verify(p => p.ValidateResponse(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.Verify(p => p.DeserializeWriteSingleHoldingRegister(It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldFailWriteSingleHoldingRegisterOnAddress()
|
||||
{
|
||||
// Arrange
|
||||
var register = new HoldingRegister
|
||||
{
|
||||
Address = START_ADDRESS + 1,
|
||||
Value = 0x1234
|
||||
};
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
bool result = await client.WriteSingleHoldingRegisterAsync(UNIT_ID, register);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(result);
|
||||
|
||||
_connection.Verify(c => c.InvokeAsync(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<Func<IReadOnlyList<byte>, bool>>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_connection.VerifyNoOtherCalls();
|
||||
|
||||
_protocol.Verify(p => p.SerializeWriteSingleHoldingRegister(UNIT_ID, register), Times.Once);
|
||||
_protocol.Verify(p => p.ValidateResponse(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.Verify(p => p.DeserializeWriteSingleHoldingRegister(It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldFailWriteSingleHoldingRegisterOnValue()
|
||||
{
|
||||
// Arrange
|
||||
var register = new HoldingRegister
|
||||
{
|
||||
Address = START_ADDRESS,
|
||||
Value = 0x1233
|
||||
};
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
bool result = await client.WriteSingleHoldingRegisterAsync(UNIT_ID, register);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(result);
|
||||
|
||||
_connection.Verify(c => c.InvokeAsync(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<Func<IReadOnlyList<byte>, bool>>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_connection.VerifyNoOtherCalls();
|
||||
|
||||
_protocol.Verify(p => p.SerializeWriteSingleHoldingRegister(UNIT_ID, register), Times.Once);
|
||||
_protocol.Verify(p => p.ValidateResponse(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.Verify(p => p.DeserializeWriteSingleHoldingRegister(It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldWriteMultipleCoils()
|
||||
{
|
||||
// Arrange
|
||||
var coils = new List<Coil>();
|
||||
for (int i = 0; i < READ_COUNT; i++)
|
||||
{
|
||||
coils.Add(new Coil
|
||||
{
|
||||
Address = (ushort)(START_ADDRESS + i),
|
||||
Value = i % 2 == 0
|
||||
});
|
||||
}
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
bool result = await client.WriteMultipleCoilsAsync(UNIT_ID, coils);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result);
|
||||
|
||||
_connection.Verify(c => c.InvokeAsync(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<Func<IReadOnlyList<byte>, bool>>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_connection.VerifyNoOtherCalls();
|
||||
|
||||
_protocol.Verify(p => p.SerializeWriteMultipleCoils(UNIT_ID, coils), Times.Once);
|
||||
_protocol.Verify(p => p.ValidateResponse(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.Verify(p => p.DeserializeWriteMultipleCoils(It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldFailWriteMultipleCoilsOnAddress()
|
||||
{
|
||||
// Arrange
|
||||
_writeMultipleCoilsResponse.startAddress = START_ADDRESS + 1;
|
||||
var coils = new List<Coil>();
|
||||
for (int i = 0; i < READ_COUNT; i++)
|
||||
{
|
||||
coils.Add(new Coil
|
||||
{
|
||||
Address = (ushort)(START_ADDRESS + i),
|
||||
Value = i % 2 == 0
|
||||
});
|
||||
}
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
bool result = await client.WriteMultipleCoilsAsync(UNIT_ID, coils);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(result);
|
||||
|
||||
_connection.Verify(c => c.InvokeAsync(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<Func<IReadOnlyList<byte>, bool>>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_connection.VerifyNoOtherCalls();
|
||||
|
||||
_protocol.Verify(p => p.SerializeWriteMultipleCoils(UNIT_ID, coils), Times.Once);
|
||||
_protocol.Verify(p => p.ValidateResponse(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.Verify(p => p.DeserializeWriteMultipleCoils(It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldFailWriteMultipleCoilsOnCount()
|
||||
{
|
||||
// Arrange
|
||||
_writeMultipleCoilsResponse.count = READ_COUNT + 1;
|
||||
var coils = new List<Coil>();
|
||||
for (int i = 0; i < READ_COUNT; i++)
|
||||
{
|
||||
coils.Add(new Coil
|
||||
{
|
||||
Address = (ushort)(START_ADDRESS + i),
|
||||
Value = i % 2 == 0
|
||||
});
|
||||
}
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
bool result = await client.WriteMultipleCoilsAsync(UNIT_ID, coils);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(result);
|
||||
|
||||
_connection.Verify(c => c.InvokeAsync(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<Func<IReadOnlyList<byte>, bool>>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_connection.VerifyNoOtherCalls();
|
||||
|
||||
_protocol.Verify(p => p.SerializeWriteMultipleCoils(UNIT_ID, coils), Times.Once);
|
||||
_protocol.Verify(p => p.ValidateResponse(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.Verify(p => p.DeserializeWriteMultipleCoils(It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldWriteMultipleRegisters()
|
||||
{
|
||||
// Arrange
|
||||
var registers = new List<HoldingRegister>();
|
||||
for (int i = 0; i < READ_COUNT; i++)
|
||||
{
|
||||
registers.Add(new HoldingRegister
|
||||
{
|
||||
Address = (ushort)(START_ADDRESS + i),
|
||||
Value = (ushort)i
|
||||
});
|
||||
}
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
bool result = await client.WriteMultipleHoldingRegistersAsync(UNIT_ID, registers);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result);
|
||||
|
||||
_connection.Verify(c => c.InvokeAsync(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<Func<IReadOnlyList<byte>, bool>>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_connection.VerifyNoOtherCalls();
|
||||
|
||||
_protocol.Verify(p => p.SerializeWriteMultipleHoldingRegisters(UNIT_ID, registers), Times.Once);
|
||||
_protocol.Verify(p => p.ValidateResponse(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.Verify(p => p.DeserializeWriteMultipleHoldingRegisters(It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldFailWriteMultiplRegistersOnAddress()
|
||||
{
|
||||
// Arrange
|
||||
_writeMultipleHoldingRegistersResponse.startAddress = START_ADDRESS + 1;
|
||||
var registers = new List<HoldingRegister>();
|
||||
for (int i = 0; i < READ_COUNT; i++)
|
||||
{
|
||||
registers.Add(new HoldingRegister
|
||||
{
|
||||
Address = (ushort)(START_ADDRESS + i),
|
||||
Value = (ushort)i
|
||||
});
|
||||
}
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
bool result = await client.WriteMultipleHoldingRegistersAsync(UNIT_ID, registers);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(result);
|
||||
|
||||
_connection.Verify(c => c.InvokeAsync(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<Func<IReadOnlyList<byte>, bool>>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_connection.VerifyNoOtherCalls();
|
||||
|
||||
_protocol.Verify(p => p.SerializeWriteMultipleHoldingRegisters(UNIT_ID, registers), Times.Once);
|
||||
_protocol.Verify(p => p.ValidateResponse(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.Verify(p => p.DeserializeWriteMultipleHoldingRegisters(It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldFailWriteMultipleRegistersOnCount()
|
||||
{
|
||||
// Arrange
|
||||
_writeMultipleHoldingRegistersResponse.count = READ_COUNT + 1;
|
||||
var registers = new List<HoldingRegister>();
|
||||
for (int i = 0; i < READ_COUNT; i++)
|
||||
{
|
||||
registers.Add(new HoldingRegister
|
||||
{
|
||||
Address = (ushort)(START_ADDRESS + i),
|
||||
Value = (ushort)i
|
||||
});
|
||||
}
|
||||
var client = GetClient();
|
||||
|
||||
// Act
|
||||
bool result = await client.WriteMultipleHoldingRegistersAsync(UNIT_ID, registers);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(result);
|
||||
|
||||
_connection.Verify(c => c.InvokeAsync(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<Func<IReadOnlyList<byte>, bool>>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_connection.VerifyNoOtherCalls();
|
||||
|
||||
_protocol.Verify(p => p.SerializeWriteMultipleHoldingRegisters(UNIT_ID, registers), Times.Once);
|
||||
_protocol.Verify(p => p.ValidateResponse(It.IsAny<IReadOnlyList<byte>>(), It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.Verify(p => p.DeserializeWriteMultipleHoldingRegisters(It.IsAny<IReadOnlyList<byte>>()), Times.Once);
|
||||
_protocol.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
#endregion Write
|
||||
|
||||
private ModbusClientBase GetClient(bool disposeConnection = true)
|
||||
{
|
||||
_connection = new Mock<IModbusConnection>();
|
||||
_connection
|
||||
.SetupGet(c => c.Name)
|
||||
.Returns("Mock");
|
||||
|
||||
_protocol = new Mock<IModbusProtocol>();
|
||||
_protocol
|
||||
.SetupGet(p => p.Name)
|
||||
.Returns("Moq");
|
||||
_protocol
|
||||
.Setup(p => p.DeserializeReadCoils(It.IsAny<IReadOnlyList<byte>>()))
|
||||
.Returns(() => _readCoilsResponse);
|
||||
_protocol
|
||||
.Setup(p => p.DeserializeReadDiscreteInputs(It.IsAny<IReadOnlyList<byte>>()))
|
||||
.Returns(() => _readDiscreteInputsResponse);
|
||||
_protocol
|
||||
.Setup(p => p.DeserializeReadHoldingRegisters(It.IsAny<IReadOnlyList<byte>>()))
|
||||
.Returns(() => _readHoldingRegistersResponse);
|
||||
_protocol
|
||||
.Setup(p => p.DeserializeReadInputRegisters(It.IsAny<IReadOnlyList<byte>>()))
|
||||
.Returns(() => _readInputRegistersResponse);
|
||||
_protocol
|
||||
.Setup(p => p.DeserializeReadDeviceIdentification(It.IsAny<IReadOnlyList<byte>>()))
|
||||
.Returns(() => _deviceIdentificationResponseQueue.Dequeue());
|
||||
|
||||
_protocol
|
||||
.Setup(p => p.DeserializeWriteSingleCoil(It.IsAny<IReadOnlyList<byte>>()))
|
||||
.Returns(() => _writeSingleCoilResponse);
|
||||
_protocol
|
||||
.Setup(p => p.DeserializeWriteSingleHoldingRegister(It.IsAny<IReadOnlyList<byte>>()))
|
||||
.Returns(() => _writeSingleHoldingRegisterResponse);
|
||||
_protocol
|
||||
.Setup(p => p.DeserializeWriteMultipleCoils(It.IsAny<IReadOnlyList<byte>>()))
|
||||
.Returns(() => _writeMultipleCoilsResponse);
|
||||
_protocol
|
||||
.Setup(p => p.DeserializeWriteMultipleHoldingRegisters(It.IsAny<IReadOnlyList<byte>>()))
|
||||
.Returns(() => _writeMultipleHoldingRegistersResponse);
|
||||
|
||||
return new ModbusClientBaseWrapper(_connection.Object, disposeConnection)
|
||||
{
|
||||
Protocol = _protocol.Object,
|
||||
};
|
||||
}
|
||||
|
||||
internal class ModbusClientBaseWrapper : ModbusClientBase
|
||||
{
|
||||
public ModbusClientBaseWrapper(IModbusConnection connection)
|
||||
: base(connection)
|
||||
{ }
|
||||
|
||||
public ModbusClientBaseWrapper(IModbusConnection connection, bool disposeConnection)
|
||||
: base(connection, disposeConnection)
|
||||
{ }
|
||||
|
||||
public override IModbusProtocol Protocol { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
namespace AMWD.Protocols.Modbus.Tests.Common.Extensions
|
||||
{
|
||||
[TestClass]
|
||||
public class ModbusDecimalExtensionsTest
|
||||
{
|
||||
#region Modbus to value
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetSingle()
|
||||
{
|
||||
// Arrange
|
||||
var registers = new HoldingRegister[]
|
||||
{
|
||||
new(),
|
||||
new() { Address = 100, HighByte = 0x41, LowByte = 0x45 },
|
||||
new() { Address = 101, HighByte = 0x70, LowByte = 0xA4 }
|
||||
};
|
||||
|
||||
// Act
|
||||
float f = registers.GetSingle(1);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(12.34f, f);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetSingleReversedRegisters()
|
||||
{
|
||||
// Arrange
|
||||
var registers = new HoldingRegister[]
|
||||
{
|
||||
new() { Address = 101, HighByte = 0x41, LowByte = 0x45 },
|
||||
new() { Address = 100, HighByte = 0x70, LowByte = 0xA4 }
|
||||
};
|
||||
|
||||
// Act
|
||||
float f = registers.GetSingle(0, reverseRegisterOrder: true);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(12.34f, f);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowNullOnGetSingle()
|
||||
{
|
||||
// Arrange
|
||||
HoldingRegister[] registers = null;
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentNullException>(() => registers.GetSingle(0));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowArgumentOnGetSingleForLength()
|
||||
{
|
||||
// Arrange
|
||||
var registers = new HoldingRegister[]
|
||||
{
|
||||
new() { Address = 101, HighByte = 0x01, LowByte = 0x02 }
|
||||
};
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentException>(() => registers.GetSingle(0));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(1)]
|
||||
[DataRow(-1)]
|
||||
public void ShouldThrowArgumentOutOfRangeOnGetSingle(int startIndex)
|
||||
{
|
||||
// Arrange
|
||||
var registers = new HoldingRegister[]
|
||||
{
|
||||
new() { Address = 101, HighByte = 0x01, LowByte = 0x02 },
|
||||
new() { Address = 100, HighByte = 0x03, LowByte = 0x04 }
|
||||
};
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => registers.GetSingle(startIndex));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowArgumentOnGetSingleForType()
|
||||
{
|
||||
// Arrange
|
||||
var registers = new ModbusObject[]
|
||||
{
|
||||
new HoldingRegister { Address = 100, HighByte = 0x01, LowByte = 0x02 },
|
||||
new InputRegister { Address = 101, HighByte = 0x03, LowByte = 0x04 }
|
||||
};
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentException>(() => registers.GetSingle(0));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetDouble()
|
||||
{
|
||||
// Arrange
|
||||
var registers = new HoldingRegister[]
|
||||
{
|
||||
new(),
|
||||
new() { Address = 100, HighByte = 0x40, LowByte = 0x28 },
|
||||
new() { Address = 101, HighByte = 0xAE, LowByte = 0x14 },
|
||||
new() { Address = 102, HighByte = 0x7A, LowByte = 0xE1 },
|
||||
new() { Address = 103, HighByte = 0x47, LowByte = 0xAE }
|
||||
};
|
||||
|
||||
// Act
|
||||
double d = registers.GetDouble(1);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(12.34, d);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetDoubleReversedRegisters()
|
||||
{
|
||||
// Arrange
|
||||
var registers = new HoldingRegister[]
|
||||
{
|
||||
new() { Address = 103, HighByte = 0x40, LowByte = 0x28 },
|
||||
new() { Address = 102, HighByte = 0xAE, LowByte = 0x14 },
|
||||
new() { Address = 101, HighByte = 0x7A, LowByte = 0xE1 },
|
||||
new() { Address = 100, HighByte = 0x47, LowByte = 0xAE }
|
||||
};
|
||||
|
||||
// Act
|
||||
double d = registers.GetDouble(0, reverseRegisterOrder: true);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(12.34, d);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowNullOnGetDouble()
|
||||
{
|
||||
// Arrange
|
||||
HoldingRegister[] registers = null;
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentNullException>(() => registers.GetDouble(0));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowArgumentOnGetDoubleForLength()
|
||||
{
|
||||
// Arrange
|
||||
var registers = new HoldingRegister[]
|
||||
{
|
||||
new() { Address = 100, HighByte = 0x40, LowByte = 0x28 },
|
||||
new() { Address = 101, HighByte = 0xAE, LowByte = 0x14 },
|
||||
new() { Address = 102, HighByte = 0x7A, LowByte = 0xE1 }
|
||||
};
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentException>(() => registers.GetDouble(0));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(1)]
|
||||
[DataRow(-1)]
|
||||
public void ShouldThrowArgumentOutOfRangeOnGetDouble(int startIndex)
|
||||
{
|
||||
// Arrange
|
||||
var registers = new HoldingRegister[]
|
||||
{
|
||||
new() { Address = 100, HighByte = 0x40, LowByte = 0x28 },
|
||||
new() { Address = 101, HighByte = 0xAE, LowByte = 0x14 },
|
||||
new() { Address = 102, HighByte = 0x7A, LowByte = 0xE1 },
|
||||
new() { Address = 103, HighByte = 0x47, LowByte = 0xAE }
|
||||
};
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => registers.GetDouble(startIndex));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowArgumentOnGetDoubleForType()
|
||||
{
|
||||
// Arrange
|
||||
var registers = new ModbusObject[]
|
||||
{
|
||||
new HoldingRegister { Address = 100, HighByte = 0x40, LowByte = 0x28 },
|
||||
new InputRegister { Address = 101, HighByte = 0xAE, LowByte = 0x14 },
|
||||
new HoldingRegister { Address = 102, HighByte = 0x7A, LowByte = 0xE1 },
|
||||
new InputRegister { Address = 103, HighByte = 0x47, LowByte = 0xAE }
|
||||
};
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentException>(() => registers.GetDouble(0));
|
||||
}
|
||||
|
||||
#endregion Modbus to value
|
||||
|
||||
#region Value to Modbus
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldConvertSingle()
|
||||
{
|
||||
// Arrange
|
||||
float f = 12.34f;
|
||||
|
||||
// Act
|
||||
var registers = f.ToRegister(5).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(registers);
|
||||
Assert.AreEqual(2, registers.Count);
|
||||
|
||||
Assert.AreEqual(5, registers[0].Address);
|
||||
Assert.AreEqual(0x41, registers[0].HighByte);
|
||||
Assert.AreEqual(0x45, registers[0].LowByte);
|
||||
|
||||
Assert.AreEqual(6, registers[1].Address);
|
||||
Assert.AreEqual(0x70, registers[1].HighByte);
|
||||
Assert.AreEqual(0xA4, registers[1].LowByte);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldConvertSingleReversed()
|
||||
{
|
||||
// Arrange
|
||||
float f = 12.34f;
|
||||
|
||||
// Act
|
||||
var registers = f.ToRegister(5, reverseRegisterOrder: true).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(registers);
|
||||
Assert.AreEqual(2, registers.Count);
|
||||
|
||||
Assert.AreEqual(6, registers[0].Address);
|
||||
Assert.AreEqual(0x41, registers[0].HighByte);
|
||||
Assert.AreEqual(0x45, registers[0].LowByte);
|
||||
|
||||
Assert.AreEqual(5, registers[1].Address);
|
||||
Assert.AreEqual(0x70, registers[1].HighByte);
|
||||
Assert.AreEqual(0xA4, registers[1].LowByte);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldConvertDouble()
|
||||
{
|
||||
// Arrange
|
||||
double d = 12.34;
|
||||
|
||||
// Act
|
||||
var registers = d.ToRegister(5).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(registers);
|
||||
Assert.AreEqual(4, registers.Count);
|
||||
|
||||
Assert.AreEqual(5, registers[0].Address);
|
||||
Assert.AreEqual(0x40, registers[0].HighByte);
|
||||
Assert.AreEqual(0x28, registers[0].LowByte);
|
||||
|
||||
Assert.AreEqual(6, registers[1].Address);
|
||||
Assert.AreEqual(0xAE, registers[1].HighByte);
|
||||
Assert.AreEqual(0x14, registers[1].LowByte);
|
||||
|
||||
Assert.AreEqual(7, registers[2].Address);
|
||||
Assert.AreEqual(0x7A, registers[2].HighByte);
|
||||
Assert.AreEqual(0xE1, registers[2].LowByte);
|
||||
|
||||
Assert.AreEqual(8, registers[3].Address);
|
||||
Assert.AreEqual(0x47, registers[3].HighByte);
|
||||
Assert.AreEqual(0xAE, registers[3].LowByte);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldConvertDoubleReversed()
|
||||
{
|
||||
// Arrange
|
||||
double d = 12.34;
|
||||
|
||||
// Act
|
||||
var registers = d.ToRegister(5, reverseRegisterOrder: true).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(registers);
|
||||
Assert.AreEqual(4, registers.Count);
|
||||
|
||||
Assert.AreEqual(8, registers[0].Address);
|
||||
Assert.AreEqual(0x40, registers[0].HighByte);
|
||||
Assert.AreEqual(0x28, registers[0].LowByte);
|
||||
|
||||
Assert.AreEqual(7, registers[1].Address);
|
||||
Assert.AreEqual(0xAE, registers[1].HighByte);
|
||||
Assert.AreEqual(0x14, registers[1].LowByte);
|
||||
|
||||
Assert.AreEqual(6, registers[2].Address);
|
||||
Assert.AreEqual(0x7A, registers[2].HighByte);
|
||||
Assert.AreEqual(0xE1, registers[2].LowByte);
|
||||
|
||||
Assert.AreEqual(5, registers[3].Address);
|
||||
Assert.AreEqual(0x47, registers[3].HighByte);
|
||||
Assert.AreEqual(0xAE, registers[3].LowByte);
|
||||
}
|
||||
|
||||
#endregion Value to Modbus
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
using System.Text;
|
||||
|
||||
namespace AMWD.Protocols.Modbus.Tests.Common.Extensions
|
||||
{
|
||||
[TestClass]
|
||||
public class ModbusExtensionsTest
|
||||
{
|
||||
#region Modbus to value
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldConvertToBoolean()
|
||||
{
|
||||
// Arrange
|
||||
var coil = new Coil { HighByte = 0x00 };
|
||||
var discreteInput = new DiscreteInput { HighByte = 0xFF };
|
||||
var holdingRegister = new HoldingRegister { HighByte = 0x01 };
|
||||
var inputRegister = new InputRegister { LowByte = 0x10 };
|
||||
|
||||
// Act
|
||||
bool coilResult = coil.GetBoolean();
|
||||
bool discreteInputResult = discreteInput.GetBoolean();
|
||||
bool holdingRegisterResult = holdingRegister.GetBoolean();
|
||||
bool inputRegisterResult = inputRegister.GetBoolean();
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(coilResult);
|
||||
Assert.IsTrue(discreteInputResult);
|
||||
Assert.IsTrue(holdingRegisterResult);
|
||||
Assert.IsTrue(inputRegisterResult);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowNullOnGetBoolean()
|
||||
{
|
||||
// Arrange
|
||||
Coil coil = null;
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentNullException>(() => coil.GetBoolean());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldConvertToString()
|
||||
{
|
||||
// Arrange
|
||||
var registers = new HoldingRegister[]
|
||||
{
|
||||
new() { Address = 1, HighByte = 65, LowByte = 66 },
|
||||
new() { Address = 2, HighByte = 67, LowByte = 0 },
|
||||
new() { Address = 3, HighByte = 95, LowByte = 96 }
|
||||
};
|
||||
|
||||
// Act
|
||||
string text = registers.GetString(3);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual("ABC", text);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldConvertToStringReversedBytes()
|
||||
{
|
||||
// Arrange
|
||||
var registers = new HoldingRegister[]
|
||||
{
|
||||
new() { Address = 1, HighByte = 66, LowByte = 65 },
|
||||
new() { Address = 2, HighByte = 0, LowByte = 67 }
|
||||
};
|
||||
|
||||
// Act
|
||||
string text = registers.GetString(2, reverseByteOrderPerRegister: true);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual("ABC", text);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldConvertToStringReversedRegisters()
|
||||
{
|
||||
// Arrange
|
||||
var registers = new HoldingRegister[]
|
||||
{
|
||||
new() { Address = 2, HighByte = 65, LowByte = 66 },
|
||||
new() { Address = 1, HighByte = 67, LowByte = 0 },
|
||||
};
|
||||
|
||||
// Act
|
||||
string text = registers.GetString(2, reverseRegisterOrder: true);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual("ABC", text);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowNullOnString()
|
||||
{
|
||||
// Arrange
|
||||
HoldingRegister[] list = null;
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentNullException>(() => list.GetString(2));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowArgumentOnStringForEmptyList()
|
||||
{
|
||||
// Arrange
|
||||
var registers = Array.Empty<HoldingRegister>();
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentException>(() => registers.GetString(2));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(1)]
|
||||
[DataRow(-1)]
|
||||
public void ShouldThrowArgumentOutOfRangeOnString(int startIndex)
|
||||
{
|
||||
// Arrange
|
||||
var registers = new HoldingRegister[]
|
||||
{
|
||||
new() { Address = 1, HighByte = 65, LowByte = 66 },
|
||||
new() { Address = 2, HighByte = 67, LowByte = 0 }
|
||||
};
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => registers.GetString(2, startIndex));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowArgumentOnStringForMixedTypes()
|
||||
{
|
||||
// Arrange
|
||||
var registers = new ModbusObject[]
|
||||
{
|
||||
new HoldingRegister { Address = 1, HighByte = 65, LowByte = 66 },
|
||||
new InputRegister { Address = 2, HighByte = 67, LowByte = 0 }
|
||||
};
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentException>(() => registers.GetString(2));
|
||||
}
|
||||
|
||||
#endregion Modbus to value
|
||||
|
||||
#region Value to Modbus
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetBooleanCoil()
|
||||
{
|
||||
// Arrange
|
||||
bool value = false;
|
||||
|
||||
// Act
|
||||
var coil = value.ToCoil(123);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(coil);
|
||||
Assert.AreEqual(123, coil.Address);
|
||||
Assert.IsFalse(coil.Value);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetBooleanRegisterTrue()
|
||||
{
|
||||
// Arrange
|
||||
bool value = true;
|
||||
|
||||
// Act
|
||||
var register = value.ToRegister(321);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(register);
|
||||
Assert.AreEqual(321, register.Address);
|
||||
Assert.IsTrue(register.Value > 0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetBooleanRegisterFalse()
|
||||
{
|
||||
// Arrange
|
||||
bool value = false;
|
||||
|
||||
// Act
|
||||
var register = value.ToRegister(321);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(register);
|
||||
Assert.AreEqual(321, register.Address);
|
||||
Assert.IsTrue(register.Value == 0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetString()
|
||||
{
|
||||
// Arrange
|
||||
string str = "abc";
|
||||
|
||||
// Act
|
||||
var registers = str.ToRegisters(100).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(registers);
|
||||
Assert.AreEqual(2, registers.Count);
|
||||
|
||||
Assert.AreEqual(100, registers[0].Address);
|
||||
Assert.AreEqual(97, registers[0].HighByte);
|
||||
Assert.AreEqual(98, registers[0].LowByte);
|
||||
|
||||
Assert.AreEqual(101, registers[1].Address);
|
||||
Assert.AreEqual(99, registers[1].HighByte);
|
||||
Assert.AreEqual(0, registers[1].LowByte);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetStringReversedRegisters()
|
||||
{
|
||||
// Arrange
|
||||
string str = "abc";
|
||||
|
||||
// Act
|
||||
var registers = str.ToRegisters(100, reverseRegisterOrder: true).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(registers);
|
||||
Assert.AreEqual(2, registers.Count);
|
||||
|
||||
Assert.AreEqual(101, registers[0].Address);
|
||||
Assert.AreEqual(97, registers[0].HighByte);
|
||||
Assert.AreEqual(98, registers[0].LowByte);
|
||||
|
||||
Assert.AreEqual(100, registers[1].Address);
|
||||
Assert.AreEqual(99, registers[1].HighByte);
|
||||
Assert.AreEqual(0, registers[1].LowByte);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetStringReversedBytes()
|
||||
{
|
||||
// Arrange
|
||||
string str = "abc";
|
||||
|
||||
// Act
|
||||
var registers = str.ToRegisters(100, reverseByteOrderPerRegister: true).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(registers);
|
||||
Assert.AreEqual(2, registers.Count);
|
||||
|
||||
Assert.AreEqual(100, registers[0].Address);
|
||||
Assert.AreEqual(97, registers[0].LowByte);
|
||||
Assert.AreEqual(98, registers[0].HighByte);
|
||||
|
||||
Assert.AreEqual(101, registers[1].Address);
|
||||
Assert.AreEqual(99, registers[1].LowByte);
|
||||
Assert.AreEqual(0, registers[1].HighByte);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowNullOnGetString()
|
||||
{
|
||||
// Arrange
|
||||
string str = null;
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentNullException>(() => str.ToRegisters(100).ToArray());
|
||||
}
|
||||
|
||||
#endregion Value to Modbus
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,428 @@
|
||||
namespace AMWD.Protocols.Modbus.Tests.Common.Extensions
|
||||
{
|
||||
[TestClass]
|
||||
public class ModbusSignedExtensionsTest
|
||||
{
|
||||
#region Modbus to value
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetSByteOnHoldingRegister()
|
||||
{
|
||||
// Arrange
|
||||
var register = new HoldingRegister { Address = 1, HighByte = 0x02, LowByte = 0xFE };
|
||||
|
||||
// Act
|
||||
sbyte sb = register.GetSByte();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(-2, sb);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetSByteOnInputRegister()
|
||||
{
|
||||
// Arrange
|
||||
var register = new InputRegister { Address = 1, HighByte = 0x02, LowByte = 0xFE };
|
||||
|
||||
// Act
|
||||
sbyte sb = register.GetSByte();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(-2, sb);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowNullForGetSByte()
|
||||
{
|
||||
// Arrange
|
||||
HoldingRegister register = null;
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentNullException>(() => register.GetSByte());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowArgumentForGetSByte()
|
||||
{
|
||||
// Arrange
|
||||
var obj = new Coil();
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentException>(() => obj.GetSByte());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetInt16OnHoldingRegister()
|
||||
{
|
||||
// Arrange
|
||||
var register = new HoldingRegister { Address = 1, HighByte = 0x02, LowByte = 0x10 };
|
||||
|
||||
// Act
|
||||
short s = register.GetInt16();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(528, s);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetInt16OnInputRegister()
|
||||
{
|
||||
// Arrange
|
||||
var register = new InputRegister { Address = 1, HighByte = 0x02, LowByte = 0x10 };
|
||||
|
||||
// Act
|
||||
short s = register.GetInt16();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(528, s);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowNullForGetInt16()
|
||||
{
|
||||
// Arrange
|
||||
HoldingRegister register = null;
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentNullException>(() => register.GetInt16());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowArgumentForGetInt16()
|
||||
{
|
||||
// Arrange
|
||||
var obj = new Coil();
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentException>(() => obj.GetInt16());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetInt32()
|
||||
{
|
||||
// Arrange
|
||||
var registers = new HoldingRegister[]
|
||||
{
|
||||
new HoldingRegister(),
|
||||
new HoldingRegister { Address = 100, HighByte = 0x01, LowByte = 0x02 },
|
||||
new HoldingRegister { Address = 101, HighByte = 0x03, LowByte = 0x04 }
|
||||
};
|
||||
|
||||
// Act
|
||||
int i = registers.GetInt32(1);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(16909060, i);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetInt32ReversedRegisters()
|
||||
{
|
||||
// Arrange
|
||||
var registers = new HoldingRegister[]
|
||||
{
|
||||
new HoldingRegister { Address = 101, HighByte = 0x01, LowByte = 0x02 },
|
||||
new HoldingRegister { Address = 100, HighByte = 0x03, LowByte = 0x04 }
|
||||
};
|
||||
|
||||
// Act
|
||||
int i = registers.GetInt32(0, reverseRegisterOrder: true);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(16909060, i);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowNullOnGetInt32()
|
||||
{
|
||||
// Arrange
|
||||
HoldingRegister[] registers = null;
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentNullException>(() => registers.GetInt32(0));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowArgumentOnGetInt32ForLength()
|
||||
{
|
||||
// Arrange
|
||||
var registers = new HoldingRegister[]
|
||||
{
|
||||
new HoldingRegister { Address = 101, HighByte = 0x01, LowByte = 0x02 }
|
||||
};
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentException>(() => registers.GetInt32(0));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(1)]
|
||||
[DataRow(-1)]
|
||||
public void ShouldThrowArgumentOutOfRangeOnGetInt32(int startIndex)
|
||||
{
|
||||
// Arrange
|
||||
var registers = new HoldingRegister[]
|
||||
{
|
||||
new HoldingRegister { Address = 101, HighByte = 0x01, LowByte = 0x02 },
|
||||
new HoldingRegister { Address = 100, HighByte = 0x03, LowByte = 0x04 }
|
||||
};
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => registers.GetInt32(startIndex));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowArgumentOnGetInt32ForType()
|
||||
{
|
||||
// Arrange
|
||||
var registers = new ModbusObject[]
|
||||
{
|
||||
new HoldingRegister { Address = 100, HighByte = 0x01, LowByte = 0x02 },
|
||||
new InputRegister { Address = 101, HighByte = 0x03, LowByte = 0x04 }
|
||||
};
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentException>(() => registers.GetInt32(0));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetInt64()
|
||||
{
|
||||
// Arrange
|
||||
var registers = new HoldingRegister[]
|
||||
{
|
||||
new HoldingRegister(),
|
||||
new HoldingRegister { Address = 100, HighByte = 0x00, LowByte = 0x00 },
|
||||
new HoldingRegister { Address = 101, HighByte = 0x00, LowByte = 0x00 },
|
||||
new HoldingRegister { Address = 102, HighByte = 0x01, LowByte = 0x02 },
|
||||
new HoldingRegister { Address = 103, HighByte = 0x03, LowByte = 0x04 }
|
||||
};
|
||||
|
||||
// Act
|
||||
long l = registers.GetInt64(1);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(16909060L, l);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetInt64ReversedRegisters()
|
||||
{
|
||||
// Arrange
|
||||
var registers = new HoldingRegister[]
|
||||
{
|
||||
new HoldingRegister { Address = 103, HighByte = 0x00, LowByte = 0x00 },
|
||||
new HoldingRegister { Address = 102, HighByte = 0x00, LowByte = 0x00 },
|
||||
new HoldingRegister { Address = 101, HighByte = 0x01, LowByte = 0x02 },
|
||||
new HoldingRegister { Address = 100, HighByte = 0x03, LowByte = 0x04 }
|
||||
};
|
||||
|
||||
// Act
|
||||
long l = registers.GetInt64(0, reverseRegisterOrder: true);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(16909060L, l);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowNullOnGetInt64()
|
||||
{
|
||||
// Arrange
|
||||
HoldingRegister[] registers = null;
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentNullException>(() => registers.GetInt64(0));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowArgumentOnGetInt64ForLength()
|
||||
{
|
||||
// Arrange
|
||||
var registers = new HoldingRegister[]
|
||||
{
|
||||
new HoldingRegister { Address = 101, HighByte = 0x00, LowByte = 0x00 },
|
||||
new HoldingRegister { Address = 102, HighByte = 0x01, LowByte = 0x02 },
|
||||
new HoldingRegister { Address = 103, HighByte = 0x03, LowByte = 0x04 }
|
||||
};
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentException>(() => registers.GetInt64(0));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(1)]
|
||||
[DataRow(-1)]
|
||||
public void ShouldThrowArgumentOutOfRangeOnGetInt64(int startIndex)
|
||||
{
|
||||
// Arrange
|
||||
var registers = new HoldingRegister[]
|
||||
{
|
||||
new HoldingRegister { Address = 100, HighByte = 0x00, LowByte = 0x00 },
|
||||
new HoldingRegister { Address = 101, HighByte = 0x00, LowByte = 0x00 },
|
||||
new HoldingRegister { Address = 102, HighByte = 0x01, LowByte = 0x02 },
|
||||
new HoldingRegister { Address = 103, HighByte = 0x03, LowByte = 0x04 }
|
||||
};
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => registers.GetInt64(startIndex));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowArgumentOnGetInt64ForType()
|
||||
{
|
||||
// Arrange
|
||||
var registers = new ModbusObject[]
|
||||
{
|
||||
new HoldingRegister { Address = 100, HighByte = 0x00, LowByte = 0x00 },
|
||||
new InputRegister { Address = 101, HighByte = 0x00, LowByte = 0x00 },
|
||||
new HoldingRegister { Address = 102, HighByte = 0x01, LowByte = 0x02 },
|
||||
new InputRegister { Address = 103, HighByte = 0x03, LowByte = 0x04 }
|
||||
};
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentException>(() => registers.GetInt64(0));
|
||||
}
|
||||
|
||||
#endregion Modbus to value
|
||||
|
||||
#region Value to Modbus
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldConvertSByte()
|
||||
{
|
||||
// Arrange
|
||||
sbyte sb = -2;
|
||||
|
||||
// Act
|
||||
var register = sb.ToRegister(24);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(register);
|
||||
Assert.AreEqual(24, register.Address);
|
||||
Assert.AreEqual(0x00, register.HighByte);
|
||||
Assert.AreEqual(0xFE, register.LowByte);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldConvertInt16()
|
||||
{
|
||||
// Arrange
|
||||
short s = 1000;
|
||||
|
||||
// Act
|
||||
var register = s.ToRegister(123);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(register);
|
||||
Assert.AreEqual(123, register.Address);
|
||||
Assert.AreEqual(0x03, register.HighByte);
|
||||
Assert.AreEqual(0xE8, register.LowByte);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldConvertInt32()
|
||||
{
|
||||
// Arrange
|
||||
int i = 75000;
|
||||
|
||||
// Act
|
||||
var registers = i.ToRegister(5).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(registers);
|
||||
Assert.AreEqual(2, registers.Count);
|
||||
|
||||
Assert.AreEqual(5, registers[0].Address);
|
||||
Assert.AreEqual(0x00, registers[0].HighByte);
|
||||
Assert.AreEqual(0x01, registers[0].LowByte);
|
||||
|
||||
Assert.AreEqual(6, registers[1].Address);
|
||||
Assert.AreEqual(0x24, registers[1].HighByte);
|
||||
Assert.AreEqual(0xF8, registers[1].LowByte);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldConvertInt32Reversed()
|
||||
{
|
||||
// Arrange
|
||||
int i = 75000;
|
||||
|
||||
// Act
|
||||
var registers = i.ToRegister(5, reverseRegisterOrder: true).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(registers);
|
||||
Assert.AreEqual(2, registers.Count);
|
||||
|
||||
Assert.AreEqual(6, registers[0].Address);
|
||||
Assert.AreEqual(0x00, registers[0].HighByte);
|
||||
Assert.AreEqual(0x01, registers[0].LowByte);
|
||||
|
||||
Assert.AreEqual(5, registers[1].Address);
|
||||
Assert.AreEqual(0x24, registers[1].HighByte);
|
||||
Assert.AreEqual(0xF8, registers[1].LowByte);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldConvertInt64()
|
||||
{
|
||||
// Arrange
|
||||
long l = 75000;
|
||||
|
||||
// Act
|
||||
var registers = l.ToRegister(10).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(registers);
|
||||
Assert.AreEqual(4, registers.Count);
|
||||
|
||||
Assert.AreEqual(10, registers[0].Address);
|
||||
Assert.AreEqual(0x00, registers[0].HighByte);
|
||||
Assert.AreEqual(0x00, registers[0].LowByte);
|
||||
|
||||
Assert.AreEqual(11, registers[1].Address);
|
||||
Assert.AreEqual(0x00, registers[1].HighByte);
|
||||
Assert.AreEqual(0x00, registers[1].LowByte);
|
||||
|
||||
Assert.AreEqual(12, registers[2].Address);
|
||||
Assert.AreEqual(0x00, registers[2].HighByte);
|
||||
Assert.AreEqual(0x01, registers[2].LowByte);
|
||||
|
||||
Assert.AreEqual(13, registers[3].Address);
|
||||
Assert.AreEqual(0x24, registers[3].HighByte);
|
||||
Assert.AreEqual(0xF8, registers[3].LowByte);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldConvertInt64Reversed()
|
||||
{
|
||||
// Arrange
|
||||
long l = 75000;
|
||||
|
||||
// Act
|
||||
var registers = l.ToRegister(10, reverseRegisterOrder: true).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(registers);
|
||||
Assert.AreEqual(4, registers.Count);
|
||||
|
||||
Assert.AreEqual(13, registers[0].Address);
|
||||
Assert.AreEqual(0x00, registers[0].HighByte);
|
||||
Assert.AreEqual(0x00, registers[0].LowByte);
|
||||
|
||||
Assert.AreEqual(12, registers[1].Address);
|
||||
Assert.AreEqual(0x00, registers[1].HighByte);
|
||||
Assert.AreEqual(0x00, registers[1].LowByte);
|
||||
|
||||
Assert.AreEqual(11, registers[2].Address);
|
||||
Assert.AreEqual(0x00, registers[2].HighByte);
|
||||
Assert.AreEqual(0x01, registers[2].LowByte);
|
||||
|
||||
Assert.AreEqual(10, registers[3].Address);
|
||||
Assert.AreEqual(0x24, registers[3].HighByte);
|
||||
Assert.AreEqual(0xF8, registers[3].LowByte);
|
||||
}
|
||||
|
||||
#endregion Value to Modbus
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,428 @@
|
||||
namespace AMWD.Protocols.Modbus.Tests.Common.Extensions
|
||||
{
|
||||
[TestClass]
|
||||
public class ModbusUnsignedExtensionsTest
|
||||
{
|
||||
#region Modbus to value
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetByteOnHoldingRegister()
|
||||
{
|
||||
// Arrange
|
||||
var register = new HoldingRegister { Address = 1, HighByte = 0x02, LowByte = 0x10 };
|
||||
|
||||
// Act
|
||||
byte b = register.GetByte();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(16, b);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetByteOnInputRegister()
|
||||
{
|
||||
// Arrange
|
||||
var register = new InputRegister { Address = 1, HighByte = 0x02, LowByte = 0x10 };
|
||||
|
||||
// Act
|
||||
byte b = register.GetByte();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(16, b);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowNullForGetByte()
|
||||
{
|
||||
// Arrange
|
||||
HoldingRegister register = null;
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentNullException>(() => register.GetByte());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowArgumentForGetByte()
|
||||
{
|
||||
// Arrange
|
||||
var obj = new Coil();
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentException>(() => obj.GetByte());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetUInt16OnHoldingRegister()
|
||||
{
|
||||
// Arrange
|
||||
var register = new HoldingRegister { Address = 1, HighByte = 0x02, LowByte = 0x10 };
|
||||
|
||||
// Act
|
||||
ushort us = register.GetUInt16();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(528, us);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetUInt16OnInputRegister()
|
||||
{
|
||||
// Arrange
|
||||
var register = new InputRegister { Address = 1, HighByte = 0x02, LowByte = 0x10 };
|
||||
|
||||
// Act
|
||||
ushort us = register.GetUInt16();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(528, us);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowNullForGetUInt16()
|
||||
{
|
||||
// Arrange
|
||||
HoldingRegister register = null;
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentNullException>(() => register.GetUInt16());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowArgumentForGetUInt16()
|
||||
{
|
||||
// Arrange
|
||||
var obj = new Coil();
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentException>(() => obj.GetUInt16());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetUInt32()
|
||||
{
|
||||
// Arrange
|
||||
var registers = new HoldingRegister[]
|
||||
{
|
||||
new(),
|
||||
new() { Address = 100, HighByte = 0x01, LowByte = 0x02 },
|
||||
new() { Address = 101, HighByte = 0x03, LowByte = 0x04 }
|
||||
};
|
||||
|
||||
// Act
|
||||
uint ui = registers.GetUInt32(1);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(16909060u, ui);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetUInt32ReversedRegisters()
|
||||
{
|
||||
// Arrange
|
||||
var registers = new HoldingRegister[]
|
||||
{
|
||||
new() { Address = 101, HighByte = 0x01, LowByte = 0x02 },
|
||||
new() { Address = 100, HighByte = 0x03, LowByte = 0x04 }
|
||||
};
|
||||
|
||||
// Act
|
||||
uint ui = registers.GetUInt32(0, reverseRegisterOrder: true);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(16909060u, ui);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowNullOnGetUInt32()
|
||||
{
|
||||
// Arrange
|
||||
HoldingRegister[] registers = null;
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentNullException>(() => registers.GetUInt32(0));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowArgumentOnGetUInt32ForLength()
|
||||
{
|
||||
// Arrange
|
||||
var registers = new HoldingRegister[]
|
||||
{
|
||||
new() { Address = 101, HighByte = 0x01, LowByte = 0x02 }
|
||||
};
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentException>(() => registers.GetUInt32(1));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(1)]
|
||||
[DataRow(-1)]
|
||||
public void ShouldThrowArgumentOutOfRangeOnGetUInt32(int startIndex)
|
||||
{
|
||||
// Arrange
|
||||
var registers = new HoldingRegister[]
|
||||
{
|
||||
new() { Address = 101, HighByte = 0x01, LowByte = 0x02 },
|
||||
new() { Address = 100, HighByte = 0x03, LowByte = 0x04 }
|
||||
};
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => registers.GetUInt32(startIndex));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowArgumentOnGetUInt32ForType()
|
||||
{
|
||||
// Arrange
|
||||
var registers = new ModbusObject[]
|
||||
{
|
||||
new HoldingRegister { Address = 100, HighByte = 0x01, LowByte = 0x02 },
|
||||
new InputRegister { Address = 101, HighByte = 0x03, LowByte = 0x04 }
|
||||
};
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentException>(() => registers.GetUInt32(0));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetUInt64()
|
||||
{
|
||||
// Arrange
|
||||
var registers = new HoldingRegister[]
|
||||
{
|
||||
new(),
|
||||
new() { Address = 100, HighByte = 0x00, LowByte = 0x00 },
|
||||
new() { Address = 101, HighByte = 0x00, LowByte = 0x00 },
|
||||
new() { Address = 102, HighByte = 0x01, LowByte = 0x02 },
|
||||
new() { Address = 103, HighByte = 0x03, LowByte = 0x04 }
|
||||
};
|
||||
|
||||
// Act
|
||||
ulong ul = registers.GetUInt64(1);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(16909060ul, ul);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetUInt64ReversedRegisters()
|
||||
{
|
||||
// Arrange
|
||||
var registers = new HoldingRegister[]
|
||||
{
|
||||
new() { Address = 103, HighByte = 0x00, LowByte = 0x00 },
|
||||
new() { Address = 102, HighByte = 0x00, LowByte = 0x00 },
|
||||
new() { Address = 101, HighByte = 0x01, LowByte = 0x02 },
|
||||
new() { Address = 100, HighByte = 0x03, LowByte = 0x04 }
|
||||
};
|
||||
|
||||
// Act
|
||||
ulong ul = registers.GetUInt64(0, reverseRegisterOrder: true);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(16909060ul, ul);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowNullOnGetUInt64()
|
||||
{
|
||||
// Arrange
|
||||
HoldingRegister[] registers = null;
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentNullException>(() => registers.GetUInt64(0));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowArgumentOnGetUInt64ForLength()
|
||||
{
|
||||
// Arrange
|
||||
var registers = new HoldingRegister[]
|
||||
{
|
||||
new() { Address = 101, HighByte = 0x00, LowByte = 0x00 },
|
||||
new() { Address = 102, HighByte = 0x01, LowByte = 0x02 },
|
||||
new() { Address = 103, HighByte = 0x03, LowByte = 0x04 }
|
||||
};
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentException>(() => registers.GetUInt64(0));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(1)]
|
||||
[DataRow(-1)]
|
||||
public void ShouldThrowArgumentOutOfRangeOnGetUInt64(int startIndex)
|
||||
{
|
||||
// Arrange
|
||||
var registers = new HoldingRegister[]
|
||||
{
|
||||
new() { Address = 100, HighByte = 0x00, LowByte = 0x00 },
|
||||
new() { Address = 101, HighByte = 0x00, LowByte = 0x00 },
|
||||
new() { Address = 102, HighByte = 0x01, LowByte = 0x02 },
|
||||
new() { Address = 103, HighByte = 0x03, LowByte = 0x04 }
|
||||
};
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => registers.GetUInt64(startIndex));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldThrowArgumentOnGetUInt64ForType()
|
||||
{
|
||||
// Arrange
|
||||
var registers = new ModbusObject[]
|
||||
{
|
||||
new HoldingRegister { Address = 100, HighByte = 0x00, LowByte = 0x00 },
|
||||
new InputRegister { Address = 101, HighByte = 0x00, LowByte = 0x00 },
|
||||
new HoldingRegister { Address = 102, HighByte = 0x01, LowByte = 0x02 },
|
||||
new InputRegister { Address = 103, HighByte = 0x03, LowByte = 0x04 }
|
||||
};
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentException>(() => registers.GetUInt64(0));
|
||||
}
|
||||
|
||||
#endregion Modbus to value
|
||||
|
||||
#region Value to Modbus
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldConvertByte()
|
||||
{
|
||||
// Arrange
|
||||
byte b = 123;
|
||||
|
||||
// Act
|
||||
var register = b.ToRegister(321);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(register);
|
||||
Assert.AreEqual(321, register.Address);
|
||||
Assert.AreEqual(0, register.HighByte);
|
||||
Assert.AreEqual(123, register.LowByte);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldConvertUInt16()
|
||||
{
|
||||
// Arrange
|
||||
ushort us = 1000;
|
||||
|
||||
// Act
|
||||
var register = us.ToRegister(123);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(register);
|
||||
Assert.AreEqual(123, register.Address);
|
||||
Assert.AreEqual(0x03, register.HighByte);
|
||||
Assert.AreEqual(0xE8, register.LowByte);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldConvertUInt32()
|
||||
{
|
||||
// Arrange
|
||||
uint ui = 75000;
|
||||
|
||||
// Act
|
||||
var registers = ui.ToRegister(5).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(registers);
|
||||
Assert.AreEqual(2, registers.Count);
|
||||
|
||||
Assert.AreEqual(5, registers[0].Address);
|
||||
Assert.AreEqual(0x00, registers[0].HighByte);
|
||||
Assert.AreEqual(0x01, registers[0].LowByte);
|
||||
|
||||
Assert.AreEqual(6, registers[1].Address);
|
||||
Assert.AreEqual(0x24, registers[1].HighByte);
|
||||
Assert.AreEqual(0xF8, registers[1].LowByte);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldConvertUInt32Reversed()
|
||||
{
|
||||
// Arrange
|
||||
uint ui = 75000;
|
||||
|
||||
// Act
|
||||
var registers = ui.ToRegister(5, reverseRegisterOrder: true).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(registers);
|
||||
Assert.AreEqual(2, registers.Count);
|
||||
|
||||
Assert.AreEqual(6, registers[0].Address);
|
||||
Assert.AreEqual(0x00, registers[0].HighByte);
|
||||
Assert.AreEqual(0x01, registers[0].LowByte);
|
||||
|
||||
Assert.AreEqual(5, registers[1].Address);
|
||||
Assert.AreEqual(0x24, registers[1].HighByte);
|
||||
Assert.AreEqual(0xF8, registers[1].LowByte);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldConvertUInt64()
|
||||
{
|
||||
// Arrange
|
||||
ulong ul = 75000;
|
||||
|
||||
// Act
|
||||
var registers = ul.ToRegister(10).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(registers);
|
||||
Assert.AreEqual(4, registers.Count);
|
||||
|
||||
Assert.AreEqual(10, registers[0].Address);
|
||||
Assert.AreEqual(0x00, registers[0].HighByte);
|
||||
Assert.AreEqual(0x00, registers[0].LowByte);
|
||||
|
||||
Assert.AreEqual(11, registers[1].Address);
|
||||
Assert.AreEqual(0x00, registers[1].HighByte);
|
||||
Assert.AreEqual(0x00, registers[1].LowByte);
|
||||
|
||||
Assert.AreEqual(12, registers[2].Address);
|
||||
Assert.AreEqual(0x00, registers[2].HighByte);
|
||||
Assert.AreEqual(0x01, registers[2].LowByte);
|
||||
|
||||
Assert.AreEqual(13, registers[3].Address);
|
||||
Assert.AreEqual(0x24, registers[3].HighByte);
|
||||
Assert.AreEqual(0xF8, registers[3].LowByte);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldConvertUInt64Reversed()
|
||||
{
|
||||
// Arrange
|
||||
ulong ul = 75000;
|
||||
|
||||
// Act
|
||||
var registers = ul.ToRegister(10, reverseRegisterOrder: true).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(registers);
|
||||
Assert.AreEqual(4, registers.Count);
|
||||
|
||||
Assert.AreEqual(13, registers[0].Address);
|
||||
Assert.AreEqual(0x00, registers[0].HighByte);
|
||||
Assert.AreEqual(0x00, registers[0].LowByte);
|
||||
|
||||
Assert.AreEqual(12, registers[1].Address);
|
||||
Assert.AreEqual(0x00, registers[1].HighByte);
|
||||
Assert.AreEqual(0x00, registers[1].LowByte);
|
||||
|
||||
Assert.AreEqual(11, registers[2].Address);
|
||||
Assert.AreEqual(0x00, registers[2].HighByte);
|
||||
Assert.AreEqual(0x01, registers[2].LowByte);
|
||||
|
||||
Assert.AreEqual(10, registers[3].Address);
|
||||
Assert.AreEqual(0x24, registers[3].HighByte);
|
||||
Assert.AreEqual(0xF8, registers[3].LowByte);
|
||||
}
|
||||
|
||||
#endregion Value to Modbus
|
||||
}
|
||||
}
|
||||
108
test/AMWD.Protocols.Modbus.Tests/Common/Models/CoilTest.cs
Normal file
108
test/AMWD.Protocols.Modbus.Tests/Common/Models/CoilTest.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
namespace AMWD.Protocols.Modbus.Tests.Common.Models
|
||||
{
|
||||
[TestClass]
|
||||
public class CoilTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void ShouldSuccessfulCompare()
|
||||
{
|
||||
// Arrange
|
||||
var coil1 = new Coil { Address = 123, Value = true };
|
||||
var coil2 = new Coil { Address = 123, Value = true };
|
||||
|
||||
// Act
|
||||
bool success = coil1.Equals(coil2);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldFailOnInstanceComparing()
|
||||
{
|
||||
// Arrange
|
||||
var coil1 = new Coil { Address = 123, Value = true };
|
||||
var coil2 = new { Address = 123, HighByte = 0xFF };
|
||||
|
||||
// Act
|
||||
bool success = coil1.Equals(coil2);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldFailOnTypeComparing()
|
||||
{
|
||||
// Arrange
|
||||
var coil1 = new Coil { Address = 123, Value = true };
|
||||
var coil2 = new DiscreteInput { Address = 123, HighByte = 0xFF };
|
||||
|
||||
// Act
|
||||
bool success = coil1.Equals(coil2);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldFailOnAddressComparing()
|
||||
{
|
||||
// Arrange
|
||||
var coil1 = new Coil { Address = 123, Value = true };
|
||||
var coil2 = new Coil { Address = 321, HighByte = 0xFF };
|
||||
|
||||
// Act
|
||||
bool success = coil1.Equals(coil2);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldFailOnHighByteComparing()
|
||||
{
|
||||
// Arrange
|
||||
var coil1 = new Coil { Address = 123, Value = true };
|
||||
var coil2 = new Coil { Address = 123, HighByte = 0x00 };
|
||||
|
||||
// Act
|
||||
bool success = coil1.Equals(coil2);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldFailOnLowByteComparing()
|
||||
{
|
||||
// Arrange
|
||||
var coil1 = new Coil { Address = 123, Value = true };
|
||||
var coil2 = new Coil { Address = 123, HighByte = 0xFF, LowByte = 0xFF };
|
||||
|
||||
// Act
|
||||
bool success = coil1.Equals(coil2);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(0xFF)]
|
||||
[DataRow(0x00)]
|
||||
public void ShouldPrintPrettyString(int highByte)
|
||||
{
|
||||
// Arrange
|
||||
var coil = new Coil { Address = 123, HighByte = (byte)highByte, LowByte = 0x00 };
|
||||
|
||||
// Act
|
||||
string str = coil.ToString();
|
||||
|
||||
// Assert
|
||||
if (highByte > 0)
|
||||
Assert.AreEqual("Coil #123 | ON", str);
|
||||
else
|
||||
Assert.AreEqual("Coil #123 | OFF", str);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
namespace AMWD.Protocols.Modbus.Tests.Common.Models
|
||||
{
|
||||
[TestClass]
|
||||
public class DiscreteInputTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void ShouldSuccessfulCompare()
|
||||
{
|
||||
// Arrange
|
||||
var input1 = new DiscreteInput { Address = 123, HighByte = 0xFF, LowByte = 0x00 };
|
||||
var input2 = new DiscreteInput { Address = 123, HighByte = 0xFF, LowByte = 0x00 };
|
||||
|
||||
// Act
|
||||
bool success = input1.Equals(input2);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldFailOnInstanceComparing()
|
||||
{
|
||||
// Arrange
|
||||
var coil1 = new Coil { Address = 123, Value = true };
|
||||
var coil2 = new { Address = 123, HighByte = 0xFF };
|
||||
|
||||
// Act
|
||||
bool success = coil1.Equals(coil2);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldFailOnTypeComparing()
|
||||
{
|
||||
// Arrange
|
||||
var input1 = new DiscreteInput { Address = 123, HighByte = 0xFF, LowByte = 0x00 };
|
||||
var input2 = new Coil { Address = 123, HighByte = 0xFF, LowByte = 0x00 };
|
||||
|
||||
// Act
|
||||
bool success = input1.Equals(input2);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldFailOnAddressComparing()
|
||||
{
|
||||
// Arrange
|
||||
var input1 = new DiscreteInput { Address = 123, HighByte = 0xFF, LowByte = 0x00 };
|
||||
var input2 = new DiscreteInput { Address = 321, HighByte = 0xFF, LowByte = 0x00 };
|
||||
|
||||
// Act
|
||||
bool success = input1.Equals(input2);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldFailOnHighByteComparing()
|
||||
{
|
||||
// Arrange
|
||||
var input1 = new DiscreteInput { Address = 123, HighByte = 0xFF, LowByte = 0x00 };
|
||||
var input2 = new DiscreteInput { Address = 123, HighByte = 0x00, LowByte = 0x00 };
|
||||
|
||||
// Act
|
||||
bool success = input1.Equals(input2);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldFailOnLowByteComparing()
|
||||
{
|
||||
// Arrange
|
||||
var input1 = new DiscreteInput { Address = 123, HighByte = 0xFF, LowByte = 0x00 };
|
||||
var input2 = new DiscreteInput { Address = 123, HighByte = 0xFF, LowByte = 0xFF };
|
||||
|
||||
// Act
|
||||
bool success = input1.Equals(input2);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(0xFF)]
|
||||
[DataRow(0x00)]
|
||||
public void ShouldPrintPrettyString(int highByte)
|
||||
{
|
||||
// Arrange
|
||||
var input = new DiscreteInput { Address = 123, HighByte = (byte)highByte, LowByte = 0x00 };
|
||||
|
||||
// Act
|
||||
string str = input.ToString();
|
||||
|
||||
// Assert
|
||||
if (highByte > 0)
|
||||
Assert.AreEqual("Discrete Input #123 | ON", str);
|
||||
else
|
||||
Assert.AreEqual("Discrete Input #123 | OFF", str);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
namespace AMWD.Protocols.Modbus.Tests.Common.Models
|
||||
{
|
||||
[TestClass]
|
||||
public class HoldingRegisterTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void ShouldSuccessfulCompare()
|
||||
{
|
||||
// Arrange
|
||||
var register1 = new HoldingRegister { Address = 123, HighByte = 0xBE, LowByte = 0xEF };
|
||||
var register2 = new HoldingRegister { Address = 123, HighByte = 0xBE, LowByte = 0xEF };
|
||||
|
||||
// Act
|
||||
bool success = register1.Equals(register2);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldFailOnInstanceComparing()
|
||||
{
|
||||
// Arrange
|
||||
var register1 = new HoldingRegister { Address = 123, HighByte = 0xBE, LowByte = 0xEF };
|
||||
var register2 = new { Address = 123, HighByte = 0xBE, LowByte = 0xEF };
|
||||
|
||||
// Act
|
||||
bool success = register1.Equals(register2);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldFailOnTypeComparing()
|
||||
{
|
||||
// Arrange
|
||||
var register1 = new HoldingRegister { Address = 123, HighByte = 0xBE, LowByte = 0xEF };
|
||||
var register2 = new InputRegister { Address = 123, HighByte = 0xBE, LowByte = 0xEF };
|
||||
|
||||
// Act
|
||||
bool success = register1.Equals(register2);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldFailOnAddressComparing()
|
||||
{
|
||||
// Arrange
|
||||
var register1 = new HoldingRegister { Address = 123, HighByte = 0xBE, LowByte = 0xEF };
|
||||
var register2 = new HoldingRegister { Address = 321, HighByte = 0xBE, LowByte = 0xEF };
|
||||
|
||||
// Act
|
||||
bool success = register1.Equals(register2);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldFailOnHighByteComparing()
|
||||
{
|
||||
// Arrange
|
||||
var register1 = new HoldingRegister { Address = 123, HighByte = 0xBE, LowByte = 0xEF };
|
||||
var register2 = new HoldingRegister { Address = 123, HighByte = 0xBD, LowByte = 0xEF };
|
||||
|
||||
// Act
|
||||
bool success = register1.Equals(register2);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldFailOnLowByteComparing()
|
||||
{
|
||||
// Arrange
|
||||
var register1 = new HoldingRegister { Address = 123, HighByte = 0xBE, LowByte = 0xEF };
|
||||
var register2 = new HoldingRegister { Address = 123, HighByte = 0xBE, LowByte = 0xEE };
|
||||
|
||||
// Act
|
||||
bool success = register1.Equals(register2);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldPrintPrettyString()
|
||||
{
|
||||
// Arrange
|
||||
var register = new HoldingRegister { Address = 123, HighByte = 0xBE, LowByte = 0xEF };
|
||||
|
||||
// Act
|
||||
string str = register.ToString();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual("Holding Register #123 | 48879 | HI: BE, LO: EF", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldSetByValue()
|
||||
{
|
||||
// Arrange
|
||||
var register = new HoldingRegister { Address = 123 };
|
||||
|
||||
// Act
|
||||
register.Value = 48879;
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(0xBE, register.HighByte);
|
||||
Assert.AreEqual(0xEF, register.LowByte);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
namespace AMWD.Protocols.Modbus.Tests.Common.Models
|
||||
{
|
||||
[TestClass]
|
||||
public class InputRegisterTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void ShouldSuccessfulCompare()
|
||||
{
|
||||
// Arrange
|
||||
var register1 = new InputRegister { Address = 123, HighByte = 0xBE, LowByte = 0xEF };
|
||||
var register2 = new InputRegister { Address = 123, HighByte = 0xBE, LowByte = 0xEF };
|
||||
|
||||
// Act
|
||||
bool success = register1.Equals(register2);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldFailOnInstanceComparing()
|
||||
{
|
||||
// Arrange
|
||||
var register1 = new InputRegister { Address = 123, HighByte = 0xBE, LowByte = 0xEF };
|
||||
var register2 = new { Address = 123, HighByte = 0xBE, LowByte = 0xEF };
|
||||
|
||||
// Act
|
||||
bool success = register1.Equals(register2);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldFailOnTypeComparing()
|
||||
{
|
||||
// Arrange
|
||||
var register1 = new InputRegister { Address = 123, HighByte = 0xBE, LowByte = 0xEF };
|
||||
var register2 = new HoldingRegister { Address = 123, HighByte = 0xBE, LowByte = 0xEF };
|
||||
|
||||
// Act
|
||||
bool success = register1.Equals(register2);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldFailOnAddressComparing()
|
||||
{
|
||||
// Arrange
|
||||
var register1 = new InputRegister { Address = 123, HighByte = 0xBE, LowByte = 0xEF };
|
||||
var register2 = new InputRegister { Address = 321, HighByte = 0xBE, LowByte = 0xEF };
|
||||
|
||||
// Act
|
||||
bool success = register1.Equals(register2);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldFailOnHighByteComparing()
|
||||
{
|
||||
// Arrange
|
||||
var register1 = new InputRegister { Address = 123, HighByte = 0xBE, LowByte = 0xEF };
|
||||
var register2 = new InputRegister { Address = 123, HighByte = 0xBD, LowByte = 0xEF };
|
||||
|
||||
// Act
|
||||
bool success = register1.Equals(register2);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldFailOnLowByteComparing()
|
||||
{
|
||||
// Arrange
|
||||
var register1 = new InputRegister { Address = 123, HighByte = 0xBE, LowByte = 0xEF };
|
||||
var register2 = new InputRegister { Address = 123, HighByte = 0xBE, LowByte = 0xEE };
|
||||
|
||||
// Act
|
||||
bool success = register1.Equals(register2);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldPrintPrettyString()
|
||||
{
|
||||
// Arrange
|
||||
var register = new InputRegister { Address = 123, HighByte = 0xBE, LowByte = 0xEF };
|
||||
|
||||
// Act
|
||||
string str = register.ToString();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual("Input Register #123 | 48879 | HI: BE, LO: EF", str);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
using AMWD.Protocols.Modbus.Common.Models;
|
||||
using System.Reflection;
|
||||
|
||||
namespace AMWD.Protocols.Modbus.Tests.Common.Models
|
||||
{
|
||||
[TestClass]
|
||||
public class ModbusDeviceTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void ShouldAllowMultipleDispose()
|
||||
{
|
||||
// Arrange
|
||||
var device = new ModbusDevice(123);
|
||||
|
||||
// Act
|
||||
device.Dispose();
|
||||
device.Dispose();
|
||||
|
||||
// Assert - no exception
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldAssertDisposed()
|
||||
{
|
||||
// Arrange
|
||||
var device = new ModbusDevice(123);
|
||||
device.Dispose();
|
||||
|
||||
// Act
|
||||
try
|
||||
{
|
||||
device.GetCoil(111);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{ }
|
||||
|
||||
try
|
||||
{
|
||||
device.SetCoil(new Coil { Address = 222 });
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{ }
|
||||
|
||||
try
|
||||
{
|
||||
device.GetDiscreteInput(111);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{ }
|
||||
|
||||
try
|
||||
{
|
||||
device.SetDiscreteInput(new DiscreteInput { Address = 222 });
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{ }
|
||||
|
||||
try
|
||||
{
|
||||
device.GetHoldingRegister(111);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{ }
|
||||
|
||||
try
|
||||
{
|
||||
device.SetHoldingRegister(new HoldingRegister { Address = 222 });
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{ }
|
||||
|
||||
try
|
||||
{
|
||||
device.GetInputRegister(111);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{ }
|
||||
|
||||
try
|
||||
{
|
||||
device.SetInputRegister(new InputRegister { Address = 222 });
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{ }
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetCoil()
|
||||
{
|
||||
// Arrange
|
||||
var device = new ModbusDevice(123);
|
||||
((HashSet<ushort>)device.GetType()
|
||||
.GetField("_coils", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.GetValue(device))
|
||||
.Add(333);
|
||||
|
||||
// Act
|
||||
var coilFalse = device.GetCoil(111);
|
||||
var coilTrue = device.GetCoil(333);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(111, coilFalse.Address);
|
||||
Assert.IsFalse(coilFalse.Value);
|
||||
|
||||
Assert.AreEqual(333, coilTrue.Address);
|
||||
Assert.IsTrue(coilTrue.Value);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldSetCoil()
|
||||
{
|
||||
// Arrange
|
||||
var device = new ModbusDevice(123);
|
||||
((HashSet<ushort>)device.GetType()
|
||||
.GetField("_coils", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.GetValue(device))
|
||||
.Add(333);
|
||||
|
||||
// Act
|
||||
device.SetCoil(new Coil { Address = 111, Value = true });
|
||||
device.SetCoil(new Coil { Address = 333, Value = false });
|
||||
|
||||
// Assert
|
||||
ushort[] coils = ((HashSet<ushort>)device.GetType()
|
||||
.GetField("_coils", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.GetValue(device)).ToArray();
|
||||
|
||||
Assert.AreEqual(1, coils.Length);
|
||||
Assert.AreEqual(111, coils.First());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetDiscreteInput()
|
||||
{
|
||||
// Arrange
|
||||
var device = new ModbusDevice(123);
|
||||
((HashSet<ushort>)device.GetType()
|
||||
.GetField("_discreteInputs", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.GetValue(device))
|
||||
.Add(333);
|
||||
|
||||
// Act
|
||||
var inputFalse = device.GetDiscreteInput(111);
|
||||
var inputTrue = device.GetDiscreteInput(333);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(111, inputFalse.Address);
|
||||
Assert.IsFalse(inputFalse.Value);
|
||||
|
||||
Assert.AreEqual(333, inputTrue.Address);
|
||||
Assert.IsTrue(inputTrue.Value);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldSetDiscreteInput()
|
||||
{
|
||||
// Arrange
|
||||
var device = new ModbusDevice(123);
|
||||
((HashSet<ushort>)device.GetType()
|
||||
.GetField("_discreteInputs", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.GetValue(device))
|
||||
.Add(333);
|
||||
|
||||
// Act
|
||||
device.SetDiscreteInput(new DiscreteInput { Address = 111, HighByte = 0xFF });
|
||||
device.SetDiscreteInput(new DiscreteInput { Address = 333, HighByte = 0x00 });
|
||||
|
||||
// Assert
|
||||
ushort[] discreteInputs = ((HashSet<ushort>)device.GetType()
|
||||
.GetField("_discreteInputs", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.GetValue(device)).ToArray();
|
||||
|
||||
Assert.AreEqual(1, discreteInputs.Length);
|
||||
Assert.AreEqual(111, discreteInputs.First());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetHoldingRegister()
|
||||
{
|
||||
// Arrange
|
||||
var device = new ModbusDevice(123);
|
||||
((Dictionary<ushort, ushort>)device.GetType()
|
||||
.GetField("_holdingRegisters", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.GetValue(device))
|
||||
.Add(333, 42);
|
||||
|
||||
// Act
|
||||
var zeroRegister = device.GetHoldingRegister(111);
|
||||
var valueRegister = device.GetHoldingRegister(333);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(111, zeroRegister.Address);
|
||||
Assert.AreEqual(0, zeroRegister.Value);
|
||||
Assert.AreEqual(0x00, zeroRegister.HighByte);
|
||||
Assert.AreEqual(0x00, zeroRegister.LowByte);
|
||||
|
||||
Assert.AreEqual(333, valueRegister.Address);
|
||||
Assert.AreEqual(42, valueRegister.Value);
|
||||
Assert.AreEqual(0x00, valueRegister.HighByte);
|
||||
Assert.AreEqual(0x2A, valueRegister.LowByte);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldSetHoldingRegister()
|
||||
{
|
||||
// Arrange
|
||||
var device = new ModbusDevice(123);
|
||||
((Dictionary<ushort, ushort>)device.GetType()
|
||||
.GetField("_holdingRegisters", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.GetValue(device))
|
||||
.Add(333, 42);
|
||||
|
||||
// Act
|
||||
device.SetHoldingRegister(new HoldingRegister { Address = 333, Value = 0 });
|
||||
device.SetHoldingRegister(new HoldingRegister { Address = 111, Value = 42 });
|
||||
|
||||
// Assert
|
||||
var registers = ((Dictionary<ushort, ushort>)device.GetType()
|
||||
.GetField("_holdingRegisters", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.GetValue(device))
|
||||
.ToDictionary(x => x.Key, x => x.Value);
|
||||
|
||||
Assert.AreEqual(1, registers.Count);
|
||||
Assert.AreEqual(111, registers.First().Key);
|
||||
Assert.AreEqual(42, registers.First().Value);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetInputRegister()
|
||||
{
|
||||
// Arrange
|
||||
var device = new ModbusDevice(123);
|
||||
((Dictionary<ushort, ushort>)device.GetType()
|
||||
.GetField("_inputRegisters", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.GetValue(device))
|
||||
.Add(333, 42);
|
||||
|
||||
// Act
|
||||
var zeroRegister = device.GetInputRegister(111);
|
||||
var valueRegister = device.GetInputRegister(333);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(111, zeroRegister.Address);
|
||||
Assert.AreEqual(0, zeroRegister.Value);
|
||||
Assert.AreEqual(0x00, zeroRegister.HighByte);
|
||||
Assert.AreEqual(0x00, zeroRegister.LowByte);
|
||||
|
||||
Assert.AreEqual(333, valueRegister.Address);
|
||||
Assert.AreEqual(42, valueRegister.Value);
|
||||
Assert.AreEqual(0x00, valueRegister.HighByte);
|
||||
Assert.AreEqual(0x2A, valueRegister.LowByte);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldSetInputRegister()
|
||||
{
|
||||
// Arrange
|
||||
var device = new ModbusDevice(123);
|
||||
((Dictionary<ushort, ushort>)device.GetType()
|
||||
.GetField("_inputRegisters", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.GetValue(device))
|
||||
.Add(333, 42);
|
||||
|
||||
// Act
|
||||
device.SetInputRegister(new InputRegister { Address = 333, LowByte = 0 });
|
||||
device.SetInputRegister(new InputRegister { Address = 111, LowByte = 42 });
|
||||
|
||||
// Assert
|
||||
var registers = ((Dictionary<ushort, ushort>)device.GetType()
|
||||
.GetField("_inputRegisters", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.GetValue(device))
|
||||
.ToDictionary(x => x.Key, x => x.Value);
|
||||
|
||||
Assert.AreEqual(1, registers.Count);
|
||||
Assert.AreEqual(111, registers.First().Key);
|
||||
Assert.AreEqual(42, registers.First().Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1196
test/AMWD.Protocols.Modbus.Tests/Common/Protocols/RtuProtocolTest.cs
Normal file
1196
test/AMWD.Protocols.Modbus.Tests/Common/Protocols/RtuProtocolTest.cs
Normal file
File diff suppressed because it is too large
Load Diff
1133
test/AMWD.Protocols.Modbus.Tests/Common/Protocols/TcpProtocolTest.cs
Normal file
1133
test/AMWD.Protocols.Modbus.Tests/Common/Protocols/TcpProtocolTest.cs
Normal file
File diff suppressed because it is too large
Load Diff
23
test/AMWD.Protocols.Modbus.Tests/Helper.cs
Normal file
23
test/AMWD.Protocols.Modbus.Tests/Helper.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System.Reflection;
|
||||
|
||||
namespace AMWD.Protocols.Modbus.Tests
|
||||
{
|
||||
internal static class Helper
|
||||
{
|
||||
public static T CreateInstance<T>(params object[] args)
|
||||
{
|
||||
var type = typeof(T);
|
||||
|
||||
object instance = type.Assembly.CreateInstance(
|
||||
typeName: type.FullName,
|
||||
ignoreCase: false,
|
||||
bindingAttr: BindingFlags.Instance | BindingFlags.NonPublic,
|
||||
binder: null,
|
||||
args: args,
|
||||
culture: null,
|
||||
activationAttributes: null);
|
||||
|
||||
return (T)instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
2003
test/AMWD.Protocols.Modbus.Tests/Serial/ModbusRtuProxyTest.cs
Normal file
2003
test/AMWD.Protocols.Modbus.Tests/Serial/ModbusRtuProxyTest.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,250 @@
|
||||
using System.IO.Ports;
|
||||
using AMWD.Protocols.Modbus.Common.Contracts;
|
||||
using AMWD.Protocols.Modbus.Serial;
|
||||
|
||||
namespace AMWD.Protocols.Modbus.Tests.Serial
|
||||
{
|
||||
[TestClass]
|
||||
public class ModbusSerialClientTest
|
||||
{
|
||||
private Mock<IModbusConnection> _genericConnectionMock;
|
||||
private Mock<ModbusSerialConnection> _serialConnectionMock;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
string portName = "COM-42";
|
||||
|
||||
_genericConnectionMock = new Mock<IModbusConnection>();
|
||||
_genericConnectionMock.Setup(c => c.IdleTimeout).Returns(TimeSpan.FromSeconds(40));
|
||||
_genericConnectionMock.Setup(c => c.ConnectTimeout).Returns(TimeSpan.FromSeconds(30));
|
||||
_genericConnectionMock.Setup(c => c.ReadTimeout).Returns(TimeSpan.FromSeconds(20));
|
||||
_genericConnectionMock.Setup(c => c.WriteTimeout).Returns(TimeSpan.FromSeconds(10));
|
||||
|
||||
_serialConnectionMock = new Mock<ModbusSerialConnection>(portName);
|
||||
|
||||
_serialConnectionMock.Setup(c => c.IdleTimeout).Returns(TimeSpan.FromSeconds(10));
|
||||
_serialConnectionMock.Setup(c => c.ConnectTimeout).Returns(TimeSpan.FromSeconds(20));
|
||||
_serialConnectionMock.Setup(c => c.ReadTimeout).Returns(TimeSpan.FromSeconds(30));
|
||||
_serialConnectionMock.Setup(c => c.WriteTimeout).Returns(TimeSpan.FromSeconds(40));
|
||||
|
||||
_serialConnectionMock.Setup(c => c.DriverEnabledRS485).Returns(true);
|
||||
_serialConnectionMock.Setup(c => c.InterRequestDelay).Returns(TimeSpan.FromSeconds(50));
|
||||
_serialConnectionMock.Setup(c => c.PortName).Returns(portName);
|
||||
_serialConnectionMock.Setup(c => c.BaudRate).Returns(BaudRate.Baud2400);
|
||||
_serialConnectionMock.Setup(c => c.DataBits).Returns(7);
|
||||
_serialConnectionMock.Setup(c => c.Handshake).Returns(Handshake.XOnXOff);
|
||||
_serialConnectionMock.Setup(c => c.Parity).Returns(Parity.Space);
|
||||
_serialConnectionMock.Setup(c => c.RtsEnable).Returns(true);
|
||||
_serialConnectionMock.Setup(c => c.StopBits).Returns(StopBits.OnePointFive);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnDefaultValuesForGenericConnection()
|
||||
{
|
||||
// Arrange
|
||||
var client = new ModbusSerialClient(_genericConnectionMock.Object);
|
||||
|
||||
// Act
|
||||
bool driverEnabled = client.DriverEnabledRS485;
|
||||
var requestDelay = client.InterRequestDelay;
|
||||
string portName = client.PortName;
|
||||
var baudRate = client.BaudRate;
|
||||
int dataBits = client.DataBits;
|
||||
var handshake = client.Handshake;
|
||||
var parity = client.Parity;
|
||||
bool rtsEnable = client.RtsEnable;
|
||||
var stopBits = client.StopBits;
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(driverEnabled);
|
||||
Assert.AreEqual(TimeSpan.Zero, requestDelay);
|
||||
Assert.IsNull(portName);
|
||||
Assert.AreEqual(0, (int)baudRate);
|
||||
Assert.AreEqual(0, dataBits);
|
||||
Assert.AreEqual(0, (int)handshake);
|
||||
Assert.AreEqual(0, (int)parity);
|
||||
Assert.IsFalse(rtsEnable);
|
||||
Assert.AreEqual(0, (int)stopBits);
|
||||
|
||||
_genericConnectionMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldNotSetValuesForGenericConnection()
|
||||
{
|
||||
// Arrange
|
||||
var client = new ModbusSerialClient(_genericConnectionMock.Object);
|
||||
|
||||
// Act
|
||||
client.DriverEnabledRS485 = true;
|
||||
client.InterRequestDelay = TimeSpan.FromSeconds(123);
|
||||
client.PortName = "COM-42";
|
||||
client.BaudRate = BaudRate.Baud2400;
|
||||
client.DataBits = 7;
|
||||
client.Handshake = Handshake.XOnXOff;
|
||||
client.Parity = Parity.Space;
|
||||
client.RtsEnable = true;
|
||||
client.StopBits = StopBits.OnePointFive;
|
||||
|
||||
// Assert
|
||||
_genericConnectionMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnValuesForGenericConnection()
|
||||
{
|
||||
// Arrange
|
||||
var client = new ModbusSerialClient(_genericConnectionMock.Object);
|
||||
|
||||
// Act
|
||||
var idleTimeout = client.IdleTimeout;
|
||||
var connectTimeout = client.ConnectTimeout;
|
||||
var readTimeout = client.ReadTimeout;
|
||||
var writeTimeout = client.WriteTimeout;
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(TimeSpan.FromSeconds(40), idleTimeout);
|
||||
Assert.AreEqual(TimeSpan.FromSeconds(30), connectTimeout);
|
||||
Assert.AreEqual(TimeSpan.FromSeconds(20), readTimeout);
|
||||
Assert.AreEqual(TimeSpan.FromSeconds(10), writeTimeout);
|
||||
|
||||
_genericConnectionMock.VerifyGet(c => c.IdleTimeout, Times.Once);
|
||||
_genericConnectionMock.VerifyGet(c => c.ConnectTimeout, Times.Once);
|
||||
_genericConnectionMock.VerifyGet(c => c.ReadTimeout, Times.Once);
|
||||
_genericConnectionMock.VerifyGet(c => c.WriteTimeout, Times.Once);
|
||||
_genericConnectionMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldSetValuesForGenericConnection()
|
||||
{
|
||||
// Arrange
|
||||
var client = new ModbusSerialClient(_genericConnectionMock.Object);
|
||||
|
||||
// Act
|
||||
client.IdleTimeout = TimeSpan.FromSeconds(10);
|
||||
client.ConnectTimeout = TimeSpan.FromSeconds(20);
|
||||
client.ReadTimeout = TimeSpan.FromSeconds(30);
|
||||
client.WriteTimeout = TimeSpan.FromSeconds(40);
|
||||
|
||||
// Assert
|
||||
_genericConnectionMock.VerifySet(c => c.IdleTimeout = TimeSpan.FromSeconds(10), Times.Once);
|
||||
_genericConnectionMock.VerifySet(c => c.ConnectTimeout = TimeSpan.FromSeconds(20), Times.Once);
|
||||
_genericConnectionMock.VerifySet(c => c.ReadTimeout = TimeSpan.FromSeconds(30), Times.Once);
|
||||
_genericConnectionMock.VerifySet(c => c.WriteTimeout = TimeSpan.FromSeconds(40), Times.Once);
|
||||
|
||||
_genericConnectionMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetValuesForSerialConnection()
|
||||
{
|
||||
// Arrange
|
||||
var client = new ModbusSerialClient(_serialConnectionMock.Object);
|
||||
|
||||
// Act
|
||||
bool driverEnabled = client.DriverEnabledRS485;
|
||||
var requestDelay = client.InterRequestDelay;
|
||||
string portName = client.PortName;
|
||||
var baudRate = client.BaudRate;
|
||||
int dataBits = client.DataBits;
|
||||
var handshake = client.Handshake;
|
||||
var parity = client.Parity;
|
||||
bool rtsEnable = client.RtsEnable;
|
||||
var stopBits = client.StopBits;
|
||||
|
||||
var idleTimeout = client.IdleTimeout;
|
||||
var connectTimeout = client.ConnectTimeout;
|
||||
var readTimeout = client.ReadTimeout;
|
||||
var writeTimeout = client.WriteTimeout;
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(driverEnabled);
|
||||
Assert.AreEqual(TimeSpan.FromSeconds(50), requestDelay);
|
||||
Assert.AreEqual("COM-42", portName);
|
||||
Assert.AreEqual(BaudRate.Baud2400, baudRate);
|
||||
Assert.AreEqual(7, dataBits);
|
||||
Assert.AreEqual(Handshake.XOnXOff, handshake);
|
||||
Assert.AreEqual(Parity.Space, parity);
|
||||
Assert.IsTrue(rtsEnable);
|
||||
Assert.AreEqual(StopBits.OnePointFive, stopBits);
|
||||
|
||||
Assert.AreEqual(TimeSpan.FromSeconds(10), idleTimeout);
|
||||
Assert.AreEqual(TimeSpan.FromSeconds(20), connectTimeout);
|
||||
Assert.AreEqual(TimeSpan.FromSeconds(30), readTimeout);
|
||||
Assert.AreEqual(TimeSpan.FromSeconds(40), writeTimeout);
|
||||
|
||||
_serialConnectionMock.VerifyGet(c => c.DriverEnabledRS485, Times.Once);
|
||||
_serialConnectionMock.VerifyGet(c => c.InterRequestDelay, Times.Once);
|
||||
_serialConnectionMock.VerifyGet(c => c.PortName, Times.Once);
|
||||
_serialConnectionMock.VerifyGet(c => c.BaudRate, Times.Once);
|
||||
_serialConnectionMock.VerifyGet(c => c.DataBits, Times.Once);
|
||||
_serialConnectionMock.VerifyGet(c => c.Handshake, Times.Once);
|
||||
_serialConnectionMock.VerifyGet(c => c.Parity, Times.Once);
|
||||
_serialConnectionMock.VerifyGet(c => c.RtsEnable, Times.Once);
|
||||
_serialConnectionMock.VerifyGet(c => c.StopBits, Times.Once);
|
||||
|
||||
_serialConnectionMock.VerifyGet(c => c.IdleTimeout, Times.Once);
|
||||
_serialConnectionMock.VerifyGet(c => c.ConnectTimeout, Times.Once);
|
||||
_serialConnectionMock.VerifyGet(c => c.ReadTimeout, Times.Once);
|
||||
_serialConnectionMock.VerifyGet(c => c.WriteTimeout, Times.Once);
|
||||
|
||||
_serialConnectionMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldSetValuesForSerialConnection()
|
||||
{
|
||||
// Arrange
|
||||
var client = new ModbusSerialClient(_serialConnectionMock.Object);
|
||||
|
||||
// Act
|
||||
client.DriverEnabledRS485 = true;
|
||||
client.InterRequestDelay = TimeSpan.FromSeconds(123);
|
||||
client.PortName = "COM-42";
|
||||
client.BaudRate = BaudRate.Baud2400;
|
||||
client.DataBits = 7;
|
||||
client.Handshake = Handshake.XOnXOff;
|
||||
client.Parity = Parity.Space;
|
||||
client.RtsEnable = true;
|
||||
client.StopBits = StopBits.OnePointFive;
|
||||
|
||||
client.IdleTimeout = TimeSpan.FromSeconds(40);
|
||||
client.ConnectTimeout = TimeSpan.FromSeconds(30);
|
||||
client.ReadTimeout = TimeSpan.FromSeconds(20);
|
||||
client.WriteTimeout = TimeSpan.FromSeconds(10);
|
||||
|
||||
// Assert
|
||||
_serialConnectionMock.VerifySet(c => c.DriverEnabledRS485 = true, Times.Once);
|
||||
_serialConnectionMock.VerifySet(c => c.InterRequestDelay = TimeSpan.FromSeconds(123), Times.Once);
|
||||
_serialConnectionMock.VerifySet(c => c.PortName = "COM-42", Times.Once);
|
||||
_serialConnectionMock.VerifySet(c => c.BaudRate = BaudRate.Baud2400, Times.Once);
|
||||
_serialConnectionMock.VerifySet(c => c.DataBits = 7, Times.Once);
|
||||
_serialConnectionMock.VerifySet(c => c.Handshake = Handshake.XOnXOff, Times.Once);
|
||||
_serialConnectionMock.VerifySet(c => c.Parity = Parity.Space, Times.Once);
|
||||
_serialConnectionMock.VerifySet(c => c.RtsEnable = true, Times.Once);
|
||||
_serialConnectionMock.VerifySet(c => c.StopBits = StopBits.OnePointFive, Times.Once);
|
||||
|
||||
_serialConnectionMock.VerifySet(c => c.IdleTimeout = TimeSpan.FromSeconds(40), Times.Once);
|
||||
_serialConnectionMock.VerifySet(c => c.ConnectTimeout = TimeSpan.FromSeconds(30), Times.Once);
|
||||
_serialConnectionMock.VerifySet(c => c.ReadTimeout = TimeSpan.FromSeconds(20), Times.Once);
|
||||
_serialConnectionMock.VerifySet(c => c.WriteTimeout = TimeSpan.FromSeconds(10), Times.Once);
|
||||
|
||||
_serialConnectionMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldPrintCleanString()
|
||||
{
|
||||
// Arrange
|
||||
using var client = new ModbusSerialClient(_serialConnectionMock.Object);
|
||||
|
||||
// Act
|
||||
string str = client.ToString();
|
||||
|
||||
// Assert
|
||||
SnapshotAssert.AreEqual(str);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,601 @@
|
||||
using System.IO;
|
||||
using System.IO.Ports;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AMWD.Protocols.Modbus.Common.Contracts;
|
||||
using AMWD.Protocols.Modbus.Serial;
|
||||
using AMWD.Protocols.Modbus.Serial.Enums;
|
||||
using AMWD.Protocols.Modbus.Serial.Utils;
|
||||
|
||||
namespace AMWD.Protocols.Modbus.Tests.Serial
|
||||
{
|
||||
[TestClass]
|
||||
public class ModbusSerialConnectionTest
|
||||
{
|
||||
private Mock<SerialPortWrapper> _serialPortMock;
|
||||
|
||||
private bool _alwaysOpen;
|
||||
private Queue<bool> _isOpenQueue;
|
||||
|
||||
private readonly int _serialPortReadTimeout = 1000;
|
||||
private readonly int _serialPortWriteTimeout = 1000;
|
||||
|
||||
private List<byte[]> _serialLineRequestCallbacks;
|
||||
|
||||
private Queue<byte[]> _serialLineResponseQueue;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
_alwaysOpen = true;
|
||||
_isOpenQueue = new Queue<bool>();
|
||||
|
||||
_serialLineRequestCallbacks = [];
|
||||
_serialLineResponseQueue = new Queue<byte[]>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetAndSetPropertiesOfBaseClient()
|
||||
{
|
||||
// Arrange
|
||||
var connection = GetSerialConnection();
|
||||
|
||||
// Act
|
||||
connection.PortName = "SerialPort";
|
||||
connection.BaudRate = BaudRate.Baud2400;
|
||||
connection.DataBits = 5;
|
||||
connection.Handshake = Handshake.XOnXOff;
|
||||
connection.Parity = Parity.None;
|
||||
connection.ReadTimeout = TimeSpan.FromSeconds(123);
|
||||
connection.RtsEnable = true;
|
||||
connection.StopBits = StopBits.OnePointFive;
|
||||
connection.WriteTimeout = TimeSpan.FromSeconds(456);
|
||||
|
||||
// Assert - part 1
|
||||
_serialPortMock.VerifySet(p => p.PortName = "SerialPort", Times.Once);
|
||||
_serialPortMock.VerifySet(p => p.BaudRate = 2400, Times.Once);
|
||||
_serialPortMock.VerifySet(p => p.DataBits = 5, Times.Once);
|
||||
_serialPortMock.VerifySet(p => p.Handshake = Handshake.XOnXOff, Times.Once);
|
||||
_serialPortMock.VerifySet(p => p.Parity = Parity.None, Times.Once);
|
||||
_serialPortMock.VerifySet(p => p.ReadTimeout = 123000, Times.Once);
|
||||
_serialPortMock.VerifySet(p => p.RtsEnable = true, Times.Once);
|
||||
_serialPortMock.VerifySet(p => p.StopBits = StopBits.OnePointFive, Times.Once);
|
||||
_serialPortMock.VerifySet(p => p.WriteTimeout = 456000, Times.Once);
|
||||
|
||||
_serialPortMock.VerifyNoOtherCalls();
|
||||
|
||||
// Assert - part 2
|
||||
Assert.AreEqual("Serial", connection.Name);
|
||||
Assert.IsNull(connection.PortName);
|
||||
Assert.AreEqual(0, (int)connection.BaudRate);
|
||||
Assert.AreEqual(0, connection.DataBits);
|
||||
Assert.AreEqual(0, (int)connection.Handshake);
|
||||
Assert.AreEqual(0, (int)connection.Parity);
|
||||
Assert.AreEqual(1, connection.ReadTimeout.TotalSeconds);
|
||||
Assert.IsFalse(connection.RtsEnable);
|
||||
Assert.AreEqual(0, (int)connection.StopBits);
|
||||
Assert.AreEqual(1, connection.WriteTimeout.TotalSeconds);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldBeAbleToDisposeMultipleTimes()
|
||||
{
|
||||
// Arrange
|
||||
var connection = GetConnection();
|
||||
|
||||
// Act
|
||||
connection.Dispose();
|
||||
connection.Dispose();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(null)]
|
||||
[DataRow("")]
|
||||
[DataRow(" ")]
|
||||
public void ShouldThrowArgumentNullExceptionOnCreate(string portName)
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentNullException>(() => new ModbusSerialClient(portName));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldThrowDisposedExceptionOnInvokeAsync()
|
||||
{
|
||||
// Arrange
|
||||
var connection = GetConnection();
|
||||
connection.Dispose();
|
||||
|
||||
// Act + Assert
|
||||
await Assert.ThrowsExactlyAsync<ObjectDisposedException>(() => connection.InvokeAsync(null, null));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(null)]
|
||||
[DataRow(new byte[0])]
|
||||
public async Task ShouldThrowArgumentNullExceptionForMissingRequestOnInvokeAsync(byte[] request)
|
||||
{
|
||||
// Arrange
|
||||
var connection = GetConnection();
|
||||
|
||||
// Act + Assert
|
||||
await Assert.ThrowsExactlyAsync<ArgumentNullException>(() => connection.InvokeAsync(request, null));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldThrowArgumentNullExceptionForMissingValidationOnInvokeAsync()
|
||||
{
|
||||
// Arrange
|
||||
byte[] request = new byte[1];
|
||||
var connection = GetConnection();
|
||||
|
||||
// Act + Assert
|
||||
await Assert.ThrowsExactlyAsync<ArgumentNullException>(() => connection.InvokeAsync(request, null));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldInvokeAsync()
|
||||
{
|
||||
// Arrange
|
||||
byte[] request = [1, 2, 3];
|
||||
byte[] expectedResponse = [9, 8, 7];
|
||||
var validation = new Func<IReadOnlyList<byte>, bool>(_ => true);
|
||||
_serialLineResponseQueue.Enqueue(expectedResponse);
|
||||
|
||||
var connection = GetConnection();
|
||||
|
||||
// Act
|
||||
var response = await connection.InvokeAsync(request, validation);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(response);
|
||||
|
||||
CollectionAssert.AreEqual(expectedResponse, response.ToArray());
|
||||
CollectionAssert.AreEqual(request, _serialLineRequestCallbacks.First());
|
||||
|
||||
_serialPortMock.Verify(c => c.IsOpen, Times.Once);
|
||||
|
||||
_serialPortMock.Verify(ns => ns.WriteAsync(It.IsAny<byte[]>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_serialPortMock.Verify(ns => ns.ReadAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
|
||||
_serialPortMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldOpenAndCloseOnInvokeAsyncOnLinuxNotModifyingDriver()
|
||||
{
|
||||
// Arrange
|
||||
_alwaysOpen = false;
|
||||
_isOpenQueue.Enqueue(false);
|
||||
_isOpenQueue.Enqueue(true);
|
||||
_isOpenQueue.Enqueue(true);
|
||||
|
||||
byte[] request = [1, 2, 3];
|
||||
byte[] expectedResponse = [9, 8, 7];
|
||||
var validation = new Func<IReadOnlyList<byte>, bool>(_ => true);
|
||||
_serialLineResponseQueue.Enqueue(expectedResponse);
|
||||
|
||||
var connection = GetSerialConnection();
|
||||
connection.GetType().GetField("_isLinux", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(connection, true);
|
||||
connection.IdleTimeout = TimeSpan.FromMilliseconds(200);
|
||||
connection.DriverEnabledRS485 = false;
|
||||
|
||||
// Act
|
||||
var response = await connection.InvokeAsync(request, validation);
|
||||
await Task.Delay(500);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(response);
|
||||
|
||||
CollectionAssert.AreEqual(expectedResponse, response.ToArray());
|
||||
CollectionAssert.AreEqual(request, _serialLineRequestCallbacks.First());
|
||||
|
||||
_serialPortMock.VerifyGet(c => c.ReadTimeout, Times.Once);
|
||||
|
||||
_serialPortMock.Verify(c => c.IsOpen, Times.Exactly(3));
|
||||
_serialPortMock.Verify(c => c.Close(), Times.Exactly(2));
|
||||
_serialPortMock.Verify(c => c.ResetRS485DriverStateFlags(), Times.Exactly(2));
|
||||
_serialPortMock.Verify(c => c.Open(), Times.Once);
|
||||
|
||||
_serialPortMock.Verify(ns => ns.WriteAsync(It.IsAny<byte[]>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_serialPortMock.Verify(ns => ns.ReadAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
|
||||
_serialPortMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldOpenAndCloseOnInvokeAsyncOnLinuxModifyingDriver()
|
||||
{
|
||||
// Arrange
|
||||
_alwaysOpen = false;
|
||||
_isOpenQueue.Enqueue(false);
|
||||
_isOpenQueue.Enqueue(true);
|
||||
_isOpenQueue.Enqueue(true);
|
||||
|
||||
byte[] request = [1, 2, 3];
|
||||
byte[] expectedResponse = [9, 8, 7];
|
||||
var validation = new Func<IReadOnlyList<byte>, bool>(_ => true);
|
||||
_serialLineResponseQueue.Enqueue(expectedResponse);
|
||||
|
||||
var connection = GetSerialConnection();
|
||||
connection.GetType().GetField("_isLinux", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(connection, true);
|
||||
connection.IdleTimeout = TimeSpan.FromMilliseconds(200);
|
||||
connection.DriverEnabledRS485 = true;
|
||||
|
||||
// Act
|
||||
var response = await connection.InvokeAsync(request, validation);
|
||||
await Task.Delay(500);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(response);
|
||||
|
||||
CollectionAssert.AreEqual(expectedResponse, response.ToArray());
|
||||
CollectionAssert.AreEqual(request, _serialLineRequestCallbacks.First());
|
||||
|
||||
_serialPortMock.VerifyGet(c => c.ReadTimeout, Times.Once);
|
||||
|
||||
_serialPortMock.Verify(c => c.IsOpen, Times.Exactly(3));
|
||||
_serialPortMock.Verify(c => c.Close(), Times.Exactly(2));
|
||||
_serialPortMock.Verify(c => c.ResetRS485DriverStateFlags(), Times.Exactly(2));
|
||||
_serialPortMock.Verify(c => c.Open(), Times.Once);
|
||||
|
||||
_serialPortMock.Verify(c => c.GetRS485DriverStateFlags(), Times.Once);
|
||||
_serialPortMock.Verify(c => c.ChangeRS485DriverStateFlags(It.IsAny<RS485Flags>()), Times.Once);
|
||||
|
||||
_serialPortMock.Verify(ns => ns.WriteAsync(It.IsAny<byte[]>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_serialPortMock.Verify(ns => ns.ReadAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
|
||||
_serialPortMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldOpenAndCloseOnInvokeAsyncOnOtherOsNotModifyingDriver()
|
||||
{
|
||||
// Arrange
|
||||
_alwaysOpen = false;
|
||||
_isOpenQueue.Enqueue(false);
|
||||
_isOpenQueue.Enqueue(true);
|
||||
_isOpenQueue.Enqueue(true);
|
||||
|
||||
byte[] request = [1, 2, 3];
|
||||
byte[] expectedResponse = [9, 8, 7];
|
||||
var validation = new Func<IReadOnlyList<byte>, bool>(_ => true);
|
||||
_serialLineResponseQueue.Enqueue(expectedResponse);
|
||||
|
||||
var connection = GetSerialConnection();
|
||||
connection.GetType().GetField("_isLinux", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(connection, false);
|
||||
connection.IdleTimeout = TimeSpan.FromMilliseconds(200);
|
||||
connection.DriverEnabledRS485 = false;
|
||||
|
||||
// Act
|
||||
var response = await connection.InvokeAsync(request, validation);
|
||||
await Task.Delay(500);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(response);
|
||||
|
||||
CollectionAssert.AreEqual(expectedResponse, response.ToArray());
|
||||
CollectionAssert.AreEqual(request, _serialLineRequestCallbacks.First());
|
||||
|
||||
_serialPortMock.VerifyGet(c => c.ReadTimeout, Times.Once);
|
||||
|
||||
_serialPortMock.Verify(c => c.IsOpen, Times.Exactly(3));
|
||||
_serialPortMock.Verify(c => c.Close(), Times.Exactly(2));
|
||||
_serialPortMock.Verify(c => c.ResetRS485DriverStateFlags(), Times.Exactly(2));
|
||||
_serialPortMock.Verify(c => c.Open(), Times.Once);
|
||||
|
||||
_serialPortMock.Verify(ns => ns.WriteAsync(It.IsAny<byte[]>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_serialPortMock.Verify(ns => ns.ReadAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
|
||||
_serialPortMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldOpenAndCloseOnInvokeAsyncOnOtherOsModifyingDriver()
|
||||
{
|
||||
// Arrange
|
||||
_alwaysOpen = false;
|
||||
_isOpenQueue.Enqueue(false);
|
||||
_isOpenQueue.Enqueue(true);
|
||||
_isOpenQueue.Enqueue(true);
|
||||
|
||||
byte[] request = [1, 2, 3];
|
||||
byte[] expectedResponse = [9, 8, 7];
|
||||
var validation = new Func<IReadOnlyList<byte>, bool>(_ => true);
|
||||
_serialLineResponseQueue.Enqueue(expectedResponse);
|
||||
|
||||
var connection = GetSerialConnection();
|
||||
connection.GetType().GetField("_isLinux", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(connection, false);
|
||||
connection.IdleTimeout = TimeSpan.FromMilliseconds(200);
|
||||
connection.DriverEnabledRS485 = true;
|
||||
|
||||
// Act
|
||||
var response = await connection.InvokeAsync(request, validation);
|
||||
await Task.Delay(500);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(response);
|
||||
|
||||
CollectionAssert.AreEqual(expectedResponse, response.ToArray());
|
||||
CollectionAssert.AreEqual(request, _serialLineRequestCallbacks.First());
|
||||
|
||||
_serialPortMock.VerifyGet(c => c.ReadTimeout, Times.Once);
|
||||
|
||||
_serialPortMock.Verify(c => c.IsOpen, Times.Exactly(3));
|
||||
_serialPortMock.Verify(c => c.Close(), Times.Exactly(2));
|
||||
_serialPortMock.Verify(c => c.ResetRS485DriverStateFlags(), Times.Exactly(2));
|
||||
_serialPortMock.Verify(c => c.Open(), Times.Once);
|
||||
|
||||
_serialPortMock.Verify(ns => ns.WriteAsync(It.IsAny<byte[]>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_serialPortMock.Verify(ns => ns.ReadAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
|
||||
_serialPortMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldThrowEndOfStreamExceptionOnInvokeAsync()
|
||||
{
|
||||
// Arrange
|
||||
byte[] request = [1, 2, 3];
|
||||
var validation = new Func<IReadOnlyList<byte>, bool>(_ => true);
|
||||
|
||||
var connection = GetConnection();
|
||||
|
||||
// Act + Assert
|
||||
await Assert.ThrowsExactlyAsync<EndOfStreamException>(() => connection.InvokeAsync(request, validation));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldSkipCloseOnTimeoutOnInvokeAsync()
|
||||
{
|
||||
// Arrange
|
||||
_alwaysOpen = false;
|
||||
_isOpenQueue.Enqueue(false);
|
||||
_isOpenQueue.Enqueue(true);
|
||||
_isOpenQueue.Enqueue(false);
|
||||
|
||||
byte[] request = [1, 2, 3];
|
||||
byte[] expectedResponse = [9, 8, 7];
|
||||
var validation = new Func<IReadOnlyList<byte>, bool>(_ => true);
|
||||
_serialLineResponseQueue.Enqueue(expectedResponse);
|
||||
|
||||
var connection = GetConnection();
|
||||
connection.IdleTimeout = TimeSpan.FromMilliseconds(200);
|
||||
|
||||
// Act
|
||||
var response = await connection.InvokeAsync(request, validation);
|
||||
await Task.Delay(500);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(response);
|
||||
|
||||
CollectionAssert.AreEqual(expectedResponse, response.ToArray());
|
||||
CollectionAssert.AreEqual(request, _serialLineRequestCallbacks.First());
|
||||
|
||||
_serialPortMock.VerifyGet(c => c.ReadTimeout, Times.Once);
|
||||
|
||||
_serialPortMock.Verify(c => c.IsOpen, Times.Exactly(3));
|
||||
_serialPortMock.Verify(c => c.Close(), Times.Once);
|
||||
_serialPortMock.Verify(c => c.ResetRS485DriverStateFlags(), Times.Once);
|
||||
_serialPortMock.Verify(c => c.Open(), Times.Once);
|
||||
|
||||
_serialPortMock.Verify(ns => ns.WriteAsync(It.IsAny<byte[]>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_serialPortMock.Verify(ns => ns.ReadAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
|
||||
_serialPortMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldRetryToConnectOnInvokeAsync()
|
||||
{
|
||||
// Arrange
|
||||
_alwaysOpen = false;
|
||||
_isOpenQueue.Enqueue(false);
|
||||
_isOpenQueue.Enqueue(false);
|
||||
_isOpenQueue.Enqueue(true);
|
||||
|
||||
byte[] request = [1, 2, 3];
|
||||
byte[] expectedResponse = [9, 8, 7];
|
||||
var validation = new Func<IReadOnlyList<byte>, bool>(_ => true);
|
||||
_serialLineResponseQueue.Enqueue(expectedResponse);
|
||||
|
||||
var connection = GetConnection();
|
||||
|
||||
// Act
|
||||
var response = await connection.InvokeAsync(request, validation);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(response);
|
||||
|
||||
CollectionAssert.AreEqual(expectedResponse, response.ToArray());
|
||||
CollectionAssert.AreEqual(request, _serialLineRequestCallbacks.First());
|
||||
|
||||
_serialPortMock.VerifyGet(c => c.ReadTimeout, Times.Exactly(2));
|
||||
|
||||
_serialPortMock.Verify(c => c.IsOpen, Times.Exactly(3));
|
||||
_serialPortMock.Verify(c => c.Close(), Times.Exactly(2));
|
||||
_serialPortMock.Verify(c => c.ResetRS485DriverStateFlags(), Times.Exactly(2));
|
||||
_serialPortMock.Verify(c => c.Open(), Times.Exactly(2));
|
||||
|
||||
_serialPortMock.Verify(ns => ns.WriteAsync(It.IsAny<byte[]>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_serialPortMock.Verify(ns => ns.ReadAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
|
||||
_serialPortMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldThrowTaskCancelledExceptionForDisposeOnInvokeAsync()
|
||||
{
|
||||
// Arrange
|
||||
byte[] request = [1, 2, 3];
|
||||
var validation = new Func<IReadOnlyList<byte>, bool>(_ => true);
|
||||
|
||||
var connection = GetConnection();
|
||||
_serialPortMock
|
||||
.Setup(ns => ns.WriteAsync(It.IsAny<byte[]>(), It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.Delay(100));
|
||||
|
||||
// Act + Assert
|
||||
await Assert.ThrowsExactlyAsync<TaskCanceledException>(async () =>
|
||||
{
|
||||
var task = connection.InvokeAsync(request, validation);
|
||||
connection.Dispose();
|
||||
await task;
|
||||
});
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldThrowTaskCancelledExceptionForCancelOnInvokeAsync()
|
||||
{
|
||||
// Arrange
|
||||
byte[] request = [1, 2, 3];
|
||||
var validation = new Func<IReadOnlyList<byte>, bool>(_ => true);
|
||||
using var cts = new CancellationTokenSource();
|
||||
|
||||
var connection = GetConnection();
|
||||
_serialPortMock
|
||||
.Setup(ns => ns.WriteAsync(It.IsAny<byte[]>(), It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.Delay(100));
|
||||
|
||||
// Act + Assert
|
||||
await Assert.ThrowsExactlyAsync<TaskCanceledException>(async () =>
|
||||
{
|
||||
var task = connection.InvokeAsync(request, validation, cts.Token);
|
||||
cts.Cancel();
|
||||
await task;
|
||||
});
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldRemoveRequestFromQueueOnInvokeAsync()
|
||||
{
|
||||
// Arrange
|
||||
byte[] request = [1, 2, 3];
|
||||
byte[] expectedResponse = [9, 8, 7];
|
||||
var validation = new Func<IReadOnlyList<byte>, bool>(_ => true);
|
||||
_serialLineResponseQueue.Enqueue(expectedResponse);
|
||||
using var cts = new CancellationTokenSource();
|
||||
|
||||
var connection = GetConnection();
|
||||
_serialPortMock
|
||||
.Setup(ns => ns.WriteAsync(It.IsAny<byte[]>(), It.IsAny<CancellationToken>()))
|
||||
.Callback<byte[], CancellationToken>((req, _) => _serialLineRequestCallbacks.Add([.. req]))
|
||||
.Returns(Task.Delay(100));
|
||||
|
||||
// Act
|
||||
var taskToComplete = connection.InvokeAsync(request, validation);
|
||||
|
||||
var taskToCancel = connection.InvokeAsync(request, validation, cts.Token);
|
||||
cts.Cancel();
|
||||
|
||||
var response = await taskToComplete;
|
||||
|
||||
// Assert - Part 1
|
||||
try
|
||||
{
|
||||
await taskToCancel;
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{ /* expected exception */ }
|
||||
|
||||
// Assert - Part 2
|
||||
Assert.AreEqual(1, _serialLineRequestCallbacks.Count);
|
||||
CollectionAssert.AreEqual(request, _serialLineRequestCallbacks.First());
|
||||
CollectionAssert.AreEqual(expectedResponse, response.ToArray());
|
||||
|
||||
_serialPortMock.Verify(c => c.IsOpen, Times.Once);
|
||||
|
||||
_serialPortMock.Verify(ns => ns.WriteAsync(It.IsAny<byte[]>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_serialPortMock.Verify(ns => ns.ReadAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
|
||||
_serialPortMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldRemoveRequestFromQueueOnDispose()
|
||||
{
|
||||
// Arrange
|
||||
byte[] request = [1, 2, 3];
|
||||
var validation = new Func<IReadOnlyList<byte>, bool>(_ => true);
|
||||
|
||||
var connection = GetConnection();
|
||||
_serialPortMock
|
||||
.Setup(ns => ns.WriteAsync(It.IsAny<byte[]>(), It.IsAny<CancellationToken>()))
|
||||
.Callback<byte[], CancellationToken>((req, _) => _serialLineRequestCallbacks.Add([.. req]))
|
||||
.Returns(Task.Delay(100));
|
||||
|
||||
// Act
|
||||
var taskToCancel = connection.InvokeAsync(request, validation);
|
||||
var taskToDequeue = connection.InvokeAsync(request, validation);
|
||||
connection.Dispose();
|
||||
|
||||
// Assert
|
||||
try
|
||||
{
|
||||
await taskToCancel;
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{ /* expected exception */ }
|
||||
|
||||
try
|
||||
{
|
||||
await taskToDequeue;
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{ /* expected exception */ }
|
||||
|
||||
Assert.AreEqual(1, _serialLineRequestCallbacks.Count);
|
||||
CollectionAssert.AreEqual(request, _serialLineRequestCallbacks.First());
|
||||
|
||||
_serialPortMock.Verify(c => c.IsOpen, Times.Once);
|
||||
_serialPortMock.Verify(c => c.Dispose(), Times.Once);
|
||||
|
||||
_serialPortMock.Verify(ns => ns.WriteAsync(It.IsAny<byte[]>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
|
||||
_serialPortMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
private IModbusConnection GetConnection()
|
||||
=> GetSerialConnection();
|
||||
|
||||
private ModbusSerialConnection GetSerialConnection()
|
||||
{
|
||||
_serialPortMock = new Mock<SerialPortWrapper>();
|
||||
_serialPortMock.Setup(p => p.IsOpen).Returns(() => _alwaysOpen || _isOpenQueue.Dequeue());
|
||||
_serialPortMock.Setup(p => p.ReadTimeout).Returns(() => _serialPortReadTimeout);
|
||||
_serialPortMock.Setup(p => p.WriteTimeout).Returns(() => _serialPortWriteTimeout);
|
||||
|
||||
_serialPortMock
|
||||
.Setup(p => p.WriteAsync(It.IsAny<byte[]>(), It.IsAny<CancellationToken>()))
|
||||
.Callback<byte[], CancellationToken>((req, _) => _serialLineRequestCallbacks.Add(req))
|
||||
.Returns(Task.CompletedTask);
|
||||
_serialPortMock
|
||||
.Setup(p => p.ReadAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
|
||||
.Returns<byte[], int, int, CancellationToken>((buffer, offset, count, _) =>
|
||||
{
|
||||
if (_serialLineResponseQueue.TryDequeue(out byte[] bytes))
|
||||
{
|
||||
int len = bytes.Length < count ? bytes.Length : count;
|
||||
Array.Copy(bytes, 0, buffer, offset, len);
|
||||
return Task.FromResult(len);
|
||||
}
|
||||
|
||||
return Task.FromResult(0);
|
||||
});
|
||||
|
||||
var connection = new ModbusSerialConnection("some-port");
|
||||
|
||||
// Replace real connection with mock
|
||||
var connectionField = connection.GetType().GetField("_serialPort", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
(connectionField.GetValue(connection) as SerialPortWrapper)?.Dispose();
|
||||
connectionField.SetValue(connection, _serialPortMock.Object);
|
||||
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
}
|
||||
82
test/AMWD.Protocols.Modbus.Tests/SnapshotAssert.cs
Normal file
82
test/AMWD.Protocols.Modbus.Tests/SnapshotAssert.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
namespace AMWD.Protocols.Modbus.Tests
|
||||
{
|
||||
// ================================================================================================================================ //
|
||||
// Source: https://git.am-wd.de/am-wd/common/-/blob/fb26e441a48214aaae72003c4a5ac33d5c7b929a/src/AMWD.Common.Test/SnapshotAssert.cs //
|
||||
// ================================================================================================================================ //
|
||||
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||
internal sealed class SnapshotAssert
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests whether the specified string is equal to the saved snapshot.
|
||||
/// </summary>
|
||||
/// <param name="actual">The current aggregated content string.</param>
|
||||
/// <param name="message">An optional message to display if the assertion fails.</param>
|
||||
/// <param name="callerFilePath">The absolute file path of the calling file (filled automatically on compile time).</param>
|
||||
/// <param name="callerMemberName">The name of the calling method (filled automatically on compile time).</param>
|
||||
public static void AreEqual(string actual, string message = null, [CallerFilePath] string callerFilePath = null, [CallerMemberName] string callerMemberName = null)
|
||||
{
|
||||
string cleanLineEnding = actual
|
||||
.Replace("\r\n", "\n") // Windows
|
||||
.Replace("\r", "\n"); // MacOS
|
||||
AreEqual(Encoding.UTF8.GetBytes(cleanLineEnding), message, callerFilePath, callerMemberName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the specified byte array is equal to the saved snapshot.
|
||||
/// </summary>
|
||||
/// <param name="actual">The current aggregated content bytes.</param>
|
||||
/// <param name="message">An optional message to display if the assertion fails.</param>
|
||||
/// <param name="callerFilePath">The absolute file path of the calling file (filled automatically on compile time).</param>
|
||||
/// <param name="callerMemberName">The name of the calling method (filled automatically on compile time).</param>
|
||||
public static void AreEqual(byte[] actual, string message = null, [CallerFilePath] string callerFilePath = null, [CallerMemberName] string callerMemberName = null)
|
||||
=> AreEqual(actual, null, message, callerFilePath, callerMemberName);
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the specified byte array is equal to the saved snapshot.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The past has shown, that e.g. wkhtmltopdf prints the current timestamp at the beginning of the PDF file.
|
||||
/// Therefore you can specify which sequences of bytes should be excluded from the comparison.
|
||||
/// </remarks>
|
||||
/// <param name="actual">The current aggregated content bytes.</param>
|
||||
/// <param name="excludedSequences">The excluded sequences.</param>
|
||||
/// <param name="message">An optional message to display if the assertion fails.</param>
|
||||
/// <param name="callerFilePath">The absolute file path of the calling file (filled automatically on compile time).</param>
|
||||
/// <param name="callerMemberName">The name of the calling method (filled automatically on compile time).</param>
|
||||
public static void AreEqual(byte[] actual, List<(int Start, int Length)> excludedSequences = null, string message = null, [CallerFilePath] string callerFilePath = null, [CallerMemberName] string callerMemberName = null)
|
||||
{
|
||||
string callerDirectory = Path.GetDirectoryName(callerFilePath);
|
||||
string callerFileName = Path.GetFileNameWithoutExtension(callerFilePath);
|
||||
|
||||
string snapshotDirectory = Path.Combine(callerDirectory, "Snapshots", callerFileName);
|
||||
string snapshotFilePath = Path.Combine(snapshotDirectory, $"{callerMemberName}.snap.bin");
|
||||
|
||||
if (File.Exists(snapshotFilePath))
|
||||
{
|
||||
byte[] expected = File.ReadAllBytes(snapshotFilePath);
|
||||
if (actual.Length != expected.Length)
|
||||
Assert.Fail(message);
|
||||
|
||||
for (int i = 0; i < actual.Length; i++)
|
||||
{
|
||||
if (excludedSequences?.Any(s => s.Start <= i && i < s.Start + s.Length) == true)
|
||||
continue;
|
||||
|
||||
if (actual[i] != expected[i])
|
||||
Assert.Fail(message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!Directory.Exists(snapshotDirectory))
|
||||
Directory.CreateDirectory(snapshotDirectory);
|
||||
|
||||
File.WriteAllBytes(snapshotFilePath, actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
179
test/AMWD.Protocols.Modbus.Tests/Tcp/ModbusTcpClientTest.cs
Normal file
179
test/AMWD.Protocols.Modbus.Tests/Tcp/ModbusTcpClientTest.cs
Normal file
@@ -0,0 +1,179 @@
|
||||
using AMWD.Protocols.Modbus.Common.Contracts;
|
||||
using AMWD.Protocols.Modbus.Tcp;
|
||||
|
||||
namespace AMWD.Protocols.Modbus.Tests.Tcp
|
||||
{
|
||||
[TestClass]
|
||||
public class ModbusTcpClientTest
|
||||
{
|
||||
private Mock<IModbusConnection> _genericConnectionMock;
|
||||
private Mock<ModbusTcpConnection> _tcpConnectionMock;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
_genericConnectionMock = new Mock<IModbusConnection>();
|
||||
_genericConnectionMock.Setup(c => c.IdleTimeout).Returns(TimeSpan.FromSeconds(40));
|
||||
_genericConnectionMock.Setup(c => c.ConnectTimeout).Returns(TimeSpan.FromSeconds(30));
|
||||
_genericConnectionMock.Setup(c => c.ReadTimeout).Returns(TimeSpan.FromSeconds(20));
|
||||
_genericConnectionMock.Setup(c => c.WriteTimeout).Returns(TimeSpan.FromSeconds(10));
|
||||
|
||||
_tcpConnectionMock = new Mock<ModbusTcpConnection>();
|
||||
|
||||
_tcpConnectionMock.Setup(c => c.IdleTimeout).Returns(TimeSpan.FromSeconds(10));
|
||||
_tcpConnectionMock.Setup(c => c.ConnectTimeout).Returns(TimeSpan.FromSeconds(20));
|
||||
_tcpConnectionMock.Setup(c => c.ReadTimeout).Returns(TimeSpan.FromSeconds(30));
|
||||
_tcpConnectionMock.Setup(c => c.WriteTimeout).Returns(TimeSpan.FromSeconds(40));
|
||||
|
||||
_tcpConnectionMock.Setup(c => c.Hostname).Returns("127.0.0.1");
|
||||
_tcpConnectionMock.Setup(c => c.Port).Returns(502);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnDefaultValuesForGenericConnection()
|
||||
{
|
||||
// Arrange
|
||||
var client = new ModbusTcpClient(_genericConnectionMock.Object);
|
||||
|
||||
// Act
|
||||
string hostname = client.Hostname;
|
||||
int port = client.Port;
|
||||
|
||||
// Assert
|
||||
Assert.IsNull(hostname);
|
||||
Assert.AreEqual(0, port);
|
||||
|
||||
_genericConnectionMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldNotSetValuesForGenericConnection()
|
||||
{
|
||||
// Arrange
|
||||
var client = new ModbusTcpClient(_genericConnectionMock.Object);
|
||||
|
||||
// Act
|
||||
client.Hostname = "localhost";
|
||||
client.Port = 205;
|
||||
|
||||
// Assert
|
||||
_genericConnectionMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReturnValuesForGenericConnection()
|
||||
{
|
||||
// Arrange
|
||||
var client = new ModbusTcpClient(_genericConnectionMock.Object);
|
||||
|
||||
// Act
|
||||
var idleTimeout = client.IdleTimeout;
|
||||
var connectTimeout = client.ConnectTimeout;
|
||||
var readTimeout = client.ReadTimeout;
|
||||
var writeTimeout = client.WriteTimeout;
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(TimeSpan.FromSeconds(40), idleTimeout);
|
||||
Assert.AreEqual(TimeSpan.FromSeconds(30), connectTimeout);
|
||||
Assert.AreEqual(TimeSpan.FromSeconds(20), readTimeout);
|
||||
Assert.AreEqual(TimeSpan.FromSeconds(10), writeTimeout);
|
||||
|
||||
_genericConnectionMock.VerifyGet(c => c.IdleTimeout, Times.Once);
|
||||
_genericConnectionMock.VerifyGet(c => c.ConnectTimeout, Times.Once);
|
||||
_genericConnectionMock.VerifyGet(c => c.ReadTimeout, Times.Once);
|
||||
_genericConnectionMock.VerifyGet(c => c.WriteTimeout, Times.Once);
|
||||
_genericConnectionMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldSetValuesForGenericConnection()
|
||||
{
|
||||
// Arrange
|
||||
var client = new ModbusTcpClient(_genericConnectionMock.Object);
|
||||
|
||||
// Act
|
||||
client.IdleTimeout = TimeSpan.FromSeconds(10);
|
||||
client.ConnectTimeout = TimeSpan.FromSeconds(20);
|
||||
client.ReadTimeout = TimeSpan.FromSeconds(30);
|
||||
client.WriteTimeout = TimeSpan.FromSeconds(40);
|
||||
|
||||
// Assert
|
||||
_genericConnectionMock.VerifySet(c => c.IdleTimeout = TimeSpan.FromSeconds(10), Times.Once);
|
||||
_genericConnectionMock.VerifySet(c => c.ConnectTimeout = TimeSpan.FromSeconds(20), Times.Once);
|
||||
_genericConnectionMock.VerifySet(c => c.ReadTimeout = TimeSpan.FromSeconds(30), Times.Once);
|
||||
_genericConnectionMock.VerifySet(c => c.WriteTimeout = TimeSpan.FromSeconds(40), Times.Once);
|
||||
|
||||
_genericConnectionMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetValuesForTcpConnection()
|
||||
{
|
||||
// Arrange
|
||||
var client = new ModbusTcpClient(_tcpConnectionMock.Object);
|
||||
|
||||
// Act
|
||||
string hostname = client.Hostname;
|
||||
int port = client.Port;
|
||||
var idleTimeout = client.IdleTimeout;
|
||||
var connectTimeout = client.ConnectTimeout;
|
||||
var readTimeout = client.ReadTimeout;
|
||||
var writeTimeout = client.WriteTimeout;
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual("127.0.0.1", hostname);
|
||||
Assert.AreEqual(502, port);
|
||||
Assert.AreEqual(TimeSpan.FromSeconds(10), idleTimeout);
|
||||
Assert.AreEqual(TimeSpan.FromSeconds(20), connectTimeout);
|
||||
Assert.AreEqual(TimeSpan.FromSeconds(30), readTimeout);
|
||||
Assert.AreEqual(TimeSpan.FromSeconds(40), writeTimeout);
|
||||
|
||||
_tcpConnectionMock.VerifyGet(c => c.Hostname, Times.Once);
|
||||
_tcpConnectionMock.VerifyGet(c => c.Port, Times.Once);
|
||||
_tcpConnectionMock.VerifyGet(c => c.IdleTimeout, Times.Once);
|
||||
_tcpConnectionMock.VerifyGet(c => c.ConnectTimeout, Times.Once);
|
||||
_tcpConnectionMock.VerifyGet(c => c.ReadTimeout, Times.Once);
|
||||
_tcpConnectionMock.VerifyGet(c => c.WriteTimeout, Times.Once);
|
||||
|
||||
_tcpConnectionMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldSetValuesForTcpConnection()
|
||||
{
|
||||
// Arrange
|
||||
var client = new ModbusTcpClient(_tcpConnectionMock.Object);
|
||||
|
||||
// Act
|
||||
client.Hostname = "localhost";
|
||||
client.Port = 205;
|
||||
client.IdleTimeout = TimeSpan.FromSeconds(40);
|
||||
client.ConnectTimeout = TimeSpan.FromSeconds(30);
|
||||
client.ReadTimeout = TimeSpan.FromSeconds(20);
|
||||
client.WriteTimeout = TimeSpan.FromSeconds(10);
|
||||
|
||||
// Assert
|
||||
_tcpConnectionMock.VerifySet(c => c.Hostname = "localhost", Times.Once);
|
||||
_tcpConnectionMock.VerifySet(c => c.Port = 205, Times.Once);
|
||||
_tcpConnectionMock.VerifySet(c => c.IdleTimeout = TimeSpan.FromSeconds(40), Times.Once);
|
||||
_tcpConnectionMock.VerifySet(c => c.ConnectTimeout = TimeSpan.FromSeconds(30), Times.Once);
|
||||
_tcpConnectionMock.VerifySet(c => c.ReadTimeout = TimeSpan.FromSeconds(20), Times.Once);
|
||||
_tcpConnectionMock.VerifySet(c => c.WriteTimeout = TimeSpan.FromSeconds(10), Times.Once);
|
||||
|
||||
_tcpConnectionMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldPrintCleanString()
|
||||
{
|
||||
// Arrange
|
||||
using var client = new ModbusTcpClient(_tcpConnectionMock.Object);
|
||||
|
||||
// Act
|
||||
string str = client.ToString();
|
||||
|
||||
// Assert
|
||||
SnapshotAssert.AreEqual(str);
|
||||
}
|
||||
}
|
||||
}
|
||||
529
test/AMWD.Protocols.Modbus.Tests/Tcp/ModbusTcpConnectionTest.cs
Normal file
529
test/AMWD.Protocols.Modbus.Tests/Tcp/ModbusTcpConnectionTest.cs
Normal file
@@ -0,0 +1,529 @@
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AMWD.Protocols.Modbus.Common.Contracts;
|
||||
using AMWD.Protocols.Modbus.Tcp;
|
||||
using AMWD.Protocols.Modbus.Tcp.Utils;
|
||||
|
||||
namespace AMWD.Protocols.Modbus.Tests.Tcp
|
||||
{
|
||||
[TestClass]
|
||||
public class ModbusTcpConnectionTest
|
||||
{
|
||||
private readonly string _hostname = "127.0.0.1";
|
||||
|
||||
private Mock<TcpClientWrapper> _tcpClientMock;
|
||||
private Mock<TcpClientWrapperFactory> _tcpClientFactoryMock;
|
||||
private Mock<NetworkStreamWrapper> _networkStreamMock;
|
||||
|
||||
private bool _alwaysConnected;
|
||||
private Queue<bool> _connectedQueue;
|
||||
|
||||
private readonly int _clientReceiveTimeout = 1000;
|
||||
private readonly int _clientSendTimeout = 1000;
|
||||
private readonly Task _clientConnectTask = Task.CompletedTask;
|
||||
|
||||
private List<byte[]> _networkRequestCallbacks;
|
||||
|
||||
private Queue<byte[]> _networkResponseQueue;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
_alwaysConnected = true;
|
||||
_connectedQueue = new Queue<bool>();
|
||||
|
||||
_networkRequestCallbacks = [];
|
||||
_networkResponseQueue = new Queue<byte[]>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldSetPropertiesOfBaseClient()
|
||||
{
|
||||
// Arrange
|
||||
byte[] request = [1, 2, 3];
|
||||
byte[] expectedResponse = [9, 8, 7];
|
||||
var validation = new Func<IReadOnlyList<byte>, bool>(_ => true);
|
||||
_networkResponseQueue.Enqueue(expectedResponse);
|
||||
|
||||
var connection = GetTcpConnection();
|
||||
await connection.InvokeAsync(request, validation);
|
||||
|
||||
_tcpClientMock.Invocations.Clear();
|
||||
_networkStreamMock.Invocations.Clear();
|
||||
|
||||
// Act
|
||||
connection.ReadTimeout = TimeSpan.FromSeconds(123);
|
||||
connection.WriteTimeout = TimeSpan.FromSeconds(456);
|
||||
|
||||
// Assert - part 1
|
||||
Assert.AreEqual("TCP", connection.Name);
|
||||
Assert.AreEqual(123, connection.ReadTimeout.TotalSeconds);
|
||||
Assert.AreEqual(456, connection.WriteTimeout.TotalSeconds);
|
||||
|
||||
Assert.AreEqual(_hostname, connection.Hostname);
|
||||
Assert.AreEqual(502, connection.Port);
|
||||
|
||||
// Assert - part 2
|
||||
_tcpClientMock.VerifySet(c => c.ReceiveTimeout = 123000, Times.Once);
|
||||
_tcpClientMock.VerifySet(c => c.SendTimeout = 456000, Times.Once);
|
||||
|
||||
_tcpClientMock.VerifyNoOtherCalls();
|
||||
_networkStreamMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(null)]
|
||||
[DataRow("")]
|
||||
[DataRow(" ")]
|
||||
public void ShouldThrowArgumentNullExceptionForInvalidHostname(string hostname)
|
||||
{
|
||||
// Arrange
|
||||
var connection = GetTcpConnection();
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentNullException>(() => connection.Hostname = hostname);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(0)]
|
||||
[DataRow(65536)]
|
||||
public void ShouldThrowArgumentOutOfRangeExceptionForInvalidPort(int port)
|
||||
{
|
||||
// Arrange
|
||||
var connection = GetTcpConnection();
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => connection.Port = port);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldBeAbleToDisposeMultipleTimes()
|
||||
{
|
||||
// Arrange
|
||||
var connection = GetConnection();
|
||||
|
||||
// Act
|
||||
connection.Dispose();
|
||||
connection.Dispose();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldThrowDisposedExceptionOnInvokeAsync()
|
||||
{
|
||||
// Arrange
|
||||
var connection = GetConnection();
|
||||
connection.Dispose();
|
||||
|
||||
// Act + Assert
|
||||
await Assert.ThrowsExactlyAsync<ObjectDisposedException>(() => connection.InvokeAsync(null, null));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(null)]
|
||||
[DataRow(new byte[0])]
|
||||
public async Task ShouldThrowArgumentNullExceptionForMissingRequestOnInvokeAsync(byte[] request)
|
||||
{
|
||||
// Arrange
|
||||
var connection = GetConnection();
|
||||
|
||||
// Act + Assert
|
||||
await Assert.ThrowsExactlyAsync<ArgumentNullException>(() => connection.InvokeAsync(request, null));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldThrowArgumentNullExceptionForMissingValidationOnInvokeAsync()
|
||||
{
|
||||
// Arrange
|
||||
byte[] request = new byte[1];
|
||||
var connection = GetConnection();
|
||||
|
||||
// Act + Assert
|
||||
await Assert.ThrowsExactlyAsync<ArgumentNullException>(() => connection.InvokeAsync(request, null));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldInvokeAsync()
|
||||
{
|
||||
// Arrange
|
||||
byte[] request = [1, 2, 3];
|
||||
byte[] expectedResponse = [9, 8, 7];
|
||||
var validation = new Func<IReadOnlyList<byte>, bool>(_ => true);
|
||||
_networkResponseQueue.Enqueue(expectedResponse);
|
||||
|
||||
var connection = GetConnection();
|
||||
|
||||
// Act
|
||||
var response = await connection.InvokeAsync(request, validation);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(response);
|
||||
|
||||
CollectionAssert.AreEqual(expectedResponse, response.ToArray());
|
||||
CollectionAssert.AreEqual(request, _networkRequestCallbacks.First());
|
||||
|
||||
_tcpClientMock.Verify(c => c.ConnectAsync(It.IsAny<IPAddress>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_tcpClientMock.Verify(c => c.Connected, Times.Once);
|
||||
_tcpClientMock.Verify(c => c.GetStream(), Times.Once);
|
||||
|
||||
_networkStreamMock.Verify(ns => ns.FlushAsync(It.IsAny<CancellationToken>()), Times.Once);
|
||||
_networkStreamMock.Verify(ns => ns.WriteAsync(It.IsAny<ReadOnlyMemory<byte>>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_networkStreamMock.Verify(ns => ns.ReadAsync(It.IsAny<Memory<byte>>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
|
||||
_tcpClientMock.VerifyNoOtherCalls();
|
||||
_networkStreamMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldConnectAndDisconnectOnInvokeAsync()
|
||||
{
|
||||
// Arrange
|
||||
_alwaysConnected = false;
|
||||
_connectedQueue.Enqueue(false);
|
||||
_connectedQueue.Enqueue(true);
|
||||
_connectedQueue.Enqueue(true);
|
||||
|
||||
byte[] request = [1, 2, 3];
|
||||
byte[] expectedResponse = [9, 8, 7];
|
||||
var validation = new Func<IReadOnlyList<byte>, bool>(_ => true);
|
||||
_networkResponseQueue.Enqueue(expectedResponse);
|
||||
|
||||
var connection = GetConnection();
|
||||
connection.IdleTimeout = TimeSpan.FromMilliseconds(200);
|
||||
|
||||
// Act
|
||||
var response = await connection.InvokeAsync(request, validation);
|
||||
await Task.Delay(500);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(response);
|
||||
|
||||
CollectionAssert.AreEqual(expectedResponse, response.ToArray());
|
||||
CollectionAssert.AreEqual(request, _networkRequestCallbacks.First());
|
||||
|
||||
_tcpClientMock.Verify(c => c.Connected, Times.Exactly(3));
|
||||
_tcpClientMock.Verify(c => c.Close(), Times.Exactly(2));
|
||||
_tcpClientMock.Verify(c => c.Dispose(), Times.Once);
|
||||
_tcpClientMock.Verify(c => c.ConnectAsync(It.IsAny<IPAddress>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.Exactly(2));
|
||||
_tcpClientMock.Verify(c => c.GetStream(), Times.Once);
|
||||
|
||||
_networkStreamMock.Verify(ns => ns.FlushAsync(It.IsAny<CancellationToken>()), Times.Once);
|
||||
_networkStreamMock.Verify(ns => ns.WriteAsync(It.IsAny<ReadOnlyMemory<byte>>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_networkStreamMock.Verify(ns => ns.ReadAsync(It.IsAny<Memory<byte>>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
|
||||
_tcpClientMock.VerifyNoOtherCalls();
|
||||
_networkStreamMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldThrowEndOfStreamExceptionOnInvokeAsync()
|
||||
{
|
||||
// Arrange
|
||||
byte[] request = [1, 2, 3];
|
||||
var validation = new Func<IReadOnlyList<byte>, bool>(_ => true);
|
||||
|
||||
var connection = GetConnection();
|
||||
|
||||
// Act + Assert
|
||||
await Assert.ThrowsExactlyAsync<EndOfStreamException>(() => connection.InvokeAsync(request, validation));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldThrowApplicationExceptionWhenHostNotResolvableOnInvokeAsync()
|
||||
{
|
||||
// Arrange
|
||||
_alwaysConnected = false;
|
||||
_connectedQueue.Enqueue(false);
|
||||
|
||||
byte[] request = [1, 2, 3];
|
||||
var validation = new Func<IReadOnlyList<byte>, bool>(_ => true);
|
||||
|
||||
var connection = GetConnection();
|
||||
connection.GetType().GetField("_hostname", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(connection, "");
|
||||
|
||||
// Act + Assert
|
||||
await Assert.ThrowsExactlyAsync<ApplicationException>(() => connection.InvokeAsync(request, validation));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldSkipCloseOnTimeoutOnInvokeAsync()
|
||||
{
|
||||
// Arrange
|
||||
_alwaysConnected = false;
|
||||
_connectedQueue.Enqueue(false);
|
||||
_connectedQueue.Enqueue(true);
|
||||
_connectedQueue.Enqueue(false);
|
||||
|
||||
byte[] request = [1, 2, 3];
|
||||
byte[] expectedResponse = [9, 8, 7];
|
||||
var validation = new Func<IReadOnlyList<byte>, bool>(_ => true);
|
||||
_networkResponseQueue.Enqueue(expectedResponse);
|
||||
|
||||
var connection = GetConnection();
|
||||
connection.IdleTimeout = TimeSpan.FromMilliseconds(200);
|
||||
|
||||
// Act
|
||||
var response = await connection.InvokeAsync(request, validation);
|
||||
await Task.Delay(500);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(response);
|
||||
|
||||
CollectionAssert.AreEqual(expectedResponse, response.ToArray());
|
||||
CollectionAssert.AreEqual(request, _networkRequestCallbacks.First());
|
||||
|
||||
_tcpClientMock.Verify(c => c.Connected, Times.Exactly(3));
|
||||
_tcpClientMock.Verify(c => c.Close(), Times.Once);
|
||||
_tcpClientMock.Verify(c => c.Dispose(), Times.Once);
|
||||
_tcpClientMock.Verify(c => c.ConnectAsync(It.IsAny<IPAddress>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.Exactly(2));
|
||||
_tcpClientMock.Verify(c => c.GetStream(), Times.Once);
|
||||
|
||||
_networkStreamMock.Verify(ns => ns.FlushAsync(It.IsAny<CancellationToken>()), Times.Once);
|
||||
_networkStreamMock.Verify(ns => ns.WriteAsync(It.IsAny<ReadOnlyMemory<byte>>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_networkStreamMock.Verify(ns => ns.ReadAsync(It.IsAny<Memory<byte>>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
|
||||
_tcpClientMock.VerifyNoOtherCalls();
|
||||
_networkStreamMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldRetryToConnectOnInvokeAsync()
|
||||
{
|
||||
// Arrange
|
||||
_alwaysConnected = false;
|
||||
_connectedQueue.Enqueue(false);
|
||||
_connectedQueue.Enqueue(false);
|
||||
_connectedQueue.Enqueue(true);
|
||||
|
||||
byte[] request = [1, 2, 3];
|
||||
byte[] expectedResponse = [9, 8, 7];
|
||||
var validation = new Func<IReadOnlyList<byte>, bool>(_ => true);
|
||||
_networkResponseQueue.Enqueue(expectedResponse);
|
||||
|
||||
var connection = GetConnection();
|
||||
|
||||
// Act
|
||||
var response = await connection.InvokeAsync(request, validation);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(response);
|
||||
|
||||
CollectionAssert.AreEqual(expectedResponse, response.ToArray());
|
||||
CollectionAssert.AreEqual(request, _networkRequestCallbacks.First());
|
||||
|
||||
_tcpClientMock.Verify(c => c.Connected, Times.Exactly(3));
|
||||
_tcpClientMock.Verify(c => c.Close(), Times.Exactly(2));
|
||||
_tcpClientMock.Verify(c => c.Dispose(), Times.Exactly(2));
|
||||
_tcpClientMock.Verify(c => c.ConnectAsync(It.IsAny<IPAddress>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.Exactly(3));
|
||||
_tcpClientMock.Verify(c => c.GetStream(), Times.Once);
|
||||
|
||||
_networkStreamMock.Verify(ns => ns.FlushAsync(It.IsAny<CancellationToken>()), Times.Once);
|
||||
_networkStreamMock.Verify(ns => ns.WriteAsync(It.IsAny<ReadOnlyMemory<byte>>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_networkStreamMock.Verify(ns => ns.ReadAsync(It.IsAny<Memory<byte>>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
|
||||
_tcpClientMock.VerifyNoOtherCalls();
|
||||
_networkStreamMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldThrowTaskCanceledExceptionForDisposeOnInvokeAsync()
|
||||
{
|
||||
// Arrange
|
||||
byte[] request = [1, 2, 3];
|
||||
var validation = new Func<IReadOnlyList<byte>, bool>(_ => true);
|
||||
|
||||
var connection = GetConnection();
|
||||
_networkStreamMock
|
||||
.Setup(ns => ns.WriteAsync(It.IsAny<ReadOnlyMemory<byte>>(), It.IsAny<CancellationToken>()))
|
||||
.Returns(new ValueTask(Task.Delay(100)));
|
||||
|
||||
// Act + Assert
|
||||
await Assert.ThrowsExactlyAsync<TaskCanceledException>(async () =>
|
||||
{
|
||||
var task = connection.InvokeAsync(request, validation);
|
||||
connection.Dispose();
|
||||
await task;
|
||||
});
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldThrowTaskCanceledExceptionForCancelOnInvokeAsync()
|
||||
{
|
||||
// Arrange
|
||||
byte[] request = [1, 2, 3];
|
||||
var validation = new Func<IReadOnlyList<byte>, bool>(_ => true);
|
||||
using var cts = new CancellationTokenSource();
|
||||
|
||||
var connection = GetConnection();
|
||||
_networkStreamMock
|
||||
.Setup(ns => ns.WriteAsync(It.IsAny<ReadOnlyMemory<byte>>(), It.IsAny<CancellationToken>()))
|
||||
.Returns(new ValueTask(Task.Delay(100)));
|
||||
|
||||
// Act + Assert
|
||||
await Assert.ThrowsExactlyAsync<TaskCanceledException>(async () =>
|
||||
{
|
||||
var task = connection.InvokeAsync(request, validation, cts.Token);
|
||||
cts.Cancel();
|
||||
await task;
|
||||
});
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldRemoveRequestFromQueueOnInvokeAsync()
|
||||
{
|
||||
// Arrange
|
||||
byte[] request = [1, 2, 3];
|
||||
byte[] expectedResponse = [9, 8, 7];
|
||||
var validation = new Func<IReadOnlyList<byte>, bool>(_ => true);
|
||||
_networkResponseQueue.Enqueue(expectedResponse);
|
||||
using var cts = new CancellationTokenSource();
|
||||
|
||||
var connection = GetConnection();
|
||||
_networkStreamMock
|
||||
.Setup(ns => ns.WriteAsync(It.IsAny<ReadOnlyMemory<byte>>(), It.IsAny<CancellationToken>()))
|
||||
.Callback<ReadOnlyMemory<byte>, CancellationToken>((req, _) => _networkRequestCallbacks.Add(req.ToArray()))
|
||||
.Returns(new ValueTask(Task.Delay(100)));
|
||||
|
||||
// Act
|
||||
var taskToComplete = connection.InvokeAsync(request, validation);
|
||||
|
||||
var taskToCancel = connection.InvokeAsync(request, validation, cts.Token);
|
||||
cts.Cancel();
|
||||
|
||||
var response = await taskToComplete;
|
||||
|
||||
// Assert - Part 1
|
||||
try
|
||||
{
|
||||
await taskToCancel;
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{ /* expected exception */ }
|
||||
|
||||
// Assert - Part 2
|
||||
Assert.AreEqual(1, _networkRequestCallbacks.Count);
|
||||
CollectionAssert.AreEqual(request, _networkRequestCallbacks.First());
|
||||
CollectionAssert.AreEqual(expectedResponse, response.ToArray());
|
||||
|
||||
_tcpClientMock.Verify(c => c.Connected, Times.Once);
|
||||
_tcpClientMock.Verify(c => c.ConnectAsync(It.IsAny<IPAddress>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_tcpClientMock.Verify(c => c.GetStream(), Times.Once);
|
||||
|
||||
_networkStreamMock.Verify(ns => ns.FlushAsync(It.IsAny<CancellationToken>()), Times.Once);
|
||||
_networkStreamMock.Verify(ns => ns.WriteAsync(It.IsAny<ReadOnlyMemory<byte>>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_networkStreamMock.Verify(ns => ns.ReadAsync(It.IsAny<Memory<byte>>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
|
||||
_tcpClientMock.VerifyNoOtherCalls();
|
||||
_networkStreamMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldRemoveRequestFromQueueOnDispose()
|
||||
{
|
||||
// Arrange
|
||||
byte[] request = [1, 2, 3];
|
||||
var validation = new Func<IReadOnlyList<byte>, bool>(_ => true);
|
||||
|
||||
var connection = GetConnection();
|
||||
_networkStreamMock
|
||||
.Setup(ns => ns.WriteAsync(It.IsAny<ReadOnlyMemory<byte>>(), It.IsAny<CancellationToken>()))
|
||||
.Callback<ReadOnlyMemory<byte>, CancellationToken>((req, _) => _networkRequestCallbacks.Add(req.ToArray()))
|
||||
.Returns(new ValueTask(Task.Delay(100)));
|
||||
|
||||
// Act
|
||||
var taskToCancel = connection.InvokeAsync(request, validation);
|
||||
var taskToDequeue = connection.InvokeAsync(request, validation);
|
||||
connection.Dispose();
|
||||
|
||||
// Assert
|
||||
try
|
||||
{
|
||||
await taskToCancel;
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{ /* expected exception */ }
|
||||
|
||||
try
|
||||
{
|
||||
await taskToDequeue;
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{ /* expected exception */ }
|
||||
|
||||
Assert.AreEqual(1, _networkRequestCallbacks.Count);
|
||||
CollectionAssert.AreEqual(request, _networkRequestCallbacks.First());
|
||||
|
||||
_tcpClientMock.Verify(c => c.Connected, Times.Once);
|
||||
_tcpClientMock.Verify(c => c.ConnectAsync(It.IsAny<IPAddress>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_tcpClientMock.Verify(c => c.GetStream(), Times.Once);
|
||||
_tcpClientMock.Verify(c => c.Dispose(), Times.Once);
|
||||
|
||||
_networkStreamMock.Verify(ns => ns.FlushAsync(It.IsAny<CancellationToken>()), Times.Once);
|
||||
_networkStreamMock.Verify(ns => ns.WriteAsync(It.IsAny<ReadOnlyMemory<byte>>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
|
||||
_tcpClientMock.VerifyNoOtherCalls();
|
||||
_networkStreamMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
private IModbusConnection GetConnection()
|
||||
=> GetTcpConnection();
|
||||
|
||||
private ModbusTcpConnection GetTcpConnection()
|
||||
{
|
||||
_networkStreamMock = new Mock<NetworkStreamWrapper>(null);
|
||||
_networkStreamMock
|
||||
.Setup(ns => ns.WriteAsync(It.IsAny<ReadOnlyMemory<byte>>(), It.IsAny<CancellationToken>()))
|
||||
.Callback<ReadOnlyMemory<byte>, CancellationToken>((req, _) => _networkRequestCallbacks.Add(req.ToArray()))
|
||||
.Returns(ValueTask.CompletedTask);
|
||||
_networkStreamMock
|
||||
.Setup(ns => ns.ReadAsync(It.IsAny<Memory<byte>>(), It.IsAny<CancellationToken>()))
|
||||
.Returns<Memory<byte>, CancellationToken>((buffer, _) =>
|
||||
{
|
||||
if (_networkResponseQueue.TryDequeue(out byte[] bytes))
|
||||
{
|
||||
bytes.CopyTo(buffer);
|
||||
return ValueTask.FromResult(bytes.Length);
|
||||
}
|
||||
|
||||
return ValueTask.FromResult(0);
|
||||
});
|
||||
|
||||
_tcpClientMock = new Mock<TcpClientWrapper>(AddressFamily.Unknown);
|
||||
_tcpClientMock.Setup(c => c.Connected).Returns(() => _alwaysConnected || _connectedQueue.Dequeue());
|
||||
_tcpClientMock.Setup(c => c.ReceiveTimeout).Returns(() => _clientReceiveTimeout);
|
||||
_tcpClientMock.Setup(c => c.SendTimeout).Returns(() => _clientSendTimeout);
|
||||
|
||||
_tcpClientMock
|
||||
.Setup(c => c.ConnectAsync(It.IsAny<IPAddress>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
|
||||
.Returns(() => _clientConnectTask);
|
||||
|
||||
_tcpClientMock
|
||||
.Setup(c => c.GetStream())
|
||||
.Returns(() => _networkStreamMock.Object);
|
||||
|
||||
_tcpClientFactoryMock = new Mock<TcpClientWrapperFactory>();
|
||||
_tcpClientFactoryMock
|
||||
.Setup(c => c.Create(It.IsAny<AddressFamily>(), It.IsAny<TimeSpan>(), It.IsAny<TimeSpan>()))
|
||||
.Returns(_tcpClientMock.Object);
|
||||
|
||||
var connection = new ModbusTcpConnection
|
||||
{
|
||||
Hostname = _hostname,
|
||||
Port = 502
|
||||
};
|
||||
|
||||
// Replace real connection with mock
|
||||
var factoryField = connection.GetType().GetField("_tcpClientFactory", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
(factoryField.GetValue(connection) as TcpClientWrapper)?.Dispose();
|
||||
factoryField.SetValue(connection, _tcpClientFactoryMock.Object);
|
||||
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
}
|
||||
2148
test/AMWD.Protocols.Modbus.Tests/Tcp/ModbusTcpProxyTest.cs
Normal file
2148
test/AMWD.Protocols.Modbus.Tests/Tcp/ModbusTcpProxyTest.cs
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user