Skip to content

Commit 3252244

Browse files
authored
Test coverage pdu 100%. (#2755)
1 parent 527a305 commit 3252244

12 files changed

+79
-95
lines changed

pymodbus/pdu/decoders.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
from pymodbus.exceptions import MessageRegisterException, ModbusException
77
from pymodbus.logging import Log
88

9-
from .pdu import ExceptionResponse, ModbusPDU
9+
from .exceptionresponse import ExceptionResponse
10+
from .pdu import ModbusPDU
1011

1112

1213
class DecodePDU:

pymodbus/pdu/pdu.py

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -111,51 +111,6 @@ def calculateRtuFrameSize(cls, data: bytes) -> int:
111111
)
112112

113113

114-
class ExceptionResponse(ModbusPDU):
115-
"""Base class for a modbus exception PDU."""
116-
117-
rtu_frame_size = 5
118-
119-
ILLEGAL_FUNCTION = 0x01
120-
ILLEGAL_ADDRESS = 0x02
121-
ILLEGAL_VALUE = 0x03
122-
DEVICE_FAILURE = 0x04
123-
ACKNOWLEDGE = 0x05
124-
DEVICE_BUSY = 0x06
125-
NEGATIVE_ACKNOWLEDGE = 0x07
126-
MEMORY_PARITY_ERROR = 0x08
127-
GATEWAY_PATH_UNAVIABLE = 0x0A
128-
GATEWAY_NO_RESPONSE = 0x0B
129-
130-
def __init__(
131-
self,
132-
function_code: int,
133-
exception_code: int = 0,
134-
device_id: int = 1,
135-
transaction: int = 0) -> None:
136-
"""Initialize the modbus exception response."""
137-
super().__init__(transaction_id=transaction, dev_id=device_id)
138-
self.function_code = function_code | 0x80
139-
self.exception_code = exception_code
140-
141-
def __str__(self) -> str:
142-
"""Build a representation of an exception response."""
143-
return (
144-
f"{self.__class__.__name__}("
145-
f"dev_id={self.dev_id}, "
146-
f"function_code={self.function_code}, "
147-
f"exception_code={self.exception_code})"
148-
)
149-
150-
def encode(self) -> bytes:
151-
"""Encode a modbus exception response."""
152-
return struct.pack(">B", self.exception_code)
153-
154-
def decode(self, data: bytes) -> None:
155-
"""Decode a modbus exception response."""
156-
self.exception_code = int(data[0])
157-
158-
159114
def pack_bitstring(bits: list[bool], align_byte=True) -> bytes:
160115
"""Create a bytestring out of a list of bits.
161116

pymodbus/server/requesthandler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from pymodbus.constants import ExcCodes
88
from pymodbus.exceptions import ModbusIOException, NoSuchIdException
99
from pymodbus.logging import Log
10-
from pymodbus.pdu.pdu import ExceptionResponse
10+
from pymodbus.pdu import ExceptionResponse
1111
from pymodbus.transaction import TransactionManager
1212
from pymodbus.transport import CommParams
1313

test/conftest.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import pytest
99
import pytest_asyncio
1010

11+
from pymodbus.constants import ExcCodes
1112
from pymodbus.datastore import ModbusBaseDeviceContext
1213
from pymodbus.server import ServerAsyncStop
1314
from pymodbus.transport import NULLMODEM_HOST, CommParams, CommType
@@ -218,6 +219,8 @@ def __init__(self, valid=False, default=True):
218219

219220
def getValues(self, _fc, _address, count=0):
220221
"""Get values."""
222+
if count > 0x100:
223+
return ExcCodes.ILLEGAL_VALUE
221224
return [self.default] * count
222225

223226
def setValues(self, _fc, _address, _values):

test/pdu/test_bit.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import pytest
55

66
import pymodbus.pdu.bit_message as bit_msg
7+
from pymodbus.constants import ExcCodes
78

89

910
class TestModbusBitMessage:
@@ -34,6 +35,18 @@ async def test_bit_read_update_datastore_value_errors(self, mock_context):
3435
):
3536
await pdu.update_datastore(context)
3637

38+
async def test_bit_datastore_exceptions(self, mock_context):
39+
"""Test bit exception response from datastore."""
40+
context = mock_context()
41+
context.async_getValues = mock.AsyncMock(return_value=ExcCodes.ILLEGAL_VALUE)
42+
for pdu in (
43+
(bit_msg.ReadCoilsRequest(address=1, count=0x800)),
44+
(bit_msg.ReadDiscreteInputsRequest(address=1, count=0x800)),
45+
(bit_msg.WriteSingleCoilRequest(address=1, bits=[True])),
46+
(bit_msg.WriteMultipleCoilsRequest(address=1, bits=[True] * 5)),
47+
):
48+
await pdu.update_datastore(context)
49+
3750
async def test_bit_read_update_datastore_address_errors(self, mock_context):
3851
"""Test bit read request encoding."""
3952
context = mock_context()

test/pdu/test_decoders.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,17 @@ def test_client_lookup(self, code, frame):
100100
if not code & 0x80:
101101
assert pdu.function_code == code
102102

103+
def test_client_lookup_no_fc(self):
104+
"""Test lookup for responses."""
105+
data = b'\x01\x70'
106+
pdu = self.client.lookupPduClass(data)
107+
assert not pdu
108+
109+
def test_list_function_codes(self):
110+
"""Test lookup for responses."""
111+
fc_list = self.client.list_function_codes()
112+
assert fc_list
113+
103114
@pytest.mark.parametrize(("code", "frame"), list(requests))
104115
def test_server_lookup(self, code, frame):
105116
"""Test lookup for requests."""

test/not_updated/test_device.py renamed to test/pdu/test_device.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,3 +362,10 @@ def test_modbus_plus_statistics_helpers(self):
362362
stats_summary = list(statistics.summary())
363363
assert sorted(summary) == sorted(stats_summary)
364364
assert not sum(sum(value[1]) for value in statistics)
365+
366+
367+
def test_device_info_name(self):
368+
"""Test setting of info_name."""
369+
name = "Maintainer"
370+
info = ModbusDeviceIdentification(info_name={"VendorName": name})
371+
assert name == info.VendorName
File renamed without changes.

test/pdu/test_mei_messages.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,14 @@ def test_read_device_information_calc1(self):
8181
assert handle.calculateRtuFrameSize(b"\x0e\x01\x83") == 999
8282
assert handle.calculateRtuFrameSize(b"\x0e\x01\x83\x00\x00\x03\x01\x03") == 998
8383

84+
85+
def test_read_device_information_sub_fc(self):
86+
"""Test calculateRtuFrameSize, short buffer."""
87+
handle = ReadDeviceInformationResponse()
88+
assert handle.decode_sub_function_code(b"\x0e\x01\x83") == 0x83
89+
handle = ReadDeviceInformationRequest()
90+
assert handle.decode_sub_function_code(b"\x0e\x01\x83") == 0x83
91+
8492
def test_read_device_information_encode(self):
8593
"""Test that the read fifo queue response can encode."""
8694
message = b"\x0e\x01\x83\x00\x00\x03"

test/pdu/test_register_read_messages.py

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,6 @@
1717

1818
TEST_MESSAGE = b"\x06\x00\x0a\x00\x0b\x00\x0c"
1919

20-
# ---------------------------------------------------------------------------#
21-
# Fixture
22-
# ---------------------------------------------------------------------------#
23-
24-
2520
class TestReadRegisterMessages:
2621
"""Register Message Test Fixture.
2722
@@ -86,14 +81,15 @@ def test_register_read_response_decode_error(self):
8681
with pytest.raises(ModbusIOException):
8782
reg.decode(b'\x14\x00\x03\x00\x11')
8883

89-
async def test_register_read_requests_count_errors(self):
84+
async def test_register_read_requests_count_errors(self, mock_context):
9085
"""This tests that the register request messages.
9186
9287
will break on counts that are out of range
9388
"""
89+
context = mock_context()
9490
requests = [
95-
#ReadHoldingRegistersRequest(address=1, count=0x800),
96-
#ReadInputRegistersRequest(address=1, count=0x800),
91+
ReadHoldingRegistersRequest(address=1, count=0x800),
92+
ReadInputRegistersRequest(address=1, count=0x800),
9793
ReadWriteMultipleRegistersRequest(
9894
read_address=1, read_count=0x800, write_address=1, write_registers=[5]
9995
),
@@ -102,7 +98,7 @@ async def test_register_read_requests_count_errors(self):
10298
),
10399
]
104100
for request in requests:
105-
result = await request.update_datastore(None)
101+
result = await request.update_datastore(context)
106102
assert result.exception_code == ExcCodes.ILLEGAL_VALUE
107103

108104
async def test_register_read_requests_verify_errors(self, mock_context):
@@ -114,8 +110,8 @@ async def test_register_read_requests_verify_errors(self, mock_context):
114110
requests = [
115111
ReadHoldingRegistersRequest(address=-1, count=5),
116112
ReadInputRegistersRequest(address=-1, count=5),
117-
# ReadWriteMultipleRegistersRequest(-1,5,1,5),
118-
# ReadWriteMultipleRegistersRequest(1,5,-1,5),
113+
ReadWriteMultipleRegistersRequest(-1,5,1,[5]),
114+
ReadWriteMultipleRegistersRequest(1,5,-1,[5]),
119115
]
120116
for request in requests:
121117
await request.update_datastore(context)
@@ -147,17 +143,28 @@ async def test_read_write_multiple_registers_verify(self, mock_context):
147143
"""Test read/write multiple registers."""
148144
context = mock_context()
149145
request = ReadWriteMultipleRegistersRequest(
150-
read_address=1, read_count=10, write_address=2, write_registers=[0x00]
146+
read_address=1, read_count=0x200, write_address=2, write_registers=[0x00]
151147
)
152-
await request.update_datastore(context)
153-
#assert response.exception_code == ExcCodes.ILLEGAL_ADDRESS
148+
response = await request.update_datastore(context)
149+
assert response.exception_code == ExcCodes.ILLEGAL_VALUE
154150

155151
await request.update_datastore(context)
156-
#assert response.exception_code == ExcCodes.ILLEGAL_ADDRESS
152+
assert response.exception_code == ExcCodes.ILLEGAL_VALUE
157153

158154
request.write_byte_count = 0x100
159155
await request.update_datastore(context)
160-
#assert response.exception_code == ExcCodes.ILLEGAL_VALUE
156+
assert response.exception_code == ExcCodes.ILLEGAL_VALUE
157+
158+
async def test_register_datastore_exceptions(self, mock_context):
159+
"""Test exception response from datastore."""
160+
context = mock_context()
161+
context.async_getValues = mock.AsyncMock(return_value=ExcCodes.ILLEGAL_VALUE)
162+
for pdu in (
163+
ReadHoldingRegistersRequest(address=-1, count=5),
164+
ReadInputRegistersRequest(address=-1, count=5),
165+
ReadWriteMultipleRegistersRequest(-1, 5, 1, [5], 1),
166+
):
167+
await pdu.update_datastore(context)
161168

162169
def test_serializing_to_string(self):
163170
"""Test serializing to string."""

0 commit comments

Comments
 (0)