33import pytest
44import zigpy .zdo
55from zigpy .zdo .types import ZDOCmd , SizePrefixedSimpleDescriptor
6+ from zigpy .exceptions import DeliveryError
67
78import zigpy_znp .types as t
89import zigpy_znp .config as conf
@@ -319,10 +320,11 @@ async def test_broadcast(device, make_application, mocker):
319320 await app .shutdown ()
320321
321322
322- @pytest .mark .parametrize ("device" , FORMED_DEVICES )
323+ @pytest .mark .parametrize ("device" , [ FormedLaunchpadCC26X2R1 ] )
323324async def test_request_concurrency (device , make_application , mocker ):
324325 app , znp_server = make_application (
325- server_cls = device , client_config = {conf .CONF_MAX_CONCURRENT_REQUESTS : 2 }
326+ server_cls = device ,
327+ client_config = {"znp_config" : {conf .CONF_MAX_CONCURRENT_REQUESTS : 2 }},
326328 )
327329
328330 await app .startup ()
@@ -331,10 +333,16 @@ async def test_request_concurrency(device, make_application, mocker):
331333
332334 # Keep track of how many requests we receive at once
333335 in_flight_requests = 0
336+ did_lock = False
334337
335338 def make_response (req ):
336339 async def callback (req ):
337340 nonlocal in_flight_requests
341+ nonlocal did_lock
342+
343+ if app ._concurrent_requests_semaphore .locked ():
344+ did_lock = True
345+
338346 in_flight_requests += 1
339347 assert in_flight_requests <= 2
340348
@@ -369,13 +377,71 @@ async def callback(req):
369377 sequence = seq ,
370378 data = b"\x00 " ,
371379 )
372- for seq in range (20 )
380+ for seq in range (10 )
373381 ]
374382 )
375383
376384 assert all (status == t .Status .SUCCESS for status , msg in responses )
385+ assert in_flight_requests == 0
386+ assert did_lock
387+
388+ await app .shutdown ()
389+
390+
391+ """
392+ @pytest.mark.parametrize("device", [FormedLaunchpadCC26X2R1])
393+ async def test_request_concurrency_overflow(device, make_application, mocker):
394+ mocker.patch("zigpy_znp.zigbee.application.MAX_WAITING_REQUESTS", new=1)
395+
396+ app, znp_server = make_application(
397+ server_cls=device, client_config={
398+ 'znp_config': {conf.CONF_MAX_CONCURRENT_REQUESTS: 1}
399+ }
400+ )
401+
402+ await app.startup()
403+
404+ device = app.add_initialized_device(ieee=t.EUI64(range(8)), nwk=0xAABB)
405+
406+ def make_response(req):
407+ async def callback(req):
408+ await asyncio.sleep(0.01 * req.TSN)
409+
410+ znp_server.send(c.AF.DataRequestExt.Rsp(Status=t.Status.SUCCESS))
411+ znp_server.send(
412+ c.AF.DataConfirm.Callback(
413+ Status=t.Status.SUCCESS, Endpoint=1, TSN=req.TSN
414+ )
415+ )
416+
417+ asyncio.create_task(callback(req))
418+
419+ znp_server.reply_to(
420+ request=c.AF.DataRequestExt.Req(partial=True), responses=[make_response]
421+ )
422+
423+ # We can only handle 1 in-flight request and 1 enqueued request. Last one will fail.
424+ responses = await asyncio.gather(
425+ *[
426+ app.request(
427+ device,
428+ profile=260,
429+ cluster=1,
430+ src_ep=1,
431+ dst_ep=1,
432+ sequence=seq,
433+ data=b"\x00 ",
434+ )
435+ for seq in range(3)
436+ ], return_exceptions=True)
437+
438+ (rsp1, stat1), (rsp2, stat2), error3 = responses
439+
440+ assert rsp1 == rsp2 == t.Status.SUCCESS
441+ assert isinstance(error3, ValueError)
377442
378443 await app.shutdown()
444+ """
379445
380446
381447@pytest .mark .parametrize ("device" , FORMED_DEVICES )
@@ -717,7 +783,7 @@ def assoc_remove(req):
717783 if can_assoc_remove and final_status == t .Status .SUCCESS :
718784 await req
719785 else :
720- with pytest .raises (RuntimeError ):
786+ with pytest .raises (DeliveryError ):
721787 await req
722788
723789 await did_assoc_get
@@ -804,7 +870,7 @@ def data_confirm_replier(req):
804870 if succeed :
805871 await req
806872 else :
807- with pytest .raises (RuntimeError ):
873+ with pytest .raises (DeliveryError ):
808874 await req
809875
810876 # In either case only one source routing attempt is performed
0 commit comments