Skip to content

Commit 353b94e

Browse files
authored
Use zigpy SerialProtocol (#256)
* Use zigpy flow control * Bump minimum zigpy version * Upgrade pytest-asyncio * Use `znp.disconnect` instead of `znp.close` * Try to improve joining test reliability * Try to wait for device initialization tasks
1 parent 06e3054 commit 353b94e

19 files changed

+103
-156
lines changed

pyproject.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ readme = "README.md"
1414
license = {text = "GPL-3.0"}
1515
requires-python = ">=3.8"
1616
dependencies = [
17-
"zigpy>=0.69.0",
17+
"zigpy>=0.70.0",
1818
"async_timeout",
1919
"voluptuous",
2020
"coloredlogs",
@@ -63,6 +63,7 @@ timeout = 20
6363
log_format = "%(asctime)s.%(msecs)03d %(levelname)s %(message)s"
6464
log_date_format = "%Y-%m-%d %H:%M:%S"
6565
asyncio_mode = "auto"
66+
asyncio_default_fixture_loop_scope = "function"
6667

6768
[tool.flake8]
6869
exclude = ".venv,.git,.tox,docs,venv,bin,lib,deps,build"

tests/api/test_connect.py

+12-12
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ async def test_connect_no_test(make_znp_server):
1919
# Nothing will be sent
2020
assert znp_server._uart.data_received.call_count == 0
2121

22-
znp.close()
22+
await znp.disconnect()
2323

2424

2525
@pytest.mark.parametrize("work_after_attempt", [1, 2, 3])
@@ -44,7 +44,7 @@ def ping_rsp(req):
4444

4545
await znp.connect(test_port=True)
4646

47-
znp.close()
47+
await znp.disconnect()
4848

4949

5050
async def test_connect_skip_bootloader_batched_rsp(make_znp_server, mocker):
@@ -82,7 +82,7 @@ def ping_rsp(req):
8282

8383
await znp.connect(test_port=True)
8484

85-
znp.close()
85+
await znp.disconnect()
8686

8787

8888
async def test_connect_skip_bootloader_failure(make_znp_server):
@@ -92,7 +92,7 @@ async def test_connect_skip_bootloader_failure(make_znp_server):
9292
with pytest.raises(asyncio.TimeoutError):
9393
await znp.connect(test_port=True)
9494

95-
znp.close()
95+
await znp.disconnect()
9696

9797

9898
async def test_connect_skip_bootloader_rts_dtr_pins(make_znp_server, mocker):
@@ -112,7 +112,7 @@ async def test_connect_skip_bootloader_rts_dtr_pins(make_znp_server, mocker):
112112
assert serial._mock_dtr_prop.mock_calls == [call(False), call(False), call(False)]
113113
assert serial._mock_rts_prop.mock_calls == [call(False), call(True), call(False)]
114114

115-
znp.close()
115+
await znp.disconnect()
116116

117117

118118
async def test_connect_skip_bootloader_config(make_znp_server, mocker):
@@ -133,24 +133,24 @@ async def test_connect_skip_bootloader_config(make_znp_server, mocker):
133133
assert serial._mock_dtr_prop.called is False
134134
assert serial._mock_rts_prop.called is False
135135

136-
znp.close()
136+
await znp.disconnect()
137137

138138

139139
async def test_api_close(connected_znp, mocker):
140140
znp, znp_server = connected_znp
141141
uart = znp._uart
142142
mocker.spy(uart, "close")
143143

144-
znp.close()
144+
await znp.disconnect()
145145

146146
# Make sure our UART was actually closed
147147
assert znp._uart is None
148148
assert znp._app is None
149149
assert uart.close.call_count == 1
150150

151-
# ZNP.close should not throw any errors if called multiple times
152-
znp.close()
153-
znp.close()
151+
# ZNP.disconnect should not throw any errors if called multiple times
152+
await znp.disconnect()
153+
await znp.disconnect()
154154

155155
def dict_minus(d, minus):
156156
return {k: v for k, v in d.items() if k not in minus}
@@ -165,8 +165,8 @@ def dict_minus(d, minus):
165165
znp2.__dict__, ignored_keys
166166
)
167167

168-
znp2.close()
169-
znp2.close()
168+
await znp2.disconnect()
169+
await znp2.disconnect()
170170

171171
assert dict_minus(znp.__dict__, ignored_keys) == dict_minus(
172172
znp2.__dict__, ignored_keys

tests/api/test_listeners.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88
from zigpy_znp.api import OneShotResponseListener, CallbackResponseListener
99

1010

11-
async def test_resolve(event_loop, mocker):
11+
async def test_resolve(mocker):
1212
callback = mocker.Mock()
1313
callback_listener = CallbackResponseListener(
1414
[c.SYS.Ping.Rsp(partial=True)], callback
1515
)
1616

17-
future = event_loop.create_future()
17+
future = asyncio.get_running_loop().create_future()
1818
one_shot_listener = OneShotResponseListener([c.SYS.Ping.Rsp(partial=True)], future)
1919

2020
match = c.SYS.Ping.Rsp(Capabilities=t.MTCapabilities.SYS)
@@ -42,9 +42,9 @@ async def test_resolve(event_loop, mocker):
4242
assert one_shot_listener.cancel()
4343

4444

45-
async def test_cancel(event_loop):
45+
async def test_cancel():
4646
# Cancelling a one-shot listener prevents it from being fired
47-
future = event_loop.create_future()
47+
future = asyncio.get_running_loop().create_future()
4848
one_shot_listener = OneShotResponseListener([c.SYS.Ping.Rsp(partial=True)], future)
4949
one_shot_listener.cancel()
5050

@@ -55,13 +55,13 @@ async def test_cancel(event_loop):
5555
await future
5656

5757

58-
async def test_multi_cancel(event_loop, mocker):
58+
async def test_multi_cancel(mocker):
5959
callback = mocker.Mock()
6060
callback_listener = CallbackResponseListener(
6161
[c.SYS.Ping.Rsp(partial=True)], callback
6262
)
6363

64-
future = event_loop.create_future()
64+
future = asyncio.get_running_loop().create_future()
6565
one_shot_listener = OneShotResponseListener([c.SYS.Ping.Rsp(partial=True)], future)
6666

6767
match = c.SYS.Ping.Rsp(Capabilities=t.MTCapabilities.SYS)
@@ -93,7 +93,7 @@ async def test_api_cancel_listeners(connected_znp, mocker):
9393
)
9494

9595
assert not future.done()
96-
znp.close()
96+
await znp.disconnect()
9797

9898
with pytest.raises(asyncio.CancelledError):
9999
await future

tests/api/test_network_state.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ async def test_state_transfer(from_device, to_device, make_connected_znp):
2323
formed_znp, _ = await make_connected_znp(server_cls=from_device)
2424

2525
await formed_znp.load_network_info()
26-
formed_znp.close()
26+
await formed_znp.disconnect()
2727

2828
empty_znp, _ = await make_connected_znp(server_cls=to_device)
2929

@@ -72,15 +72,15 @@ async def test_broken_cc2531_load_state(device, make_connected_znp, caplog):
7272
await znp.load_network_info()
7373
assert "inconsistent" in caplog.text
7474

75-
znp.close()
75+
await znp.disconnect()
7676

7777

7878
@pytest.mark.parametrize("device", [FormedZStack3CC2531])
7979
async def test_state_write_tclk_zstack3(device, make_connected_znp, caplog):
8080
formed_znp, _ = await make_connected_znp(server_cls=device)
8181

8282
await formed_znp.load_network_info()
83-
formed_znp.close()
83+
await formed_znp.disconnect()
8484

8585
empty_znp, _ = await make_connected_znp(server_cls=device)
8686

@@ -106,7 +106,7 @@ async def test_state_write_tclk_zstack3(device, make_connected_znp, caplog):
106106
async def test_write_settings_fast(device, make_connected_znp):
107107
formed_znp, _ = await make_connected_znp(server_cls=FormedLaunchpadCC26X2R1)
108108
await formed_znp.load_network_info()
109-
formed_znp.close()
109+
await formed_znp.disconnect()
110110

111111
znp, _ = await make_connected_znp(server_cls=device)
112112

@@ -126,7 +126,7 @@ async def test_write_settings_fast(device, make_connected_znp):
126126
async def test_formation_failure_on_corrupted_nvram(device, make_connected_znp):
127127
formed_znp, _ = await make_connected_znp(server_cls=FormedLaunchpadCC26X2R1)
128128
await formed_znp.load_network_info()
129-
formed_znp.close()
129+
await formed_znp.disconnect()
130130

131131
znp, znp_server = await make_connected_znp(server_cls=device)
132132

tests/api/test_request.py

+12-12
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from zigpy_znp.exceptions import CommandNotRecognized, InvalidCommandResponse
1212

1313

14-
async def test_callback_rsp(connected_znp, event_loop):
14+
async def test_callback_rsp(connected_znp):
1515
znp, znp_server = connected_znp
1616

1717
def send_responses():
@@ -20,7 +20,7 @@ def send_responses():
2020
c.AF.DataConfirm.Callback(Endpoint=56, TSN=1, Status=t.Status.SUCCESS)
2121
)
2222

23-
event_loop.call_soon(send_responses)
23+
asyncio.get_running_loop().call_soon(send_responses)
2424

2525
# The UART sometimes replies with a SRSP and an AREQ faster than
2626
# we can register callbacks for both. This method is a workaround.
@@ -150,7 +150,7 @@ async def replier(req):
150150
assert len(znp._unhandled_command.mock_calls) == 0
151151

152152

153-
async def test_callback_rsp_cleanup_concurrent(connected_znp, event_loop, mocker):
153+
async def test_callback_rsp_cleanup_concurrent(connected_znp, mocker):
154154
znp, znp_server = connected_znp
155155

156156
mocker.spy(znp, "_unhandled_command")
@@ -163,7 +163,7 @@ def send_responses():
163163
znp_server.send(c.SYS.OSALTimerExpired.Callback(Id=0xAB))
164164
znp_server.send(c.SYS.OSALTimerExpired.Callback(Id=0xCD))
165165

166-
event_loop.call_soon(send_responses)
166+
asyncio.get_running_loop().call_soon(send_responses)
167167

168168
callback_rsp = await znp.request_callback_rsp(
169169
request=c.UTIL.TimeAlive.Req(),
@@ -183,7 +183,7 @@ def send_responses():
183183
]
184184

185185

186-
async def test_znp_request_kwargs(connected_znp, event_loop):
186+
async def test_znp_request_kwargs(connected_znp):
187187
znp, znp_server = connected_znp
188188

189189
# Invalid format
@@ -196,7 +196,7 @@ async def test_znp_request_kwargs(connected_znp, event_loop):
196196

197197
# Valid format, valid name
198198
ping_rsp = c.SYS.Ping.Rsp(Capabilities=t.MTCapabilities.SYS)
199-
event_loop.call_soon(znp_server.send, ping_rsp)
199+
asyncio.get_running_loop().call_soon(znp_server.send, ping_rsp)
200200
assert (
201201
await znp.request(c.SYS.Ping.Req(), RspCapabilities=t.MTCapabilities.SYS)
202202
) == ping_rsp
@@ -227,7 +227,7 @@ async def test_znp_request_kwargs(connected_znp, event_loop):
227227
)
228228

229229

230-
async def test_znp_request_not_recognized(connected_znp, event_loop):
230+
async def test_znp_request_not_recognized(connected_znp):
231231
znp, _ = connected_znp
232232

233233
# An error is raise when a bad request is sent
@@ -237,11 +237,11 @@ async def test_znp_request_not_recognized(connected_znp, event_loop):
237237
)
238238

239239
with pytest.raises(CommandNotRecognized):
240-
event_loop.call_soon(znp.frame_received, unknown_rsp.to_frame())
240+
asyncio.get_running_loop().call_soon(znp.frame_received, unknown_rsp.to_frame())
241241
await znp.request(request)
242242

243243

244-
async def test_znp_request_wrong_params(connected_znp, event_loop):
244+
async def test_znp_request_wrong_params(connected_znp):
245245
znp, _ = connected_znp
246246

247247
# You cannot specify response kwargs for responses with no response
@@ -250,14 +250,14 @@ async def test_znp_request_wrong_params(connected_znp, event_loop):
250250

251251
# An error is raised when a response with bad params is received
252252
with pytest.raises(InvalidCommandResponse):
253-
event_loop.call_soon(
253+
asyncio.get_running_loop().call_soon(
254254
znp.frame_received,
255255
c.SYS.Ping.Rsp(Capabilities=t.MTCapabilities.SYS).to_frame(),
256256
)
257257
await znp.request(c.SYS.Ping.Req(), RspCapabilities=t.MTCapabilities.APP)
258258

259259

260-
async def test_znp_sreq_srsp(connected_znp, event_loop):
260+
async def test_znp_sreq_srsp(connected_znp):
261261
znp, _ = connected_znp
262262

263263
# Each SREQ must have a corresponding SRSP, so this will fail
@@ -267,7 +267,7 @@ async def test_znp_sreq_srsp(connected_znp, event_loop):
267267

268268
# This will work
269269
ping_rsp = c.SYS.Ping.Rsp(Capabilities=t.MTCapabilities.SYS)
270-
event_loop.call_soon(znp.frame_received, ping_rsp.to_frame())
270+
asyncio.get_running_loop().call_soon(znp.frame_received, ping_rsp.to_frame())
271271

272272
await znp.request(c.SYS.Ping.Req())
273273

tests/api/test_response.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ async def test_wait_responses_empty(connected_znp):
190190
await znp.wait_for_responses([])
191191

192192

193-
async def test_response_callback_simple(connected_znp, event_loop, mocker):
193+
async def test_response_callback_simple(connected_znp, mocker):
194194
znp, _ = connected_znp
195195

196196
sync_callback = mocker.Mock()
@@ -207,7 +207,7 @@ async def test_response_callback_simple(connected_znp, event_loop, mocker):
207207
sync_callback.assert_called_once_with(good_response)
208208

209209

210-
async def test_response_callbacks(connected_znp, event_loop, mocker):
210+
async def test_response_callbacks(connected_znp, mocker):
211211
znp, _ = connected_znp
212212

213213
sync_callback = mocker.Mock()
@@ -270,7 +270,7 @@ async def async_callback(response):
270270
assert len(async_callback_responses) == 3
271271

272272

273-
async def test_wait_for_responses(connected_znp, event_loop):
273+
async def test_wait_for_responses(connected_znp):
274274
znp, _ = connected_znp
275275

276276
response1 = c.SYS.Ping.Rsp(Capabilities=t.MTCapabilities.SYS)

tests/application/test_joining.py

+15
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,10 @@ async def test_permit_join_with_key(device, permit_result, make_application, moc
184184
await app.shutdown()
185185

186186

187+
@mock.patch(
188+
"zigpy.device.Device._initialize",
189+
new=zigpy.device.Device._initialize.__wrapped__, # to disable retries
190+
)
187191
@pytest.mark.parametrize("device", FORMED_DEVICES)
188192
async def test_on_zdo_device_join(device, make_application, mocker):
189193
app, znp_server = make_application(server_cls=device)
@@ -204,6 +208,10 @@ async def test_on_zdo_device_join(device, make_application, mocker):
204208
await app.shutdown()
205209

206210

211+
@mock.patch(
212+
"zigpy.device.Device._initialize",
213+
new=zigpy.device.Device._initialize.__wrapped__, # to disable retries
214+
)
207215
@pytest.mark.parametrize("device", FORMED_DEVICES)
208216
async def test_on_zdo_device_join_and_announce_fast(device, make_application, mocker):
209217
app, znp_server = make_application(server_cls=device)
@@ -258,8 +266,12 @@ async def test_on_zdo_device_join_and_announce_fast(device, make_application, mo
258266
# Everything is cleaned up
259267
assert not app._join_announce_tasks
260268

269+
app.get_device(ieee=ieee).cancel_initialization()
261270
await app.shutdown()
262271

272+
with pytest.raises(asyncio.CancelledError):
273+
await app.get_device(ieee=ieee)._initialize_task
274+
263275

264276
@mock.patch("zigpy_znp.zigbee.application.DEVICE_JOIN_MAX_DELAY", new=0.1)
265277
@mock.patch(
@@ -329,3 +341,6 @@ async def test_on_zdo_device_join_and_announce_slow(device, make_application, mo
329341

330342
app.get_device(ieee=ieee).cancel_initialization()
331343
await app.shutdown()
344+
345+
with pytest.raises(asyncio.CancelledError):
346+
await app.get_device(ieee=ieee)._initialize_task

0 commit comments

Comments
 (0)