11import asyncio
2+ from unittest .mock import patch
23
34import pytest
45
56import zigpy_znp .config as conf
67from zigpy_znp .uart import connect as uart_connect
78from zigpy_znp .zigbee .application import ControllerApplication
89
9- from ..conftest import FORMED_DEVICES , FormedLaunchpadCC26X2R1 , swap_attribute
10+ from ..conftest import FORMED_DEVICES , FormedLaunchpadCC26X2R1
1011
1112
1213async def test_no_double_connect (make_znp_server , mocker ):
@@ -57,7 +58,7 @@ async def test_probe_unsuccessful():
5758
5859
5960@pytest .mark .parametrize ("device" , FORMED_DEVICES )
60- async def test_probe_unsuccessful_slow (device , make_znp_server , mocker ):
61+ async def test_probe_unsuccessful_slow1 (device , make_znp_server , mocker ):
6162 znp_server = make_znp_server (server_cls = device , shorten_delays = False )
6263
6364 # Don't respond to anything
@@ -74,6 +75,24 @@ async def test_probe_unsuccessful_slow(device, make_znp_server, mocker):
7475 assert not any ([t ._is_connected for t in znp_server ._transports ])
7576
7677
78+ @pytest .mark .parametrize ("device" , FORMED_DEVICES )
79+ async def test_probe_unsuccessful_slow2 (device , make_znp_server , mocker ):
80+ znp_server = make_znp_server (server_cls = device , shorten_delays = False )
81+
82+ # Don't respond to anything
83+ znp_server ._listeners .clear ()
84+
85+ mocker .patch ("zigpy_znp.zigbee.application.PROBE_TIMEOUT" , new = 0.1 )
86+
87+ assert not (
88+ await ControllerApplication .probe (
89+ conf .SCHEMA_DEVICE ({conf .CONF_DEVICE_PATH : znp_server .serial_port })
90+ )
91+ )
92+
93+ assert not any ([t ._is_connected for t in znp_server ._transports ])
94+
95+
7796@pytest .mark .parametrize ("device" , FORMED_DEVICES )
7897async def test_probe_successful (device , make_znp_server ):
7998 znp_server = make_znp_server (server_cls = device , shorten_delays = False )
@@ -100,8 +119,8 @@ async def test_probe_multiple(device, make_znp_server):
100119
101120
102121@pytest .mark .parametrize ("device" , FORMED_DEVICES )
103- async def test_reconnect (device , event_loop , make_application ):
104- app , znp_server = make_application (
122+ async def test_reconnect (device , make_application ):
123+ app , znp_server = await make_application (
105124 server_cls = device ,
106125 client_config = {
107126 # Make auto-reconnection happen really fast
@@ -118,7 +137,7 @@ async def test_reconnect(device, event_loop, make_application):
118137 assert app ._znp is not None
119138
120139 # Don't reply to anything for a bit
121- with swap_attribute (znp_server , "frame_received" , lambda _ : None ):
140+ with patch . object (znp_server , "frame_received" , lambda _ : None ):
122141 # Now that we're connected, have the server close the connection
123142 znp_server ._uart ._transport .close ()
124143
@@ -143,7 +162,7 @@ async def test_reconnect(device, event_loop, make_application):
143162
144163@pytest .mark .parametrize ("device" , FORMED_DEVICES )
145164async def test_shutdown_from_app (device , mocker , make_application ):
146- app , znp_server = make_application (server_cls = device )
165+ app , znp_server = await make_application (server_cls = device )
147166
148167 await app .startup (auto_form = False )
149168
@@ -159,7 +178,7 @@ async def test_shutdown_from_app(device, mocker, make_application):
159178
160179
161180async def test_clean_shutdown (make_application ):
162- app , znp_server = make_application (server_cls = FormedLaunchpadCC26X2R1 )
181+ app , znp_server = await make_application (server_cls = FormedLaunchpadCC26X2R1 )
163182 await app .startup (auto_form = False )
164183
165184 # This should not throw
@@ -170,7 +189,7 @@ async def test_clean_shutdown(make_application):
170189
171190
172191async def test_multiple_shutdown (make_application ):
173- app , znp_server = make_application (server_cls = FormedLaunchpadCC26X2R1 )
192+ app , znp_server = await make_application (server_cls = FormedLaunchpadCC26X2R1 )
174193 await app .startup (auto_form = False )
175194
176195 await app .shutdown ()
@@ -179,10 +198,10 @@ async def test_multiple_shutdown(make_application):
179198
180199
181200@pytest .mark .parametrize ("device" , FORMED_DEVICES )
182- async def test_reconnect_lockup (device , event_loop , make_application , mocker ):
201+ async def test_reconnect_lockup (device , make_application , mocker ):
183202 mocker .patch ("zigpy_znp.zigbee.application.WATCHDOG_PERIOD" , 0.1 )
184203
185- app , znp_server = make_application (
204+ app , znp_server = await make_application (
186205 server_cls = device ,
187206 client_config = {
188207 # Make auto-reconnection happen really fast
@@ -197,7 +216,7 @@ async def test_reconnect_lockup(device, event_loop, make_application, mocker):
197216 await app .startup (auto_form = False )
198217
199218 # Stop responding
200- with swap_attribute (znp_server , "frame_received" , lambda _ : None ):
219+ with patch . object (znp_server , "frame_received" , lambda _ : None ):
201220 assert app ._znp is not None
202221 assert app ._reconnect_task .done ()
203222
@@ -219,15 +238,16 @@ async def test_reconnect_lockup(device, event_loop, make_application, mocker):
219238 await app .shutdown ()
220239
221240
222- @pytest .mark .parametrize ("device" , FORMED_DEVICES )
223- async def test_reconnect_lockup_pyserial (device , event_loop , make_application , mocker ):
241+ @pytest .mark .parametrize ("device" , [ FormedLaunchpadCC26X2R1 ] )
242+ async def test_reconnect_lockup_pyserial (device , make_application , mocker ):
224243 mocker .patch ("zigpy_znp.zigbee.application.WATCHDOG_PERIOD" , 0.1 )
225244
226- app , znp_server = make_application (
245+ app , znp_server = await make_application (
227246 server_cls = device ,
228247 client_config = {
229248 conf .CONF_ZNP_CONFIG : {
230- conf .CONF_AUTO_RECONNECT_RETRY_DELAY : 0.1 ,
249+ conf .CONF_AUTO_RECONNECT_RETRY_DELAY : 0.01 ,
250+ conf .CONF_SREQ_TIMEOUT : 0.1 ,
231251 }
232252 },
233253 )
@@ -242,20 +262,20 @@ async def test_reconnect_lockup_pyserial(device, event_loop, make_application, m
242262 # We are connected
243263 assert app ._znp is not None
244264
245- did_load_info = asyncio .get_running_loop ().create_future ()
265+ did_start_network = asyncio .get_running_loop ().create_future ()
246266
247- async def patched_load_network_info ( * , old_load = app .load_network_info ):
267+ async def patched_start_network ( old_start_network = app .start_network , ** kwargs ):
248268 try :
249- return await old_load ( )
269+ return await old_start_network ( ** kwargs )
250270 finally :
251- did_load_info .set_result (True )
271+ did_start_network .set_result (True )
252272
253- with swap_attribute (app , "load_network_info " , patched_load_network_info ):
273+ with patch . object (app , "start_network " , patched_start_network ):
254274 # "Drop" the connection like PySerial
255275 app ._znp ._uart .connection_lost (exc = None )
256276
257277 # Wait until we are reconnecting
258- await did_load_info
278+ await did_start_network
259279
260280 # "Drop" the connection like PySerial again, but during connect
261281 app ._znp ._uart .connection_lost (exc = None )
@@ -269,3 +289,49 @@ async def patched_load_network_info(*, old_load=app.load_network_info):
269289 assert app ._znp and app ._znp ._uart
270290
271291 await app .shutdown ()
292+
293+
294+ @pytest .mark .parametrize ("device" , [FormedLaunchpadCC26X2R1 ])
295+ async def test_disconnect (device , make_application ):
296+ app , znp_server = await make_application (
297+ server_cls = device ,
298+ client_config = {
299+ conf .CONF_ZNP_CONFIG : {
300+ conf .CONF_SREQ_TIMEOUT : 0.1 ,
301+ }
302+ },
303+ )
304+
305+ assert app ._znp is None
306+ await app .connect ()
307+
308+ assert app ._znp is not None
309+
310+ await app .disconnect ()
311+ assert app ._znp is None
312+
313+ await app .disconnect ()
314+ await app .disconnect ()
315+
316+
317+ @pytest .mark .parametrize ("device" , [FormedLaunchpadCC26X2R1 ])
318+ async def test_disconnect_failure (device , make_application ):
319+ app , znp_server = await make_application (
320+ server_cls = device ,
321+ client_config = {
322+ conf .CONF_ZNP_CONFIG : {
323+ conf .CONF_SREQ_TIMEOUT : 0.1 ,
324+ }
325+ },
326+ )
327+
328+ assert app ._znp is None
329+ await app .connect ()
330+
331+ assert app ._znp is not None
332+
333+ with patch .object (app ._znp , "reset" , side_effect = RuntimeError ("An error" )):
334+ # Runs without error
335+ await app .disconnect ()
336+
337+ assert app ._znp is None
0 commit comments