From 6371b344b939b299099589f374c6695e37d09ac7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 24 Mar 2024 07:38:05 -1000 Subject: [PATCH] Ensure discovery can setup legacy device tracker platforms (#114101) --- .../components/device_tracker/__init__.py | 10 +--- .../components/device_tracker/legacy.py | 53 ++++++++++++++----- tests/components/device_tracker/test_init.py | 28 +++++++++- 3 files changed, 67 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index d453c101a52..ca78b1cbdc5 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -69,15 +69,7 @@ def is_on(hass: HomeAssistant, entity_id: str) -> bool: async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the device tracker.""" - # - # Legacy and platforms load in a non-awaited tracked task - # to ensure device tracker setup can continue and config - # entry integrations are not waiting for legacy device - # tracker platforms to be set up. - # - hass.async_create_task( - async_setup_legacy_integration(hass, config), eager_start=True - ) + async_setup_legacy_integration(hass, config) return True diff --git a/homeassistant/components/device_tracker/legacy.py b/homeassistant/components/device_tracker/legacy.py index c3aec89be90..91cf35f43bd 100644 --- a/homeassistant/components/device_tracker/legacy.py +++ b/homeassistant/components/device_tracker/legacy.py @@ -201,9 +201,47 @@ def see( hass.services.call(DOMAIN, SERVICE_SEE, data) -async def async_setup_integration(hass: HomeAssistant, config: ConfigType) -> None: +@callback +def async_setup_integration(hass: HomeAssistant, config: ConfigType) -> None: + """Set up the legacy integration.""" + # The tracker is loaded in the _async_setup_integration task so + # we create a future to avoid waiting on it here so that only + # async_platform_discovered will have to wait in the rare event + # a custom component still uses the legacy device tracker discovery. + tracker_future: asyncio.Future[DeviceTracker] = hass.loop.create_future() + + async def async_platform_discovered( + p_type: str, info: dict[str, Any] | None + ) -> None: + """Load a platform.""" + platform = await async_create_platform_type(hass, config, p_type, {}) + + if platform is None or platform.type != PLATFORM_TYPE_LEGACY: + return + + tracker = await tracker_future + await platform.async_setup_legacy(hass, tracker, info) + + discovery.async_listen_platform(hass, DOMAIN, async_platform_discovered) + # + # Legacy and platforms load in a non-awaited tracked task + # to ensure device tracker setup can continue and config + # entry integrations are not waiting for legacy device + # tracker platforms to be set up. + # + hass.async_create_task( + _async_setup_integration(hass, config, tracker_future), eager_start=True + ) + + +async def _async_setup_integration( + hass: HomeAssistant, + config: ConfigType, + tracker_future: asyncio.Future[DeviceTracker], +) -> None: """Set up the legacy integration.""" tracker = await get_tracker(hass, config) + tracker_future.set_result(tracker) async def async_see_service(call: ServiceCall) -> None: """Service to see a device.""" @@ -227,19 +265,6 @@ async def async_setup_integration(hass: HomeAssistant, config: ConfigType) -> No if setup_tasks: await asyncio.wait(setup_tasks) - async def async_platform_discovered( - p_type: str, info: dict[str, Any] | None - ) -> None: - """Load a platform.""" - platform = await async_create_platform_type(hass, config, p_type, {}) - - if platform is None or platform.type != PLATFORM_TYPE_LEGACY: - return - - await platform.async_setup_legacy(hass, tracker, info) - - discovery.async_listen_platform(hass, DOMAIN, async_platform_discovered) - # Clean up stale devices cancel_update_stale = async_track_utc_time_change( hass, tracker.async_update_stale, second=range(0, 60, 5) diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index f926b2f8e5f..188b581de40 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -235,7 +235,8 @@ async def test_discover_platform( """Test discovery of device_tracker demo platform.""" await async_setup_component(hass, "homeassistant", {}) await async_setup_component(hass, device_tracker.DOMAIN, {}) - await hass.async_block_till_done() + # async_block_till_done is intentionally missing here so we + # can verify async_load_platform still works without it with patch("homeassistant.components.device_tracker.legacy.update_config"): await discovery.async_load_platform( hass, device_tracker.DOMAIN, "demo", {"test_key": "test_val"}, {"bla": {}} @@ -251,6 +252,31 @@ async def test_discover_platform( ) +async def test_discover_platform_missing_platform( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Test discovery of device_tracker missing platform.""" + await async_setup_component(hass, "homeassistant", {}) + await async_setup_component(hass, device_tracker.DOMAIN, {}) + # async_block_till_done is intentionally missing here so we + # can verify async_load_platform still works without it + with patch("homeassistant.components.device_tracker.legacy.update_config"): + await discovery.async_load_platform( + hass, + device_tracker.DOMAIN, + "its_not_there", + {"test_key": "test_val"}, + {"bla": {}}, + ) + await hass.async_block_till_done() + assert device_tracker.DOMAIN in hass.config.components + assert ( + "Unable to prepare setup for platform 'its_not_there.device_tracker'" + in caplog.text + ) + # This test should not generate an unhandled exception + + async def test_update_stale( hass: HomeAssistant, mock_device_tracker_conf: list[legacy.Device],