1010
1111import pytest
1212
13- from zeroconf import IPVersion , ServiceInfo , Zeroconf , _engine , _listener
13+ from zeroconf import InterfaceChoice , IPVersion , ServiceInfo , Zeroconf , _engine , _listener
1414from zeroconf ._engine import _interface_key , _listen_socket_supports
1515from zeroconf ._transport import _strip_zone , _WrappedTransport , make_wrapped_transport
1616from zeroconf .asyncio import AsyncZeroconf
@@ -432,6 +432,34 @@ async def fake_wrap(sock: object, is_sender: bool) -> _WrappedTransport:
432432 assert engine .senders [0 ].interface_key == ("10.0.0.9" , 0 )
433433
434434
435+ @pytest .mark .asyncio
436+ async def test_update_interfaces_transient_empty_set_is_noop (
437+ aiozc_loopback : AsyncZeroconf , caplog : pytest .LogCaptureFixture
438+ ) -> None :
439+ """An All instance that transiently resolves to zero interfaces logs and no-ops."""
440+ zc = aiozc_loopback .zeroconf
441+ await zc .async_wait_for_start ()
442+ engine = zc .engine
443+ before = (len (engine .senders ), len (engine .readers ), len (engine .protocols ))
444+
445+ # normalize_interface_choice raises for an All instance with no addresses
446+ # (adapter churn); the rescan must not crash the caller's handler.
447+ with (
448+ patch .object (
449+ _engine ,
450+ "normalize_interface_choice" ,
451+ side_effect = RuntimeError ("No interfaces to listen on" ),
452+ ),
453+ caplog .at_level (logging .WARNING ),
454+ ):
455+ added = await engine .async_update_interfaces (InterfaceChoice .All , IPVersion .All , False )
456+
457+ assert added is False
458+ assert "Skipping interface update; no interfaces available" in caplog .text
459+ # Current sockets are left intact rather than torn down.
460+ assert (len (engine .senders ), len (engine .readers ), len (engine .protocols )) == before
461+
462+
435463@pytest .mark .asyncio
436464async def test_update_interfaces_add_failure_adds_no_sender (aiozc_loopback : AsyncZeroconf ) -> None :
437465 """An interface that fails to come up adds no responder socket."""
@@ -450,7 +478,7 @@ async def test_update_interfaces_add_failure_adds_no_sender(aiozc_loopback: Asyn
450478async def test_update_interfaces_rolls_back_membership_on_wrap_failure (
451479 aiozc_loopback : AsyncZeroconf ,
452480) -> None :
453- """If endpoint creation raises, the interface's join and socket are rolled back ."""
481+ """Endpoint creation failing rolls the interface back and is skipped, not raised ."""
454482 zc = aiozc_loopback .zeroconf
455483 await zc .async_wait_for_start ()
456484 await aiozc_loopback .async_update_interfaces ([])
@@ -462,15 +490,14 @@ async def test_update_interfaces_rolls_back_membership_on_wrap_failure(
462490 patch .object (_engine , "add_interface" , return_value = fake_socket ),
463491 patch .object (_engine , "drop_multicast_member" ) as mock_drop ,
464492 patch .object (_engine .AsyncEngine , "_async_wrap_socket" , new = AsyncMock (side_effect = OSError ("boom" ))),
465- pytest .raises (OSError ),
466493 ):
494+ # Best-effort: the failure is logged and skipped, not propagated.
467495 await aiozc_loopback .async_update_interfaces (["127.0.0.1" ])
468496
469- # The just-joined membership was dropped, the socket closed, and the
470- # failed reconcile left the retained config unchanged.
497+ # The just-joined membership was dropped, the socket closed, no sender added.
471498 mock_drop .assert_called_once ()
472499 fake_socket .close .assert_called_once ()
473- assert zc ._interfaces == []
500+ assert zc .engine . senders == []
474501
475502
476503@pytest .mark .asyncio
@@ -484,10 +511,10 @@ async def test_add_interface_rollback_without_listen_socket(aiozc_loopback: Asyn
484511 patch .object (_engine , "add_interface" , return_value = fake_socket ),
485512 patch .object (_engine , "drop_multicast_member" ) as mock_drop ,
486513 patch .object (_engine .AsyncEngine , "_async_wrap_socket" , new = AsyncMock (side_effect = OSError ("boom" ))),
487- pytest .raises (OSError ),
488514 ):
489- await engine ._async_add_interface ("127.0.0.1" , None , False )
515+ added = await engine ._async_add_interface ("127.0.0.1" , None , False )
490516
517+ assert added is False
491518 fake_socket .close .assert_called_once ()
492519 mock_drop .assert_not_called ()
493520
0 commit comments