From f4c71b08194cca98b9e8bdb9630904520456a0ea Mon Sep 17 00:00:00 2001 From: jan iversen Date: Fri, 13 Feb 2026 19:29:45 +0100 Subject: [PATCH 01/10] Fix smaller bugs in test, part 2. --- test/server/test_server_asyncio.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/server/test_server_asyncio.py b/test/server/test_server_asyncio.py index 45c81acaf..d5b2b2fca 100755 --- a/test/server/test_server_asyncio.py +++ b/test/server/test_server_asyncio.py @@ -33,14 +33,14 @@ class BasicClient(asyncio.BaseProtocol): """Basic client.""" - connected = None - data = None - dataTo = None - received_data = None - done = None - eof = None - transport = None - protocol = None + connected: asyncio.Future + data: bytes + dataTo: bytes + received_data: bytes + done: asyncio.Future + eof: asyncio.Future + transport: asyncio.BaseTransport | None = None + protocol: asyncio.BaseProtocol | None = None def connection_made(self, transport): """Get Connection made.""" From f205985dd757f3a7883764ccf921d49b41ef89c5 Mon Sep 17 00:00:00 2001 From: jan iversen Date: Sat, 14 Feb 2026 17:39:26 +0100 Subject: [PATCH 02/10] test_delayed_response. --- test/transaction/test_transaction.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/transaction/test_transaction.py b/test/transaction/test_transaction.py index 5710a7e61..1d463d687 100755 --- a/test/transaction/test_transaction.py +++ b/test/transaction/test_transaction.py @@ -365,7 +365,6 @@ async def test_delayed_response(self, use_clc, framer, scenario): None, None, ) - transact.send = mock.Mock() # type: ignore[method-assign] request1 = ReadCoilsRequest(address=117, count=5, dev_id=1) request2 = ReadCoilsRequest(address=118, count=2, dev_id=1) response1 = ReadCoilsResponse(bits=[True, False, True, True] + [False]*4, dev_id=1) @@ -379,6 +378,7 @@ async def test_delayed_response(self, use_clc, framer, scenario): transact.retries = 1 transact.connection_made(mock.AsyncMock()) transact.transport.write = mock.Mock() # type: ignore[attr-defined] + transact.send = mock.Mock() # type: ignore[method-assign] transact.comm_params.timeout_connect = 0.1 if scenario == 0: # timeout + double response @@ -390,7 +390,6 @@ async def test_delayed_response(self, use_clc, framer, scenario): assert result.bits == response1.bits else: # if scenario == 1: # timeout + new request + double response resp = asyncio.create_task(transact.execute(False, request1)) - await asyncio.sleep(0.25) with pytest.raises(ModbusIOException): await resp resp = asyncio.create_task(transact.execute(False, request2)) From 7b6810c7b6435a4ab88e1260734a31171b4aed32 Mon Sep 17 00:00:00 2001 From: jan iversen Date: Sat, 14 Feb 2026 18:07:29 +0100 Subject: [PATCH 03/10] temp. --- test/server/test_base.py | 5 +- test/server/test_requesthandler.py | 2 +- test/server/test_server_asyncio.py | 75 +++++++++++++++--------------- test/server/test_startstop.py | 2 +- 4 files changed, 43 insertions(+), 41 deletions(-) diff --git a/test/server/test_base.py b/test/server/test_base.py index 4bec6dcd1..e6377d288 100755 --- a/test/server/test_base.py +++ b/test/server/test_base.py @@ -4,6 +4,7 @@ import pytest from pymodbus.datastore import ModbusServerContext +from pymodbus.framer import FramerType from pymodbus.pdu import ReadHoldingRegistersRequest from pymodbus.server import ModbusBaseServer from pymodbus.simulator import SimData, SimDevice @@ -28,7 +29,7 @@ async def baseserver(self): False, False, None, - "socket", + FramerType.SOCKET, None, None, None, @@ -53,7 +54,7 @@ async def test_base_simcore(self): False, False, None, - "socket", + FramerType.SOCKET, None, None, None, diff --git a/test/server/test_requesthandler.py b/test/server/test_requesthandler.py index 0b21b76f0..f87932cba 100755 --- a/test/server/test_requesthandler.py +++ b/test/server/test_requesthandler.py @@ -66,7 +66,7 @@ async def test_rh_handle_request(self, requesthandler): await requesthandler.handle_request() requesthandler.last_pdu = ExceptionResponse(17) await requesthandler.handle_request() - requesthandler.last_pdu.datastore_update = mock.AsyncMock() + requesthandler.last_pdu.datastore_update = mock.AsyncMock() # type: ignore[method-assign] requesthandler.server.broadcast_enable = True requesthandler.last_pdu.dev_id = 0 await requesthandler.handle_request() diff --git a/test/server/test_server_asyncio.py b/test/server/test_server_asyncio.py index d5b2b2fca..887dc3a91 100755 --- a/test/server/test_server_asyncio.py +++ b/test/server/test_server_asyncio.py @@ -2,7 +2,7 @@ import asyncio import logging import ssl -from asyncio import CancelledError +from asyncio import CancelledError, Task from contextlib import suppress from unittest import mock @@ -20,6 +20,7 @@ ModbusTcpServer, ModbusTlsServer, ModbusUdpServer, + ModbusBaseServer, ) @@ -51,10 +52,10 @@ def connection_made(self, transport): self.transport = transport if BasicClient.data is not None: _logger.debug("TEST Client write data") - self.transport.write(BasicClient.data) + self.transport.write(BasicClient.data) # type: ignore[union-attr] if BasicClient.dataTo is not None: _logger.debug("TEST Client sendTo data") - self.transport.sendto(BasicClient.dataTo) + self.transport.sendto(BasicClient.dataTo) # type: ignore[union-attr] def data_received(self, data): """Get Data received.""" @@ -69,7 +70,7 @@ def datagram_received(self, data, _addr): BasicClient.received_data = data if BasicClient.done is not None: # pragma: no cover BasicClient.done.set_result(True) - self.transport.close() + self.transport.close() # type: ignore[union-attr] def connection_lost(self, exc): """EOF received.""" @@ -87,12 +88,12 @@ def clear(cls): if BasicClient.transport: BasicClient.transport.close() BasicClient.transport = None - BasicClient.data = None - BasicClient.connected = None - BasicClient.done = None - BasicClient.received_data = None - BasicClient.eof = None - BasicClient.my_protocol = None + BasicClient.data = None # type: ignore[assignment] + BasicClient.connected = None # type: ignore[assignment] + BasicClient.done = None # type: ignore[assignment] + BasicClient.received_data = None # type: ignore[assignment] + BasicClient.eof = None # type: ignore[assignment] + BasicClient.my_protocol = None # type: ignore[attr-defined] class TestAsyncioServer: @@ -104,12 +105,12 @@ class TestAsyncioServer: This test suite does not attempt to test any of the underlying protocol details """ - server = None - task = None - loop = None - store = None - context = None - identity = None + server: ModbusBaseServer | None = None + task: Task | None = None + loop: asyncio.AbstractEventLoop | None = None + store: ModbusDeviceContext | None = None + context: ModbusServerContext | None = None + identity: ModbusDeviceIdentification | None = None @pytest.fixture(autouse=True) async def _setup_teardown(self): @@ -206,13 +207,13 @@ async def connect_server(self): BasicClient.connected = asyncio.Future() BasicClient.done = asyncio.Future() BasicClient.eof = asyncio.Future() - random_port = self.server.transport.sockets[0].getsockname()[ + random_port = self.server.transport.sockets[0].getsockname()[ # type: ignore[union-attr] 1 ] # get the random server port ( BasicClient.transport, BasicClient.my_protocol, - ) = await self.loop.create_connection( + ) = await self.loop.create_connection( # type: ignore[union-attr] BasicClient, host="127.0.0.1", port=random_port ) await asyncio.wait_for(BasicClient.connected, timeout=0.1) @@ -230,7 +231,7 @@ async def test_async_tcp_server_serve_forever_twice(self): """Call on serve_forever() twice should result in a runtime error.""" await self.start_server() with pytest.raises(RuntimeError): - await self.server.serve_forever() + await self.server.serve_forever() # type: ignore[union-attr] async def test_async_tcp_server_receive_data(self): """Test data sent on socket is received by internals - doesn't not process data.""" @@ -257,12 +258,12 @@ async def test_async_server_trace_connect_disconnect(self): """Test connect/disconnect trace handler.""" trace_connect = mock.Mock() await self.start_server() - self.server.trace_connect = trace_connect + self.server.trace_connect = trace_connect # type: ignore[union-attr] await self.connect_server() trace_connect.assert_called_once_with(True) trace_connect.reset_mock() - BasicClient.transport.close() + BasicClient.transport.close() # type: ignore[union-attr] await asyncio.sleep(0.2) # so we have to wait a bit trace_connect.assert_called_once_with(False) @@ -271,7 +272,7 @@ async def test_async_tcp_server_connection_lost(self): await self.start_server() await self.connect_server() - BasicClient.transport.close() + BasicClient.transport.close() # type: ignore[union-attr] await asyncio.sleep(0.2) # so we have to wait a bit async def test_async_tcp_server_shutdown_connection(self): @@ -282,7 +283,7 @@ async def test_async_tcp_server_shutdown_connection(self): # On Windows we seem to need to give this an extra chance to finish, # otherwise there ends up being an active connection at the assert. await asyncio.sleep(0.5) - await self.server.shutdown() + await self.server.shutdown() # type: ignore[union-attr] async def test_async_tcp_server_no_device(self): """Test unknown device exception.""" @@ -293,7 +294,7 @@ async def test_async_tcp_server_no_device(self): await self.start_server() await self.connect_server() assert not BasicClient.eof.done() - await self.server.shutdown() + await self.server.shutdown() # type: ignore[union-attr] self.server = None async def test_async_tcp_server_modbus_error(self): @@ -314,44 +315,44 @@ async def test_async_start_tls_server_no_loop(self): """Test that the modbus tls asyncio server starts correctly.""" with mock.patch.object(ssl.SSLContext, "load_cert_chain"): await self.start_server(do_tls=True, do_forever=False, do_ident=True) - assert self.server.control.Identity.VendorName == "VendorName" + assert self.server.control.Identity.VendorName == "VendorName" # type: ignore[union-attr] async def test_async_start_tls_server(self): """Test that the modbus tls asyncio server starts correctly.""" with mock.patch.object(ssl.SSLContext, "load_cert_chain"): await self.start_server(do_tls=True, do_ident=True) - assert self.server.control.Identity.VendorName == "VendorName" + assert self.server.control.Identity.VendorName == "VendorName" # type: ignore[union-attr] async def test_async_tls_server_serve_forever_twice(self): """Call on serve_forever() twice should result in a runtime error.""" with mock.patch.object(ssl.SSLContext, "load_cert_chain"): await self.start_server(do_tls=True) with pytest.raises(RuntimeError): - await self.server.serve_forever() + await self.server.serve_forever() # type: ignore[union-attr] async def test_async_start_udp_server_no_loop(self): """Test that the modbus udp asyncio server starts correctly.""" await self.start_server(do_udp=True, do_forever=False, do_ident=True) - assert self.server.control.Identity.VendorName == "VendorName" - assert not self.server.transport + assert self.server.control.Identity.VendorName == "VendorName" # type: ignore[union-attr] + assert not self.server.transport # type: ignore[union-attr] async def test_async_start_udp_server(self): """Test that the modbus udp asyncio server starts correctly.""" await self.start_server(do_udp=True, do_ident=True) - assert self.server.control.Identity.VendorName == "VendorName" - assert self.server.transport + assert self.server.control.Identity.VendorName == "VendorName" # type: ignore[union-attr] + assert self.server.transport # type: ignore[union-attr] async def test_async_udp_server_serve_forever_close(self): """Test StarAsyncUdpServer serve_forever() method.""" await self.start_server(do_udp=True) - await self.server.shutdown() + await self.server.shutdown() # type: ignore[union-attr] self.server = None async def test_async_udp_server_serve_forever_twice(self): """Call on serve_forever() twice should result in a runtime error.""" await self.start_server(do_udp=True, do_ident=True) with pytest.raises(RuntimeError): - await self.server.serve_forever() + await self.server.serve_forever() # type: ignore[union-attr] async def test_async_udp_server_roundtrip(self): """Test sending and receiving data on udp socket.""" @@ -361,8 +362,8 @@ async def test_async_udp_server_roundtrip(self): BasicClient.dataTo = TEST_DATA # device 1, read register BasicClient.done = asyncio.Future() await self.start_server(do_udp=True) - random_port = self.server.transport._sock.getsockname()[1] # pylint: disable=protected-access - transport, _ = await self.loop.create_datagram_endpoint( + random_port = self.server.transport._sock.getsockname()[1] # type: ignore[union-attr] # pylint: disable=protected-access + transport, _ = await self.loop.create_datagram_endpoint( # type: ignore[union-attr] BasicClient, remote_addr=("127.0.0.1", random_port) ) await asyncio.wait_for(BasicClient.done, timeout=0.1) @@ -380,8 +381,8 @@ async def test_async_udp_server_exception(self): new_callable=lambda: mock.Mock(side_effect=Exception), ): # get the random server port pylint: disable=protected-access - random_port = self.server.transport._sock.getsockname()[1] - _, _ = await self.loop.create_datagram_endpoint( + random_port = self.server.transport._sock.getsockname()[1] # type: ignore[union-attr] + _, _ = await self.loop.create_datagram_endpoint( # type: ignore[union-attr] BasicClient, remote_addr=("127.0.0.1", random_port) ) await asyncio.wait_for(BasicClient.connected, timeout=0.1) diff --git a/test/server/test_startstop.py b/test/server/test_startstop.py index ede861ae7..e5247bcff 100755 --- a/test/server/test_startstop.py +++ b/test/server/test_startstop.py @@ -80,7 +80,7 @@ def test_ServerStop(self): thread.start() while not ModbusBaseServer.active_server: # pragma: no cover sleep(0.1) - ServerStop() + ServerStop() # type: ignore[unreachable] assert not ModbusBaseServer.active_server thread.join() From 6eb89f984c862a6e150b576af7883412caa26467 Mon Sep 17 00:00:00 2001 From: jan iversen Date: Tue, 17 Feb 2026 12:59:21 +0100 Subject: [PATCH 04/10] Server. --- pymodbus/pdu/pdu.py | 4 +--- test/framer/test_extras.py | 13 +------------ test/pdu/test_decoders.py | 2 ++ test/pdu/test_diag_messages.py | 15 +++++++++------ test/pdu/test_mei_messages.py | 8 +++++--- test/pdu/test_other_messages.py | 17 +++++++++-------- test/pdu/test_pdu.py | 16 +++++++++------- test/pdu/test_register_write_messages.py | 5 ----- test/server/test_server_asyncio.py | 9 +++++---- 9 files changed, 41 insertions(+), 48 deletions(-) diff --git a/pymodbus/pdu/pdu.py b/pymodbus/pdu/pdu.py index 6ef37a2b1..419c7c56a 100644 --- a/pymodbus/pdu/pdu.py +++ b/pymodbus/pdu/pdu.py @@ -3,7 +3,6 @@ import asyncio import struct -from abc import abstractmethod from ..datastore import ModbusServerContext from ..exceptions import ModbusIOException, NotImplementedException @@ -82,11 +81,10 @@ def get_response_pdu_size(self) -> int: """Calculate response pdu size.""" return 0 - @abstractmethod def encode(self) -> bytes: """Encode the message.""" + return b'' - @abstractmethod def decode(self, data: bytes) -> None: """Decode data part of the message.""" diff --git a/test/framer/test_extras.py b/test/framer/test_extras.py index f555fc4d0..bb5445695 100755 --- a/test/framer/test_extras.py +++ b/test/framer/test_extras.py @@ -19,18 +19,7 @@ class TestExtras: """Test for the framer module.""" - client = None - decoder = None - _tcp = None - _tls = None - _rtu = None - _ascii = None - _manager = None - _tm = None - - # ----------------------------------------------------------------------- # - # Test Construction - # ----------------------------------------------------------------------- # + def setup_method(self): """Set up the test environment.""" self.client = None diff --git a/test/pdu/test_decoders.py b/test/pdu/test_decoders.py index fec67e78e..4d1e382de 100644 --- a/test/pdu/test_decoders.py +++ b/test/pdu/test_decoders.py @@ -123,12 +123,14 @@ def test_server_lookup(self, code, frame): def test_client_decode(self, code, frame): """Test lookup for responses.""" pdu = self.client.decode(frame) + assert pdu assert pdu.function_code == code @pytest.mark.parametrize(("code", "frame"), list(requests)) def test_server_decode(self, code, frame): """Test lookup for requests.""" pdu = self.server.decode(frame) + assert pdu assert pdu.function_code == code @pytest.mark.parametrize(("frame"), [b'', b'NO FRAME']) diff --git a/test/pdu/test_diag_messages.py b/test/pdu/test_diag_messages.py index a439b5c51..21a678930 100644 --- a/test/pdu/test_diag_messages.py +++ b/test/pdu/test_diag_messages.py @@ -1,7 +1,10 @@ """Test diag messages.""" +from typing import cast + import pytest from pymodbus.constants import ModbusPlusOperation, ModbusStatus +from pymodbus.datastore import ModbusServerContext from pymodbus.pdu.diag_message import ( ChangeAsciiInputDelimiterRequest, ChangeAsciiInputDelimiterResponse, @@ -126,7 +129,7 @@ def test_diagnostic_encode_decode(self): def test_diagnostic_encode_error(self): """Testing diagnostic request/response can be decoded and encoded.""" msg_obj = DiagnosticBase() - msg_obj.message = "not allowed" + msg_obj.message = b"not allowed" with pytest.raises(TypeError): msg_obj.encode() @@ -167,7 +170,7 @@ def test_diagnostic_requests_encode(self): async def test_diagnostic_datastore_update(self): """Testing diagnostic message execution.""" for message, encoded, datastore_updated in self.requests: - encoded = (await message().datastore_update(None, 1)).encode() + encoded = (await message().datastore_update(cast(ModbusServerContext, None), 1)).encode() assert encoded == datastore_updated def test_return_query_data_request(self): @@ -199,10 +202,10 @@ def test_restart_communications_option(self): async def test_get_clear_modbus_plus_request_datastore_update(self): """Testing diagnostic message execution.""" request = GetClearModbusPlusRequest(message=ModbusPlusOperation.CLEAR_STATISTICS) - response = await request.datastore_update(None, 0) - assert response.message == ModbusPlusOperation.CLEAR_STATISTICS + response = await request.datastore_update(cast(ModbusServerContext, None), 0) + assert cast(GetClearModbusPlusResponse, response).message == ModbusPlusOperation.CLEAR_STATISTICS request = GetClearModbusPlusRequest(message=ModbusPlusOperation.GET_STATISTICS) - response = await request.datastore_update(None, 0) + response = await request.datastore_update(cast(ModbusServerContext, None), 0) resp = [ModbusPlusOperation.GET_STATISTICS] - assert response.message == resp + [0x00] * 55 + assert cast(GetClearModbusPlusResponse, response).message == resp + [0x00] * 55 diff --git a/test/pdu/test_mei_messages.py b/test/pdu/test_mei_messages.py index 661da8593..e72e28271 100644 --- a/test/pdu/test_mei_messages.py +++ b/test/pdu/test_mei_messages.py @@ -3,6 +3,8 @@ This fixture tests the functionality of all the mei based request/response messages: """ +from typing import cast + import pytest from pymodbus.constants import DeviceInformation @@ -61,7 +63,7 @@ async def test_read_device_information_request(self, mock_server_context): read_code=DeviceInformation.EXTENDED, object_id=0x80 ) result = await handle.datastore_update(context, 0) - assert result.information[0x81] == ["Test", "Repeated"] + assert cast(ReadDeviceInformationResponse, result).information[0x81] == ["Test", "Repeated"] async def test_read_device_information_request_error(self, mock_server_context): """Test basic bit message encoding/decoding.""" @@ -87,8 +89,8 @@ def test_read_device_information_sub_fc(self): """Test calculateRtuFrameSize, short buffer.""" handle = ReadDeviceInformationResponse() assert handle.decode_sub_function_code(b"\x0e\x01\x83") == 0x83 - handle = ReadDeviceInformationRequest() - assert handle.decode_sub_function_code(b"\x0e\x01\x83") == 0x83 + handle2 = ReadDeviceInformationRequest() + assert handle2.decode_sub_function_code(b"\x0e\x01\x83") == 0x83 def test_read_device_information_encode(self): """Test that the read fifo queue response can encode.""" diff --git a/test/pdu/test_other_messages.py b/test/pdu/test_other_messages.py index 5aa31fe32..6352eca93 100644 --- a/test/pdu/test_other_messages.py +++ b/test/pdu/test_other_messages.py @@ -1,4 +1,5 @@ """Test other messages.""" +from typing import cast from unittest import mock import pymodbus.pdu.other_message as pymodbus_message @@ -18,15 +19,15 @@ class TestOtherMessage: pymodbus_message.ReadExceptionStatusResponse(0x12), pymodbus_message.GetCommEventCounterResponse(0x12), pymodbus_message.GetCommEventLogResponse, - pymodbus_message.ReportDeviceIdResponse(0x12), + pymodbus_message.ReportDeviceIdResponse(b'\x12'), ] def test_other_messages_to_string(self): """Test other messages to string.""" for message in self.requests: assert str(message) - for message in self.responses: - assert str(message) + for message2 in self.responses: + assert str(message2) async def test_read_exception_status(self, mock_server_context): """Test read exception status.""" @@ -109,10 +110,10 @@ async def test_report_device_id_request(self, mock_server_context): request = pymodbus_message.ReportDeviceIdRequest() response = await request.datastore_update(context, 1) - assert response.identifier == expected_identity + assert cast(pymodbus_message.ReportDeviceIdResponse, response).identifier == expected_identity # Change to byte strings and test again (final result should be the same) - identity = { + identity2 = { 0x00: b"VN", # VendorName 0x01: b"PC", # ProductCode 0x02: b"REV", # MajorMinorRevision @@ -123,11 +124,11 @@ async def test_report_device_id_request(self, mock_server_context): 0x07: b"RA", # reserved 0x08: b"RB", # reserved } - dif.get.return_value = identity + dif.get.return_value = identity2 request = pymodbus_message.ReportDeviceIdRequest() response = await request.datastore_update(context, 0) - assert response.identifier == expected_identity + assert cast(pymodbus_message.ReportDeviceIdResponse, response).identifier == expected_identity async def test_report_device_id(self, mock_server_context): """Test report device_id.""" @@ -140,7 +141,7 @@ async def test_report_device_id(self, mock_server_context): assert (await request.datastore_update(context, 0)).function_code == 0x11 response = pymodbus_message.ReportDeviceIdResponse( - (await request.datastore_update(context, 0)).identifier, True + cast(pymodbus_message.ReportDeviceIdResponse, await request.datastore_update(context, 0)).identifier, True ) assert response.encode() == b"\tPymodbus\xff" diff --git a/test/pdu/test_pdu.py b/test/pdu/test_pdu.py index f774eeec2..6d3da81e2 100644 --- a/test/pdu/test_pdu.py +++ b/test/pdu/test_pdu.py @@ -1,4 +1,6 @@ """Test pdu.""" +from typing import Any + import pytest import pymodbus.pdu.bit_message as bit_msg @@ -47,7 +49,7 @@ def test_calculate_frame_size(self): ModbusPDU.calculateRtuFrameSize(b"") ModbusPDU.rtu_frame_size = 5 assert ModbusPDU.calculateRtuFrameSize(b"") == 5 - ModbusPDU.rtu_frame_size = None + ModbusPDU.rtu_frame_size = 0 ModbusPDU.rtu_byte_count_pos = 2 assert ( ModbusPDU.calculateRtuFrameSize( @@ -56,12 +58,12 @@ def test_calculate_frame_size(self): == 0x05 + 5 ) assert not ModbusPDU.calculateRtuFrameSize(b"\x11") - ModbusPDU.rtu_byte_count_pos = None + ModbusPDU.rtu_byte_count_pos = 0 with pytest.raises(NotImplementedException): ModbusPDU.calculateRtuFrameSize(b"") ModbusPDU.rtu_frame_size = 12 assert ModbusPDU.calculateRtuFrameSize(b"") == 12 - ModbusPDU.rtu_frame_size = None + ModbusPDU.rtu_frame_size = 0 ModbusPDU.rtu_byte_count_pos = 2 assert ( ModbusPDU.calculateRtuFrameSize( @@ -69,13 +71,13 @@ def test_calculate_frame_size(self): ) == 0x05 + 5 ) - ModbusPDU.rtu_byte_count_pos = None + ModbusPDU.rtu_byte_count_pos = 0 # -------------------------- # Test PDU types generically # -------------------------- - requests = [ + requests: list[tuple[type[ModbusPDU], tuple[()], dict[str, Any], bytes]] = [ (bit_msg.ReadCoilsRequest, (), {"address": 117, "count": 3}, b'\x01\x00\x75\x00\x03'), (bit_msg.ReadDiscreteInputsRequest, (), {"address": 117, "count": 3}, b'\x02\x00\x75\x00\x03'), (bit_msg.WriteSingleCoilRequest, (), {"address": 117, "bits": [True]}, b'\x05\x00\x75\xff\x00'), @@ -115,7 +117,7 @@ def test_calculate_frame_size(self): (reg_msg.MaskWriteRegisterRequest, (), {"address": 0x0104, "and_mask": 0xE1D2, "or_mask": 0x1234}, b'\x16\x01\x04\xe1\xd2\x12\x34'), ] - responses = [ + responses: list[tuple[type[ModbusPDU], tuple[()], dict[str, Any], bytes]] = [ (bit_msg.ReadCoilsResponse, (), {"bits": [True, True] + [False] * 6, "address": 17}, b'\x01\x01\x03'), (bit_msg.ReadDiscreteInputsResponse, (), {"bits": [True, True], "address": 17}, b'\x02\x01\x03'), (bit_msg.WriteSingleCoilResponse, (), {"address": 117, "bits": [True]}, b'\x05\x00\x75\xff\x00'), @@ -185,7 +187,7 @@ def test_pdu_instance_extras(self, pdutype, kwargs): def test_pdu_register_as_byte(self): """Test verify functions.""" - registers =[b'ab', b'cd'] + registers =[int.from_bytes(b'ab', 'big'), int.from_bytes(b'cd', 'big')] # NOT ALLOWED, NO conversion. req = reg_msg.ReadHoldingRegistersRequest(address=117, registers=registers, count=3) assert len(req.registers) == 2 diff --git a/test/pdu/test_register_write_messages.py b/test/pdu/test_register_write_messages.py index 35700741e..a69fa5304 100644 --- a/test/pdu/test_register_write_messages.py +++ b/test/pdu/test_register_write_messages.py @@ -30,11 +30,6 @@ class TestWriteRegisterMessages: * Read Holding Registers """ - value = None - values = None - builder = None - write = None - def setup_method(self): """Initialize the test environment and builds request/result encoding pairs.""" self.value = 0xABCD diff --git a/test/server/test_server_asyncio.py b/test/server/test_server_asyncio.py index 887dc3a91..6e21980b6 100755 --- a/test/server/test_server_asyncio.py +++ b/test/server/test_server_asyncio.py @@ -4,6 +4,7 @@ import ssl from asyncio import CancelledError, Task from contextlib import suppress +from typing import Any from unittest import mock import pytest @@ -16,11 +17,11 @@ ) from pymodbus.exceptions import NoSuchIdException from pymodbus.server import ( + ModbusBaseServer, ModbusSerialServer, ModbusTcpServer, ModbusTlsServer, ModbusUdpServer, - ModbusBaseServer, ) @@ -109,7 +110,7 @@ class TestAsyncioServer: task: Task | None = None loop: asyncio.AbstractEventLoop | None = None store: ModbusDeviceContext | None = None - context: ModbusServerContext | None = None + context: Any = None identity: ModbusDeviceIdentification | None = None @pytest.fixture(autouse=True) @@ -151,7 +152,7 @@ async def start_server( self, do_forever=True, do_serial=False, do_tls=False, do_udp=False, do_ident=False, serv_addr=SERV_ADDR, ): """Handle setup and control of tcp server.""" - args = { + args: dict[str, Any] = { "context": self.context, "address": SERV_ADDR, } @@ -212,7 +213,7 @@ async def connect_server(self): ] # get the random server port ( BasicClient.transport, - BasicClient.my_protocol, + BasicClient.protocol, ) = await self.loop.create_connection( # type: ignore[union-attr] BasicClient, host="127.0.0.1", port=random_port ) From 58d4302fc5e0c211e43ee1365b02a7c0aa4f89df Mon Sep 17 00:00:00 2001 From: jan iversen Date: Tue, 17 Feb 2026 17:05:06 +0100 Subject: [PATCH 05/10] examples. --- examples/client_async.py | 2 +- examples/helper.py | 2 +- test/examples/test_examples.py | 8 ++++---- test/framer/test_extras.py | 1 - test/pdu/test_diag_messages.py | 2 +- test/pdu/test_pdu.py | 4 ++-- 6 files changed, 9 insertions(+), 10 deletions(-) diff --git a/examples/client_async.py b/examples/client_async.py index 3aab14949..951d43326 100755 --- a/examples/client_async.py +++ b/examples/client_async.py @@ -48,7 +48,7 @@ _logger.setLevel("DEBUG") -def setup_async_client(description: str | None =None, cmdline: str | None = None) -> modbusClient.ModbusBaseClient: +def setup_async_client(description: str | None =None, cmdline: list[str] | None = None) -> modbusClient.ModbusBaseClient: """Run client setup.""" args = helper.get_commandline( server=False, description=description, cmdline=cmdline diff --git a/examples/helper.py b/examples/helper.py index 20345192b..4dd7f5902 100755 --- a/examples/helper.py +++ b/examples/helper.py @@ -17,7 +17,7 @@ _logger = logging.getLogger(__file__) -def get_commandline(server: bool = False, description: str | None = None, extras: Any = None, cmdline: str | None = None): +def get_commandline(server: bool = False, description: str | None = None, extras: Any = None, cmdline: list[str] | None = None): """Read and check command line arguments.""" parser = argparse.ArgumentParser(description=description) parser.add_argument( diff --git a/test/examples/test_examples.py b/test/examples/test_examples.py index 490ad92aa..997c4dab6 100755 --- a/test/examples/test_examples.py +++ b/test/examples/test_examples.py @@ -147,11 +147,11 @@ async def test_client_async_calls(self, mock_server): async def test_client_async_calls_errors(self, mock_server): """Test client_async_calls.""" client = setup_async_client(cmdline=mock_server) - client.read_coils = mock.AsyncMock(side_effect=ModbusException("test")) + client.read_coils = mock.AsyncMock(side_effect=ModbusException("test")) # type: ignore[method-assign] with pytest.raises(ModbusException): await run_async_client(client, modbus_calls=async_template_call) client.close() - client.read_coils = mock.AsyncMock(return_value=ExceptionResponse(0x05, 0x10)) + client.read_coils = mock.AsyncMock(return_value=ExceptionResponse(0x05, 0x10)) # type: ignore[method-assign] with pytest.raises(ModbusException): await run_async_client(client, modbus_calls=async_template_call) client.close() @@ -159,11 +159,11 @@ async def test_client_async_calls_errors(self, mock_server): async def test_client_calls_errors(self, mock_server): """Test client_calls.""" client = setup_async_client(cmdline=mock_server) - client.read_coils = mock.Mock(side_effect=ModbusException("test")) + client.read_coils = mock.Mock(side_effect=ModbusException("test")) # type: ignore[method-assign] with pytest.raises(ModbusException): await run_async_client(client, modbus_calls=async_template_call) client.close() - client.read_coils = mock.Mock(return_value=ExceptionResponse(0x05, 0x10)) + client.read_coils = mock.Mock(return_value=ExceptionResponse(0x05, 0x10)) # type: ignore[method-assign] with pytest.raises(ModbusException): await run_async_client(client, modbus_calls=template_call) client.close() diff --git a/test/framer/test_extras.py b/test/framer/test_extras.py index bb5445695..121fab90c 100755 --- a/test/framer/test_extras.py +++ b/test/framer/test_extras.py @@ -19,7 +19,6 @@ class TestExtras: """Test for the framer module.""" - def setup_method(self): """Set up the test environment.""" self.client = None diff --git a/test/pdu/test_diag_messages.py b/test/pdu/test_diag_messages.py index 21a678930..ab4262a59 100644 --- a/test/pdu/test_diag_messages.py +++ b/test/pdu/test_diag_messages.py @@ -129,7 +129,7 @@ def test_diagnostic_encode_decode(self): def test_diagnostic_encode_error(self): """Testing diagnostic request/response can be decoded and encoded.""" msg_obj = DiagnosticBase() - msg_obj.message = b"not allowed" + msg_obj.message = "not allowed" # type: ignore[assignment] with pytest.raises(TypeError): msg_obj.encode() diff --git a/test/pdu/test_pdu.py b/test/pdu/test_pdu.py index 6d3da81e2..280b0e611 100644 --- a/test/pdu/test_pdu.py +++ b/test/pdu/test_pdu.py @@ -191,8 +191,8 @@ def test_pdu_register_as_byte(self): # NOT ALLOWED, NO conversion. req = reg_msg.ReadHoldingRegistersRequest(address=117, registers=registers, count=3) assert len(req.registers) == 2 - assert req.registers[0] != 24930 - assert req.registers[1] != 25444 + assert req.registers[0] == 24930 + assert req.registers[1] == 25444 def test_pdu_verify_address(self): """Test verify functions.""" From 8d60a8e568f97cd8a244c495678bd01e64d66725 Mon Sep 17 00:00:00 2001 From: jan iversen Date: Tue, 17 Feb 2026 17:30:11 +0100 Subject: [PATCH 06/10] Datastore. --- test/datastore/test_context.py | 2 +- test/datastore/test_simulator_datastore.py | 2 +- test/datastore/test_sparse_datastore.py | 7 +++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/test/datastore/test_context.py b/test/datastore/test_context.py index 0eba4111a..6fcd93b9d 100644 --- a/test/datastore/test_context.py +++ b/test/datastore/test_context.py @@ -16,7 +16,7 @@ def test_datastore_base_device(self): """Test ModbusDeviceContext.""" dev = ModbusBaseDeviceContext() dev.getValues(0x01, 0x01) - dev.setValues(0x05, 0x01, None) + dev.setValues(0x05, 0x01, [0]) def test_datastore_device(self): """Test ModbusDeviceContext.""" diff --git a/test/datastore/test_simulator_datastore.py b/test/datastore/test_simulator_datastore.py index 9db0ea835..04eb1ffc6 100644 --- a/test/datastore/test_simulator_datastore.py +++ b/test/datastore/test_simulator_datastore.py @@ -1,5 +1,5 @@ +# zuban: ignore """Test datastore.""" - import copy import pytest diff --git a/test/datastore/test_sparse_datastore.py b/test/datastore/test_sparse_datastore.py index bc04dc343..13629c93f 100644 --- a/test/datastore/test_sparse_datastore.py +++ b/test/datastore/test_sparse_datastore.py @@ -11,7 +11,7 @@ class TestRemoteDataStore: """Unittest for the pymodbus.datastore.remote module.""" - data_in_block = { + data_in_block: dict[int, int | list[int]] = { 1: 6720, 2: 130, 30: [0x0D, 0xFE], @@ -32,7 +32,6 @@ def test_sparse_datastore(self): async def test_sparse_datastore_async(self): """Test check frame.""" datablock = ModbusSparseDataBlock(self.data_in_block) - _ = str(datablock) for key, entry in self.data_in_block.items(): if isinstance(entry, int): entry = [entry] @@ -81,7 +80,7 @@ def test_sparse_datastore_set(self): async def test_sparse_datastore_async_set(self): """Test check frame.""" datablock = ModbusSparseDataBlock(self.data_in_block) - assert not await datablock.async_setValues(1, {1: 5}) + assert not await datablock.async_setValues(1, [5]) def test_sparse_datastore_set_not_ok(self): """Test check frame.""" @@ -91,7 +90,7 @@ def test_sparse_datastore_set_not_ok(self): with pytest.raises(ParameterException): datablock.setValues(1, [2, 3, 4]) datablock = ModbusSparseDataBlock(self.data_in_block) - datablock._process_values = mock.Mock(side_effect=KeyError) + datablock._process_values = mock.Mock(side_effect=KeyError) # type: ignore[method-assign] assert datablock.setValues(30, {17: 0}) == ExcCodes.ILLEGAL_ADDRESS def test_sparse_datastore_iter(self): From 1d9cc9888d5ef4386de3ce0be35cba3f5b9c4804 Mon Sep 17 00:00:00 2001 From: jan iversen Date: Tue, 17 Feb 2026 17:55:45 +0100 Subject: [PATCH 07/10] Client. --- pymodbus/client/mixin.py | 3 +- test/client/test_client.py | 19 +++--- test/client/test_client_sync.py | 113 +++++++++++++++----------------- test/conftest.py | 2 +- 4 files changed, 65 insertions(+), 72 deletions(-) diff --git a/pymodbus/client/mixin.py b/pymodbus/client/mixin.py index 027b70116..c4af3ccdc 100644 --- a/pymodbus/client/mixin.py +++ b/pymodbus/client/mixin.py @@ -3,7 +3,6 @@ import enum import struct -from abc import abstractmethod from typing import Generic, Literal, TypeVar, cast from ..constants import ModbusStatus @@ -49,9 +48,9 @@ class ModbusClientMixin(Generic[T]): # pylint: disable=too-many-public-methods def __init__(self): """Initialize.""" - @abstractmethod def execute(self, no_response_expected: bool, request: ModbusPDU) -> T: """Execute request.""" + return cast(T, None) def read_coils(self, address: int, *, count: int = 1, device_id: int = 1, no_response_expected: bool = False) -> T: """Read coils (code 0x01). diff --git a/test/client/test_client.py b/test/client/test_client.py index 791595c96..ce077a32a 100755 --- a/test/client/test_client.py +++ b/test/client/test_client.py @@ -1,6 +1,7 @@ """Test client sync.""" import socket import ssl +from typing import cast from unittest import mock import pytest @@ -250,7 +251,7 @@ def test_client_mixin_convert(self, datatype, word_order, registers, value, stri if isinstance(result, list): result = [round(v, 6) for v in result] else: - result = round(result, 6) + result = round(cast(float, result), 6) assert result == value @pytest.mark.parametrize( @@ -283,7 +284,7 @@ def test_client_mixin_convert_1234(self, datatype, registers, value): regs = ModbusClientMixin.convert_to_registers(value, datatype) result = ModbusClientMixin.convert_from_registers(regs, datatype) if datatype == ModbusClientMixin.DATATYPE.FLOAT32 or datatype == ModbusClientMixin.DATATYPE.FLOAT64: - result = round(result, 6) + result = round(cast(float, result), 6) assert result == value assert regs == registers @@ -296,15 +297,15 @@ def test_client_mixin_convert_fail(self): ModbusClientMixin.convert_from_registers([123], ModbusClientMixin.DATATYPE.FLOAT64) with pytest.raises(TypeError): - ModbusClientMixin.convert_to_registers(bool, ModbusClientMixin.DATATYPE.BITS) + ModbusClientMixin.convert_to_registers(bool, ModbusClientMixin.DATATYPE.BITS) # type: ignore[arg-type] def test_client_mixin_convert_datatype_fail(self): """Test convert fail.""" with pytest.raises(TypeError): - ModbusClientMixin.convert_to_registers(123, ("s", 0)) + ModbusClientMixin.convert_to_registers(123, ("s", 0)) # type: ignore[arg-type] with pytest.raises(TypeError): - ModbusClientMixin.convert_from_registers([123], ("d", 4)) + ModbusClientMixin.convert_from_registers([123], ("d", 4)) # type: ignore[arg-type] class TestClientBase: @@ -618,7 +619,7 @@ def test_client_tls_connect2(self): def test_tcp_client_register(self): """Test tcp client.""" - class CustomRequest: # pylint: disable=too-few-public-methods + class CustomRequest(ModbusPDU): # pylint: disable=too-few-public-methods """Dummy custom request.""" function_code = 79 @@ -636,12 +637,12 @@ def test_sync_block(self): def test_sync_execute(self): """Test sync execute.""" client = lib_client.ModbusTcpClient("127.0.0.1") - client.connect = mock.Mock(return_value=False) + client.connect = mock.Mock(return_value=False) # type: ignore[method-assign] with pytest.raises(ConnectionException): - client.execute(False, None) + client.execute(False, None) # type: ignore[arg-type] client.transaction = mock.Mock() client.connect.return_value = True - client.execute(False, None) + client.execute(False, None) # type: ignore[arg-type] @pytest.mark.parametrize( ("client_class"), diff --git a/test/client/test_client_sync.py b/test/client/test_client_sync.py index 0b7019d18..0d46c4e65 100755 --- a/test/client/test_client_sync.py +++ b/test/client/test_client_sync.py @@ -1,5 +1,6 @@ """Test client sync.""" from itertools import count +from typing import cast from unittest import mock import pytest @@ -39,7 +40,7 @@ def test_basic_syn_udp_client(self): """Test the basic methods for the udp sync client.""" # receive/send client = ModbusUdpClient("127.0.0.1") - client.socket = mockSocket() + client.socket = mockSocket() # type: ignore[assignment] assert not client.send(b'') assert client.send(b"\x50") == 1 assert client.recv(1) == b"\x50" @@ -64,18 +65,18 @@ def test_udp_client_send(self): """Test the udp client send method.""" client = ModbusUdpClient("127.0.0.1") with pytest.raises(ConnectionException): - client.send(None) - client.socket = mockSocket() - assert not client.send(None) - assert client.send("1234") == 4 + client.send(b'') + client.socket = mockSocket() # type: ignore[assignment] + assert not client.send(b'') + assert client.send(b"1234") == 4 def test_udp_client_recv(self): """Test the udp client receive method.""" client = ModbusUdpClient("127.0.0.1") with pytest.raises(ConnectionException): client.recv(1024) - client.socket = mockSocket() - client.socket.mock_prepare_receive(b"\x00" * 4) + client.socket = mockSocket() # type: ignore[assignment] + cast(mockSocket, client.socket).mock_prepare_receive(b"\x00" * 4) assert client.recv(0) == b"" assert client.recv(4) == b"\x00" * 4 @@ -83,9 +84,9 @@ def test_udp_client_recv_duplicate(self): """Test the udp client receive method.""" test_msg = b"\x00\x01\x00\x00\x00\x05\x01\x04\x02\x00\x03" client = ModbusUdpClient("127.0.0.1") - client.socket = mockSocket(copy_send=False) - client.socket.mock_prepare_receive(test_msg) - client.socket.mock_prepare_receive(test_msg) + client.socket = mockSocket(copy_send=False) # type: ignore[assignment] + cast(mockSocket, client.socket).mock_prepare_receive(test_msg) + cast(mockSocket, client.socket).mock_prepare_receive(test_msg) reply_ok = client.read_input_registers(0x820, count=1, device_id=1) assert not reply_ok.isError() with pytest.raises(ModbusIOException): @@ -116,8 +117,8 @@ def test_basic_syn_tcp_client(self, mock_select): # receive/send mock_select.select.return_value = [True] client = ModbusTcpClient("127.0.0.1") - client.socket = mockSocket() - assert not client.send(None) + client.socket = mockSocket() # type: ignore[assignment] + assert not client.send(b'') assert client.send(b"\x45") == 1 assert client.recv(1) == b"\x45" @@ -130,7 +131,7 @@ def test_basic_syn_tcp_client(self, mock_select): client.close() # already closed socket - client.socket = False + client.socket = False # type: ignore[assignment] client.close() assert str(client) == "ModbusTcpClient 127.0.0.1:502" @@ -144,10 +145,10 @@ def test_tcp_client_send(self): """Test the tcp client send method.""" client = ModbusTcpClient("127.0.0.1") with pytest.raises(ConnectionException): - client.send(None) - client.socket = mockSocket() - assert not client.send(None) - assert client.send("1234") == 4 + client.send(b'') + client.socket = mockSocket() # type: ignore[assignment] + assert not client.send(b'') + assert client.send(b"1234") == 4 @mock.patch("pymodbus.client.tcp.time") @mock.patch("pymodbus.client.tcp.select") @@ -158,9 +159,9 @@ def test_tcp_client_recv(self, mock_select, mock_time): client = ModbusTcpClient("127.0.0.1") with pytest.raises(ConnectionException): client.recv(1024) - client.socket = mockSocket() + client.socket = mockSocket() # type: ignore[assignment] assert client.recv(0) == b"" - client.socket.mock_prepare_receive(b"\x00" * 4) + cast(mockSocket, client.socket).mock_prepare_receive(b"\x00" * 4) assert client.recv(4) == b"\x00" * 4 mock_socket = mock.MagicMock() @@ -172,8 +173,8 @@ def test_tcp_client_recv(self, mock_select, mock_time): assert client.recv(2) == b"\x00\x01" mock_select.select.return_value = [False] assert client.recv(2) == b"" - client.socket = mockSocket() - client.socket.mock_prepare_receive(b"\x00") + client.socket = mockSocket() # type: ignore[assignment] + cast(mockSocket, client.socket).mock_prepare_receive(b"\x00") mock_select.select.return_value = [True] assert client.recv(None) in b"\x00" @@ -213,8 +214,8 @@ def test_basic_syn_tls_client(self, mock_select): # receive/send mock_select.select.return_value = [True] client = ModbusTlsClient("localhost") - client.socket = mockSocket() - assert not client.send(None) + client.socket = mockSocket() # type: ignore[assignment] + assert not client.send(b'') assert client.send(b"\x45") == 1 assert client.recv(1) == b"\x45" @@ -224,24 +225,24 @@ def test_basic_syn_tls_client(self, mock_select): client.close() # already closed socket - client.socket = False + client.socket = False # type: ignore[assignment] client.close() assert str(client) == "ModbusTlsClient localhost:802" - client = ModbusTcpClient("127.0.0.1") - client.socket = mockSocket() - assert not client.send(None) - assert client.send(b"\x45") == 1 - assert client.recv(1) == b"\x45" + client2 = ModbusTcpClient("127.0.0.1") + client2.socket = mockSocket() # type: ignore[assignment] + assert not client2.send(b'') + assert client2.send(b"\x45") == 1 + assert client2.recv(1) == b"\x45" def test_tls_client_send(self): """Test the tls client send method.""" client = ModbusTlsClient("127.0.0.1") with pytest.raises(ConnectionException): - client.send(None) - client.socket = mockSocket() - assert not client.send(None) - assert client.send("1234") == 4 + client.send(b'') + client.socket = mockSocket() # type: ignore[assignment] + assert not client.send(b'') + assert client.send(b"1234") == 4 @mock.patch("pymodbus.client.tcp.time") @mock.patch("pymodbus.client.tcp.select") @@ -253,13 +254,13 @@ def test_tls_client_recv(self, mock_select, mock_time): client.recv(1024) mock_time.time.side_effect = count() - client.socket = mockSocket() - client.socket.mock_prepare_receive(b"\x00" * 4) + client.socket = mockSocket() # type: ignore[assignment] + cast(mockSocket, client.socket).mock_prepare_receive(b"\x00" * 4) assert client.recv(0) == b"" assert client.recv(4) == b"\x00" * 4 client.comm_params.timeout_connect = 2 - client.socket.mock_prepare_receive(b"\x00") + cast(mockSocket, client.socket).mock_prepare_receive(b"\x00") assert b"\x00" in client.recv(None) def test_tls_client_repr(self): @@ -302,9 +303,7 @@ def test_basic_sync_serial_client(self, mock_serial): mock_serial.read = lambda size: b"\x00" * size client = ModbusSerialClient("/dev/null") client.socket = mock_serial - client.state = 0 assert not client.send(b'') - client.state = 0 assert client.send(b"\x00") == 1 assert client.recv(1) == b"\x00" @@ -316,7 +315,6 @@ def test_basic_sync_serial_client(self, mock_serial): # rtu connect/disconnect rtu_client = ModbusSerialClient("/dev/null", framer=FramerType.RTU) assert rtu_client.connect() - assert rtu_client.socket.inter_byte_timeout == rtu_client.inter_byte_timeout rtu_client.close() assert str(client) == "ModbusSerialClient /dev/null:0" @@ -351,12 +349,10 @@ def test_serial_client_send(self, mock_serial): mock_serial.write = lambda x: len(x) # pylint: disable=unnecessary-lambda client = ModbusSerialClient("/dev/null") with pytest.raises(ConnectionException): - client.send(None) + client.send(b'') client.socket = mock_serial - client.state = 0 - assert not client.send(None) - client.state = 0 - assert client.send("1234") == 4 + assert not client.send(b'') + assert client.send(b"1234") == 4 @mock.patch("serial.Serial") @@ -367,26 +363,23 @@ def test_serial_client_cleanup_buffer_before_send(self, mock_serial): mock_serial.write = lambda x: len(x) # pylint: disable=unnecessary-lambda client = ModbusSerialClient("/dev/null") with pytest.raises(ConnectionException): - client.send(None) + client.send(b'') client.socket = mock_serial - client.state = 0 - assert not client.send(None) - client.state = 0 - assert client.send("1234") == 4 + assert not client.send(b'') + assert client.send(b"1234") == 4 def test_serial_client_recv(self): """Test the serial client receive method.""" client = ModbusSerialClient("/dev/null") with pytest.raises(ConnectionException): client.recv(1024) - client.socket = mockSocket() + client.socket = mockSocket() # type: ignore[assignment] assert client.recv(0) == b"" - client.socket.mock_prepare_receive(b"\x00" * 4) + cast(mockSocket, client.socket).mock_prepare_receive(b"\x00" * 4) assert client.recv(4) == b"\x00" * 4 - client.socket = mockSocket() - client.socket.mock_prepare_receive(b"") + client.socket = mockSocket() # type: ignore[assignment] + cast(mockSocket, client.socket).mock_prepare_receive(b"") assert client.recv(None) == b"" - client.socket.timeout = 0 assert client.recv(0) == b"" def test_serial_client_recv_split(self): @@ -394,11 +387,11 @@ def test_serial_client_recv_split(self): client = ModbusSerialClient("/dev/null") with pytest.raises(ConnectionException): client.recv(1024) - client.socket = mockSocket(copy_send=False) - client.socket.mock_prepare_receive(b'') - client.socket.mock_prepare_receive(b'\x11\x03\x06\xAE') - client.socket.mock_prepare_receive(b'\x41\x56\x52\x43\x40\x49') - client.socket.mock_prepare_receive(b'\xAD') + client.socket = mockSocket(copy_send=False) # type: ignore[assignment] + cast(mockSocket, client.socket).mock_prepare_receive(b'') + cast(mockSocket, client.socket).mock_prepare_receive(b'\x11\x03\x06\xAE') + cast(mockSocket, client.socket).mock_prepare_receive(b'\x41\x56\x52\x43\x40\x49') + cast(mockSocket, client.socket).mock_prepare_receive(b'\xAD') reply_ok = client.read_input_registers(0x820, count=3, device_id=17) assert not reply_ok.isError() client.close() @@ -417,4 +410,4 @@ def test_serial_client_with(self): """Test with block.""" with mock.patch("pymodbus.client.serial.ModbusSerialClient.connect"), ModbusSerialClient("/dev/null") as client: assert client - client.socket = mockSocket() + client.socket = mockSocket() # type: ignore[assignment] diff --git a/test/conftest.py b/test/conftest.py index 2b77e38ed..b4a848dd5 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -237,7 +237,7 @@ def __init__(self, valid=False, default=True): super().__init__() self.valid = valid self.default = default - self.last_values = [] + self.last_values: list = [] async def async_getValues(self, device_id, func_code, address, count=0): """Get values.""" From 13cd2da2b03fe20cc1058f2ea35b67ef207aebc7 Mon Sep 17 00:00:00 2001 From: jan iversen Date: Tue, 17 Feb 2026 18:27:21 +0100 Subject: [PATCH 08/10] final. --- .github/workflows/ci.yml | 2 +- check_ci.sh | 2 +- pymodbus/client/mixin.py | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 59203e324..9840ba49f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,7 +77,7 @@ jobs: - name: Type check with zuban if: matrix.run_lint == true run: | - uv run zuban check pymodbus examples + uv run zuban check pymodbus examples test - name: ruff if: matrix.run_lint == true diff --git a/check_ci.sh b/check_ci.sh index 717187f6e..177a3d4f5 100755 --- a/check_ci.sh +++ b/check_ci.sh @@ -8,6 +8,6 @@ trap 'echo "\"${last_command}\" command filed with exit code $?."' EXIT codespell ruff check --fix --exit-non-zero-on-fix . pylint --recursive=y examples pymodbus test -zuban check pymodbus examples +zuban check pymodbus examples test pytest -x --cov --numprocesses auto echo "Ready to push" diff --git a/pymodbus/client/mixin.py b/pymodbus/client/mixin.py index c4af3ccdc..176e55e43 100644 --- a/pymodbus/client/mixin.py +++ b/pymodbus/client/mixin.py @@ -50,6 +50,7 @@ def __init__(self): def execute(self, no_response_expected: bool, request: ModbusPDU) -> T: """Execute request.""" + _ = no_response_expected, request return cast(T, None) def read_coils(self, address: int, *, count: int = 1, device_id: int = 1, no_response_expected: bool = False) -> T: From d32d495a1c7f08dfe6e210075f7705eb3becfab8 Mon Sep 17 00:00:00 2001 From: jan iversen Date: Tue, 17 Feb 2026 18:35:29 +0100 Subject: [PATCH 09/10] cov. --- test/client/test_client.py | 6 ++++++ test/datastore/test_sequential.py | 1 + test/pdu/test_pdu.py | 7 +++++++ test/server/test_server_asyncio.py | 5 ++--- test/server/test_simulator.py | 7 ++++++- 5 files changed, 22 insertions(+), 4 deletions(-) diff --git a/test/client/test_client.py b/test/client/test_client.py index ce077a32a..0ff7a4c6c 100755 --- a/test/client/test_client.py +++ b/test/client/test_client.py @@ -307,6 +307,12 @@ def test_client_mixin_convert_datatype_fail(self): with pytest.raises(TypeError): ModbusClientMixin.convert_from_registers([123], ("d", 4)) # type: ignore[arg-type] + def test_client_mixin_execute(self): + """Test mixin execute.""" + a = ModbusClientMixin() + a.execute(False, cast(ModbusPDU, None)) + + class TestClientBase: """Test client code.""" diff --git a/test/datastore/test_sequential.py b/test/datastore/test_sequential.py index 1fec8a0b8..73c5d1042 100644 --- a/test/datastore/test_sequential.py +++ b/test/datastore/test_sequential.py @@ -28,4 +28,5 @@ def test_datastore_Sequential_set(self): def test_datastore_Sequential_iter(self): """Test check frame.""" block = ModbusSequentialDataBlock(0x01, [17]) + str(block) _ = list(block) diff --git a/test/pdu/test_pdu.py b/test/pdu/test_pdu.py index 280b0e611..12e9e6b32 100644 --- a/test/pdu/test_pdu.py +++ b/test/pdu/test_pdu.py @@ -164,6 +164,13 @@ def test_pdu_instance(self, pdutype): pdu = pdutype() assert pdu + def test_pdu_base_instance(self): + """Test that all PDU types can be created.""" + pdu = ModbusPDU(4) + str(pdu) + assert pdu + assert pdu.encode() == b'' + @pytest.mark.parametrize(("pdutype", "args", "kwargs", "frame"), requests + responses) @pytest.mark.usefixtures("frame", "args") def test_pdu_instance_args(self, pdutype, kwargs): diff --git a/test/server/test_server_asyncio.py b/test/server/test_server_asyncio.py index 6e21980b6..8df331aef 100755 --- a/test/server/test_server_asyncio.py +++ b/test/server/test_server_asyncio.py @@ -54,9 +54,8 @@ def connection_made(self, transport): if BasicClient.data is not None: _logger.debug("TEST Client write data") self.transport.write(BasicClient.data) # type: ignore[union-attr] - if BasicClient.dataTo is not None: - _logger.debug("TEST Client sendTo data") - self.transport.sendto(BasicClient.dataTo) # type: ignore[union-attr] + _logger.debug("TEST Client sendTo data") + self.transport.sendto(BasicClient.dataTo) # type: ignore[union-attr] def data_received(self, data): """Get Data received.""" diff --git a/test/server/test_simulator.py b/test/server/test_simulator.py index 7c9a0ebb7..18d2edd86 100644 --- a/test/server/test_simulator.py +++ b/test/server/test_simulator.py @@ -10,7 +10,7 @@ from pymodbus.client import AsyncModbusTcpClient from pymodbus.datastore.simulator import Cell, CellType from pymodbus.server import ModbusSimulatorServer -from pymodbus.server.simulator.main import run_main +from pymodbus.server.simulator.main import get_commandline, run_main from pymodbus.transport import NULLMODEM_HOST @@ -235,6 +235,11 @@ async def setup_simulator_server(self, server, device, unused_tcp_port, only_obj await task.stop() await task_future + def test_simulator_commandline(self): + """Test commandline.""" + assert get_commandline() + assert get_commandline(["--json_file", "got it"]) + async def test_simulator_server_tcp(self, simulator_server): """Test init simulator server.""" From 94f8ea0c1d33a3f3ae82dbe1f4ec1acd4c5517ec Mon Sep 17 00:00:00 2001 From: jan iversen Date: Tue, 17 Feb 2026 18:45:08 +0100 Subject: [PATCH 10/10] hmmmm. --- test/server/test_simulator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/server/test_simulator.py b/test/server/test_simulator.py index 18d2edd86..36b737001 100644 --- a/test/server/test_simulator.py +++ b/test/server/test_simulator.py @@ -238,7 +238,8 @@ async def setup_simulator_server(self, server, device, unused_tcp_port, only_obj def test_simulator_commandline(self): """Test commandline.""" assert get_commandline() - assert get_commandline(["--json_file", "got it"]) + with mock.patch("os.path.exists", return_value=True): + assert get_commandline(["--json_file", "got it"]) async def test_simulator_server_tcp(self, simulator_server): """Test init simulator server."""