Skip to content

Commit cf6f4b7

Browse files
committed
Test coverage server 100%.
1 parent 660e87b commit cf6f4b7

File tree

13 files changed

+295
-160
lines changed

13 files changed

+295
-160
lines changed

pymodbus/server/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""**Server classes**."""
22

33
__all__ = [
4+
"ModbusBaseServer",
45
"ModbusSerialServer",
56
"ModbusSimulatorServer",
67
"ModbusTcpServer",
@@ -19,6 +20,7 @@
1920
"get_simulator_commandline",
2021
]
2122

23+
from pymodbus.server.base import ModbusBaseServer
2224
from pymodbus.server.server import (
2325
ModbusSerialServer,
2426
ModbusTcpServer,

pymodbus/server/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
class ModbusBaseServer(ModbusProtocol):
1919
"""Common functionality for all server classes."""
2020

21-
active_server: ModbusBaseServer | None
21+
active_server: ModbusBaseServer | None = None
2222

2323
def __init__( # pylint: disable=too-many-arguments
2424
self,

pymodbus/server/requesthandler.py

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -43,24 +43,17 @@ def __init__(self, owner, trace_packet, trace_pdu, trace_connect):
4343
def callback_disconnected(self, exc: Exception | None) -> None:
4444
"""Call when connection is lost."""
4545
super().callback_disconnected(exc)
46-
try:
47-
if exc is None:
48-
Log.debug(
49-
"Handler for stream [{}] has been canceled", self.comm_params.comm_name
50-
)
51-
else:
52-
Log.debug(
53-
"Client Disconnection {} due to {}",
54-
self.comm_params.comm_name,
55-
exc,
56-
)
57-
self.running = False
58-
except Exception as exc: # pylint: disable=broad-except
59-
Log.error(
60-
"Datastore unable to fulfill request: {}; {}",
46+
if exc is None:
47+
Log.debug(
48+
"Handler for stream [{}] has been canceled", self.comm_params.comm_name
49+
)
50+
else:
51+
Log.debug(
52+
"Client Disconnection {} due to {}",
53+
self.comm_params.comm_name,
6154
exc,
62-
traceback.format_exc(),
6355
)
56+
self.running = False
6457

6558
def callback_data(self, data: bytes, addr: tuple | None = None) -> int:
6659
"""Handle received data."""
@@ -89,7 +82,7 @@ async def handle_request(self):
8982
if self.server.broadcast_enable and not self.last_pdu.dev_id:
9083
# if broadcasting then execute on all device contexts,
9184
# note response will be ignored
92-
for dev_id in self.server.context.device_id():
85+
for dev_id in self.server.context.device_ids():
9386
await self.last_pdu.update_datastore(self.server.context[dev_id])
9487
return
9588

pymodbus/server/simulator/custom_actions.py

Lines changed: 0 additions & 10 deletions
This file was deleted.

pymodbus/server/simulator/main.py

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
from pymodbus.server.simulator.http_server import ModbusSimulatorServer
4949

5050

51-
def get_commandline(extras=None, cmdline=None):
51+
def get_commandline(cmdline=None):
5252
"""Get command line arguments."""
5353
parser = argparse.ArgumentParser(
5454
description="Modbus server with REST-API and web server"
@@ -97,9 +97,6 @@ def get_commandline(extras=None, cmdline=None):
9797
help="python file with custom actions, default is none",
9898
type=str,
9999
)
100-
if extras:
101-
for extra in extras:
102-
parser.add_argument(extra[0], **extra[1])
103100
args = parser.parse_args(cmdline)
104101
pymodbus_apply_logging_config(args.log.upper())
105102
Log.info("Start simulator")
@@ -112,17 +109,11 @@ def get_commandline(extras=None, cmdline=None):
112109
return cmd_args
113110

114111

115-
async def run_main():
112+
async def run_main(cmdline=None):
116113
"""Run server async."""
117-
cmd_args = get_commandline()
114+
cmd_args = get_commandline(cmdline=cmdline)
118115
task = ModbusSimulatorServer(**cmd_args)
119116
await task.run_forever()
120117

121-
122-
def main():
123-
"""Run server."""
124-
asyncio.run(run_main(), debug=True)
125-
126-
127118
if __name__ == "__main__":
128-
main()
119+
asyncio.run(run_main(), debug=True)

pymodbus/server/startstop.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from __future__ import annotations
33

44
import asyncio
5+
import concurrent
56
import os
67

78
from pymodbus.datastore import ModbusServerContext
@@ -158,10 +159,13 @@ async def ServerAsyncStop() -> None:
158159
await ModbusBaseServer.active_server.shutdown()
159160
ModbusBaseServer.active_server = None
160161

161-
162162
def ServerStop() -> None:
163163
"""Terminate server."""
164164
if not ModbusBaseServer.active_server:
165165
raise RuntimeError("Modbus server not running.")
166-
future = asyncio.run_coroutine_threadsafe(ServerAsyncStop(), ModbusBaseServer.active_server.loop)
167-
future.result(timeout=10 if os.name == 'nt' else 0.1)
166+
try:
167+
future = asyncio.run_coroutine_threadsafe(ServerAsyncStop(), ModbusBaseServer.active_server.loop)
168+
future.result(timeout=10 if os.name == 'nt' else 0.1)
169+
except concurrent.futures.TimeoutError:
170+
pass
171+
ModbusBaseServer.active_server = None

pymodbus/transaction/transaction.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ def __init__(
6262
self._lock = asyncio.Lock()
6363
self.low_level_send = self.send
6464
self.response_future: asyncio.Future = asyncio.Future()
65-
self.last_pdu: ModbusPDU | None
66-
self.last_addr: tuple | None
65+
self.last_pdu: ModbusPDU | None = None
66+
self.last_addr: tuple | None = None
6767

6868
def dummy_trace_packet(self, sending: bool, data: bytes) -> bytes:
6969
"""Do dummy trace."""

test/server/test_base.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"""Test server asyncio."""
2+
from unittest import mock
3+
4+
import pytest
5+
6+
from pymodbus.pdu import ReadHoldingRegistersRequest
7+
from pymodbus.server import ModbusBaseServer
8+
from pymodbus.transport import CommParams, CommType
9+
10+
11+
class TestBaseServer:
12+
"""Test for the pymodbus.server.startstop module."""
13+
14+
@pytest.fixture
15+
async def baseserver(self):
16+
"""Fixture to provide base_server."""
17+
server = ModbusBaseServer(
18+
CommParams(
19+
comm_type=CommType.TCP,
20+
comm_name="server_listener",
21+
reconnect_delay=0.0,
22+
reconnect_delay_max=0.0,
23+
timeout_connect=0.0,
24+
),
25+
None,
26+
False,
27+
False,
28+
None,
29+
"socket",
30+
None,
31+
None,
32+
None,
33+
[ReadHoldingRegistersRequest],
34+
)
35+
return server
36+
37+
async def test_base(self, baseserver):
38+
"""Test __init__."""
39+
40+
async def test_base_serve_forever1(self, baseserver):
41+
"""Test serve_forever."""
42+
baseserver.listen = mock.AsyncMock(return_value=None)
43+
with pytest.raises(RuntimeError):
44+
await baseserver.serve_forever()
45+
46+
async def test_base_serve_forever2(self, baseserver):
47+
"""Test serve_forever."""
48+
baseserver.listen = mock.AsyncMock(return_value=True)
49+
await baseserver.serve_forever(background=True)
50+
baseserver.serving.set_result(True)
51+
await baseserver.serve_forever()
52+
53+
54+
async def test_base_connected(self, baseserver):
55+
"""Test serve_forever."""
56+
with pytest.raises(RuntimeError):
57+
baseserver.callback_connected()
58+
59+
async def test_base_disconnected(self, baseserver):
60+
"""Test serve_forever."""
61+
with pytest.raises(RuntimeError):
62+
baseserver.callback_disconnected(None)
63+
64+
async def test_base_data(self, baseserver):
65+
"""Test serve_forever."""
66+
with pytest.raises(RuntimeError):
67+
baseserver.callback_data(None)

test/server/test_requesthandler.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
"""Test server asyncio."""
2+
from unittest import mock
3+
4+
import pytest
5+
6+
from pymodbus.server import ModbusBaseServer
7+
from pymodbus.transport import CommParams, CommType
8+
from pymodbus.exceptions import ModbusIOException, NoSuchIdException
9+
from pymodbus.pdu import ExceptionResponse
10+
from pymodbus.datastore import (
11+
ModbusDeviceContext,
12+
ModbusSequentialDataBlock,
13+
ModbusServerContext,
14+
)
15+
16+
17+
class TestRequesthandler:
18+
"""Test for the pymodbus.server.startstop module."""
19+
20+
@pytest.fixture
21+
async def requesthandler(self):
22+
"""Fixture to provide base_server."""
23+
store = ModbusDeviceContext(
24+
di=ModbusSequentialDataBlock(0, [17] * 100),
25+
co=ModbusSequentialDataBlock(0, [17] * 100),
26+
hr=ModbusSequentialDataBlock(0, [17] * 100),
27+
ir=ModbusSequentialDataBlock(0, [17] * 100),
28+
)
29+
server = ModbusBaseServer(
30+
CommParams(
31+
comm_type=CommType.TCP,
32+
comm_name="server_listener",
33+
reconnect_delay=0.0,
34+
reconnect_delay_max=0.0,
35+
timeout_connect=0.0,
36+
source_address=(0, 0),
37+
),
38+
ModbusServerContext(devices=store, single=True),
39+
False,
40+
False,
41+
None,
42+
"socket",
43+
None,
44+
None,
45+
None,
46+
[],
47+
)
48+
conn = server.callback_new_connection()
49+
conn.pdu_send = mock.Mock(return_value=True)
50+
return conn
51+
52+
async def test_requesthandler(self, requesthandler):
53+
"""Test __init__."""
54+
55+
async def test_rh_callback_data(self, requesthandler):
56+
"""Test __init__."""
57+
with mock.patch("pymodbus.transaction.TransactionManager.callback_data") as cb_data:
58+
cb_data.side_effect=ModbusIOException
59+
data = b"012"
60+
assert len(data) == requesthandler.callback_data(data, None)
61+
62+
async def test_rh_handle_request(self, requesthandler):
63+
"""Test __init__."""
64+
requesthandler.last_pdu = None
65+
await requesthandler.handle_request()
66+
requesthandler.last_pdu = ExceptionResponse(17)
67+
await requesthandler.handle_request()
68+
requesthandler.last_pdu.update_datastore = mock.AsyncMock()
69+
requesthandler.server.broadcast_enable = True
70+
requesthandler.last_pdu.dev_id = 0
71+
await requesthandler.handle_request()
72+
requesthandler.last_pdu.update_datastore.side_effect = NoSuchIdException
73+
await requesthandler.handle_request()
74+
requesthandler.server.ignore_missing_devices = True
75+
await requesthandler.handle_request()
76+
77+
async def test_rh_server_send(self, requesthandler):
78+
"""Test __init__."""
79+
requesthandler.server_send(None, None)
80+
requesthandler.server_send(ExceptionResponse(17), None)

test/server/test_server_asyncio.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@
1515
ModbusServerContext,
1616
)
1717
from pymodbus.exceptions import NoSuchIdException
18-
from pymodbus.server import ModbusTcpServer, ModbusTlsServer, ModbusUdpServer
18+
from pymodbus.server import (
19+
ModbusTcpServer,
20+
ModbusTlsServer,
21+
ModbusUdpServer,
22+
)
1923

2024

2125
_logger = logging.getLogger()

0 commit comments

Comments
 (0)