diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index fc12ec065a9..45c04651461 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -389,6 +389,7 @@ async def _async_watch_pending_setups(hass: core.HomeAssistant) -> None: """Periodic log of setups that are pending for longer than LOG_SLOW_STARTUP_INTERVAL.""" loop_count = 0 setup_started: dict[str, datetime] = hass.data[DATA_SETUP_STARTED] + previous_was_empty = True while True: now = dt_util.utcnow() remaining_with_setup_started = { @@ -396,9 +397,11 @@ async def _async_watch_pending_setups(hass: core.HomeAssistant) -> None: for domain in setup_started } _LOGGER.debug("Integration remaining: %s", remaining_with_setup_started) - async_dispatcher_send( - hass, SIGNAL_BOOTSTRAP_INTEGRATONS, remaining_with_setup_started - ) + if remaining_with_setup_started or not previous_was_empty: + async_dispatcher_send( + hass, SIGNAL_BOOTSTRAP_INTEGRATONS, remaining_with_setup_started + ) + previous_was_empty = not remaining_with_setup_started await asyncio.sleep(SLOW_STARTUP_CHECK_INTERVAL) loop_count += SLOW_STARTUP_CHECK_INTERVAL diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index 24646386278..1fecf7be96b 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -7,8 +7,10 @@ from unittest.mock import Mock, patch import pytest from homeassistant import bootstrap, core, runner +from homeassistant.bootstrap import SIGNAL_BOOTSTRAP_INTEGRATONS import homeassistant.config as config_util from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.dispatcher import async_dispatcher_connect import homeassistant.util.dt as dt_util from tests.common import ( @@ -610,3 +612,60 @@ async def test_setup_safe_mode_if_no_frontend( assert hass.config.skip_pip assert hass.config.internal_url == "http://192.168.1.100:8123" assert hass.config.external_url == "https://abcdef.ui.nabu.casa" + + +@pytest.mark.parametrize("load_registries", [False]) +async def test_empty_integrations_list_is_only_sent_at_the_end_of_bootstrap(hass): + """Test empty integrations list is only sent at the end of bootstrap.""" + order = [] + + def gen_domain_setup(domain): + async def async_setup(hass, config): + order.append(domain) + await asyncio.sleep(0.1) + + async def _background_task(): + await asyncio.sleep(0.2) + + await hass.async_create_task(_background_task()) + return True + + return async_setup + + mock_integration( + hass, + MockModule( + domain="normal_integration", + async_setup=gen_domain_setup("normal_integration"), + partial_manifest={"after_dependencies": ["an_after_dep"]}, + ), + ) + mock_integration( + hass, + MockModule( + domain="an_after_dep", + async_setup=gen_domain_setup("an_after_dep"), + ), + ) + + integrations = [] + + @core.callback + def _bootstrap_integrations(data): + integrations.append(data) + + async_dispatcher_connect( + hass, SIGNAL_BOOTSTRAP_INTEGRATONS, _bootstrap_integrations + ) + with patch.object(bootstrap, "SLOW_STARTUP_CHECK_INTERVAL", 0.05): + await bootstrap._async_set_up_integrations( + hass, {"normal_integration": {}, "an_after_dep": {}} + ) + + assert integrations[0] != {} + assert "an_after_dep" in integrations[0] + assert integrations[-3] != {} + assert integrations[-1] == {} + + assert "normal_integration" in hass.config.components + assert order == ["an_after_dep", "normal_integration"]