Avoid waiting for integration platforms in the parent integration (#112467)

This commit is contained in:
J. Nick Koston 2024-03-05 21:16:42 -10:00 committed by GitHub
parent 87739bc072
commit f3a9756f81
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 126 additions and 5 deletions

View File

@ -125,7 +125,7 @@ class BackupManager:
async def load_platforms(self) -> None:
"""Load backup platforms."""
await integration_platform.async_process_integration_platforms(
self.hass, DOMAIN, self._add_platform
self.hass, DOMAIN, self._add_platform, wait_for_platforms=True
)
LOGGER.debug("Loaded %s platforms", len(self.platforms))
self.loaded_platforms = True

View File

@ -71,7 +71,9 @@ async def async_get_energy_platforms(
platforms[domain] = cast(EnergyPlatform, platform).async_get_solar_forecast
await async_process_integration_platforms(hass, DOMAIN, _process_energy_platform)
await async_process_integration_platforms(
hass, DOMAIN, _process_energy_platform, wait_for_platforms=True
)
return platforms

View File

@ -15,7 +15,9 @@ async def async_process_hardware_platforms(hass: HomeAssistant) -> None:
"""Start processing hardware platforms."""
hass.data[DOMAIN]["hardware_platform"] = {}
await async_process_integration_platforms(hass, DOMAIN, _register_hardware_platform)
await async_process_integration_platforms(
hass, DOMAIN, _register_hardware_platform, wait_for_platforms=True
)
@callback

View File

@ -143,7 +143,10 @@ class MultiprotocolAddonManager(WaitingAddonManager):
async def async_setup(self) -> None:
"""Set up the manager."""
await async_process_integration_platforms(
self._hass, "silabs_multiprotocol", self._register_multipan_platform
self._hass,
"silabs_multiprotocol",
self._register_multipan_platform,
wait_for_platforms=True,
)
await self.async_load()

View File

@ -102,7 +102,9 @@ async def async_process_repairs_platforms(hass: HomeAssistant) -> None:
"""Start processing repairs platforms."""
hass.data[DOMAIN]["platforms"] = {}
await async_process_integration_platforms(hass, DOMAIN, _register_repairs_platform)
await async_process_integration_platforms(
hass, DOMAIN, _register_repairs_platform, wait_for_platforms=True
)
@callback

View File

@ -157,6 +157,7 @@ async def async_process_integration_platforms(
platform_name: str,
# Any = platform.
process_platform: Callable[[HomeAssistant, str, Any], Awaitable[None] | None],
wait_for_platforms: bool = False,
) -> None:
"""Process a specific platform for all current and future loaded integrations."""
if DATA_INTEGRATION_PLATFORMS not in hass.data:
@ -194,6 +195,36 @@ async def async_process_integration_platforms(
if not top_level_components:
return
# We create a task here for two reasons:
#
# 1. We want the integration that provides the integration platform to
# not be delayed by waiting on each individual platform to be processed
# since the import or the integration platforms themselves may have to
# schedule I/O or executor jobs.
#
# 2. We want the behavior to be the same as if the integration that has
# the integration platform is loaded after the platform is processed.
#
# We use hass.async_create_task instead of asyncio.create_task because
# we want to make sure that startup waits for the task to complete.
#
future = hass.async_create_task(
_async_process_integration_platforms(
hass, platform_name, top_level_components.copy(), process_job
),
eager_start=True,
)
if wait_for_platforms:
await future
async def _async_process_integration_platforms(
hass: HomeAssistant,
platform_name: str,
top_level_components: set[str],
process_job: HassJob,
) -> None:
"""Process integration platforms for a component."""
integrations = await async_get_integrations(hass, top_level_components)
loaded_integrations: list[Integration] = [
integration

View File

@ -19,6 +19,7 @@ async def test_accuweather_system_health(
aioclient_mock.get("https://dataservice.accuweather.com/", text="")
hass.config.components.add(DOMAIN)
assert await async_setup_component(hass, "system_health", {})
await hass.async_block_till_done()
hass.data[DOMAIN] = {}
hass.data[DOMAIN]["0123xyz"] = {}
@ -43,6 +44,7 @@ async def test_accuweather_system_health_fail(
aioclient_mock.get("https://dataservice.accuweather.com/", exc=ClientError)
hass.config.components.add(DOMAIN)
assert await async_setup_component(hass, "system_health", {})
await hass.async_block_till_done()
hass.data[DOMAIN] = {}
hass.data[DOMAIN]["0123xyz"] = {}

View File

@ -19,6 +19,7 @@ async def test_airly_system_health(
aioclient_mock.get("https://airapi.airly.eu/v2/", text="")
hass.config.components.add(DOMAIN)
assert await async_setup_component(hass, "system_health", {})
await hass.async_block_till_done()
hass.data[DOMAIN] = {}
hass.data[DOMAIN]["0123xyz"] = Mock(
@ -47,6 +48,7 @@ async def test_airly_system_health_fail(
aioclient_mock.get("https://airapi.airly.eu/v2/", exc=ClientError)
hass.config.components.add(DOMAIN)
assert await async_setup_component(hass, "system_health", {})
await hass.async_block_till_done()
hass.data[DOMAIN] = {}
hass.data[DOMAIN]["0123xyz"] = Mock(

View File

@ -11,6 +11,7 @@ async def test_humanify_alexa_event(hass: HomeAssistant) -> None:
hass.config.components.add("recorder")
await async_setup_component(hass, "alexa", {})
await async_setup_component(hass, "logbook", {})
await hass.async_block_till_done()
hass.states.async_set("light.kitchen", "on", {"friendly_name": "Kitchen Light"})
results = mock_humanify(

View File

@ -15,6 +15,7 @@ async def test_recording_event(
"""Test recording event."""
hass.config.components.add("recorder")
assert await async_setup_component(hass, "logbook", {})
await hass.async_block_till_done()
entry = MockConfigEntry()
entry.add_to_hass(hass)

View File

@ -1862,6 +1862,7 @@ async def test_logbook_humanify_automation_triggered_event(hass: HomeAssistant)
hass.config.components.add("recorder")
await async_setup_component(hass, automation.DOMAIN, {})
await async_setup_component(hass, "logbook", {})
await hass.async_block_till_done()
event1, event2 = mock_humanify(
hass,

View File

@ -11,6 +11,7 @@ async def test_humanify_automation_trigger_event(hass: HomeAssistant) -> None:
hass.config.components.add("recorder")
assert await async_setup_component(hass, "automation", {})
assert await async_setup_component(hass, "logbook", {})
await hass.async_block_till_done()
context = Context()
event1, event2 = mock_humanify(

View File

@ -199,6 +199,7 @@ async def test_loading_platforms(
),
)
await manager.load_platforms()
await hass.async_block_till_done()
assert manager.loaded_platforms
assert len(manager.platforms) == 1
@ -218,6 +219,7 @@ async def test_not_loading_bad_platforms(
await _setup_mock_domain(hass)
await manager.load_platforms()
await hass.async_block_till_done()
assert manager.loaded_platforms
assert len(manager.platforms) == 0

View File

@ -74,6 +74,7 @@ async def test_humanifying_deconz_alarm_event(
hass.config.components.add("recorder")
assert await async_setup_component(hass, "logbook", {})
await hass.async_block_till_done()
events = mock_humanify(
hass,
@ -183,6 +184,7 @@ async def test_humanifying_deconz_event(
hass.config.components.add("recorder")
assert await async_setup_component(hass, "logbook", {})
await hass.async_block_till_done()
events = mock_humanify(
hass,

View File

@ -89,6 +89,7 @@ async def test_save_preferences(
mock_energy_platform,
) -> None:
"""Test we can save preferences."""
await hass.async_block_till_done()
client = await hass_ws_client(hass)
# Test saving default prefs is also valid.
@ -283,6 +284,7 @@ async def test_get_solar_forecast(
entry.add_to_hass(hass)
manager = await data.async_get_manager(hass)
manager.data = data.EnergyManager.default_preferences()
manager.data["energy_sources"].append(
{
@ -292,6 +294,7 @@ async def test_get_solar_forecast(
}
)
client = await hass_ws_client(hass)
await hass.async_block_till_done()
await client.send_json({"id": 5, "type": "energy/solar_forecast"})

View File

@ -21,6 +21,7 @@ async def test_gios_system_health(
await integration.async_get_component()
hass.config.components.add(DOMAIN)
assert await async_setup_component(hass, "system_health", {})
await hass.async_block_till_done()
info = await get_system_health_info(hass, DOMAIN)
@ -40,6 +41,7 @@ async def test_gios_system_health_fail(
await integration.async_get_component()
hass.config.components.add(DOMAIN)
assert await async_setup_component(hass, "system_health", {})
await hass.async_block_till_done()
info = await get_system_health_info(hass, DOMAIN)

View File

@ -18,6 +18,7 @@ async def test_humanify_command_received(hass: HomeAssistant) -> None:
hass.config.components.add("frontend")
hass.config.components.add("google_assistant")
assert await async_setup_component(hass, "logbook", {})
await hass.async_block_till_done()
hass.states.async_set(
"light.kitchen", "on", {ATTR_FRIENDLY_NAME: "The Kitchen Lights"}

View File

@ -759,6 +759,7 @@ async def test_service_group_services_add_remove_entities(hass: HomeAssistant) -
assert await async_setup_component(hass, "person", {})
with assert_setup_component(0, "group"):
await async_setup_component(hass, "group", {"group": {}})
await hass.async_block_till_done()
assert hass.services.has_service("group", group.SERVICE_SET)

View File

@ -29,6 +29,7 @@ async def test_hassio_system_health(
hass.config.components.add("hassio")
assert await async_setup_component(hass, "system_health", {})
await hass.async_block_till_done()
hass.data["hassio_info"] = {
"channel": "stable",
@ -88,6 +89,7 @@ async def test_hassio_system_health_with_issues(
hass.config.components.add("hassio")
assert await async_setup_component(hass, "system_health", {})
await hass.async_block_till_done()
hass.data["hassio_info"] = {"channel": "stable"}
hass.data["hassio_host_info"] = {}

View File

@ -33,6 +33,7 @@ async def test_humanify_homekit_changed_event(
with patch("homeassistant.components.homekit.HomeKit"):
assert await async_setup_component(hass, "homekit", {"homekit": {}})
assert await async_setup_component(hass, "logbook", {})
await hass.async_block_till_done()
event1, event2 = mock_humanify(
hass,

View File

@ -17,6 +17,7 @@ async def test_ipma_system_health(
hass.config.components.add("ipma")
assert await async_setup_component(hass, "system_health", {})
await hass.async_block_till_done()
info = await get_system_health_info(hass, "ipma")

View File

@ -27,6 +27,7 @@ async def test_system_health(
hass.config.components.add(DOMAIN)
assert await async_setup_component(hass, "system_health", {})
await hass.async_block_till_done()
MockConfigEntry(
domain=DOMAIN,
@ -66,6 +67,7 @@ async def test_system_health_failed_connect(
hass.config.components.add(DOMAIN)
assert await async_setup_component(hass, "system_health", {})
await hass.async_block_till_done()
MockConfigEntry(
domain=DOMAIN,

View File

@ -19,6 +19,7 @@ async def test_nextdns_system_health(
aioclient_mock.get(API_ENDPOINT, text="")
hass.config.components.add(DOMAIN)
assert await async_setup_component(hass, "system_health", {})
await hass.async_block_till_done()
info = await get_system_health_info(hass, DOMAIN)
@ -36,6 +37,7 @@ async def test_nextdns_system_health_fail(
aioclient_mock.get(API_ENDPOINT, exc=ClientError)
hass.config.components.add(DOMAIN)
assert await async_setup_component(hass, "system_health", {})
await hass.async_block_till_done()
info = await get_system_health_info(hass, DOMAIN)

View File

@ -18,6 +18,7 @@ async def test_hardware_info(
"""Test we can get the board info."""
mock_integration(hass, MockModule("hassio"))
await async_setup_component(hass, HASSIO_DOMAIN, {})
await hass.async_block_till_done()
# Setup the config entry
config_entry = MockConfigEntry(
@ -70,6 +71,7 @@ async def test_hardware_info_fail(
"""Test async_info raises if os_info is not as expected."""
mock_integration(hass, MockModule("hassio"))
await async_setup_component(hass, HASSIO_DOMAIN, {})
await hass.async_block_till_done()
# Setup the config entry
config_entry = MockConfigEntry(

View File

@ -898,6 +898,7 @@ async def test_logbook_humanify_script_started_event(hass: HomeAssistant) -> Non
hass.config.components.add("recorder")
await async_setup_component(hass, DOMAIN, {})
await async_setup_component(hass, "logbook", {})
await hass.async_block_till_done()
event1, event2 = mock_humanify(
hass,

View File

@ -31,6 +31,7 @@ async def test_humanify_shelly_click_event_block_device(
hass.config.components.add("recorder")
assert await async_setup_component(hass, "logbook", {})
await hass.async_block_till_done()
event1, event2 = mock_humanify(
hass,
@ -81,6 +82,7 @@ async def test_humanify_shelly_click_event_rpc_device(
hass.config.components.add("recorder")
assert await async_setup_component(hass, "logbook", {})
await hass.async_block_till_done()
event1, event2 = mock_humanify(
hass,

View File

@ -84,6 +84,7 @@ async def test_zha_logbook_event_device_with_triggers(
hass.config.components.add("recorder")
assert await async_setup_component(hass, "logbook", {})
await hass.async_block_till_done()
events = mock_humanify(
hass,
@ -162,6 +163,7 @@ async def test_zha_logbook_event_device_no_triggers(
hass.config.components.add("recorder")
assert await async_setup_component(hass, "logbook", {})
await hass.async_block_till_done()
events = mock_humanify(
hass,
@ -246,6 +248,7 @@ async def test_zha_logbook_event_device_no_device(
hass.config.components.add("recorder")
assert await async_setup_component(hass, "logbook", {})
await hass.async_block_till_done()
events = mock_humanify(
hass,

View File

@ -25,6 +25,7 @@ async def test_humanifying_zwave_js_notification_event(
hass.config.components.add("recorder")
assert await async_setup_component(hass, "logbook", {})
await hass.async_block_till_done()
events = mock_humanify(
hass,
@ -108,6 +109,7 @@ async def test_humanifying_zwave_js_value_notification_event(
hass.config.components.add("recorder")
assert await async_setup_component(hass, "logbook", {})
await hass.async_block_till_done()
events = mock_humanify(
hass,

View File

@ -16,6 +16,44 @@ from homeassistant.setup import ATTR_COMPONENT, EVENT_COMPONENT_LOADED
from tests.common import mock_platform
async def test_process_integration_platforms_with_wait(hass: HomeAssistant) -> None:
"""Test processing integrations."""
loaded_platform = Mock()
mock_platform(hass, "loaded.platform_to_check", loaded_platform)
hass.config.components.add("loaded")
event_platform = Mock()
mock_platform(hass, "event.platform_to_check", event_platform)
processed = []
async def _process_platform(hass, domain, platform):
"""Process platform."""
processed.append((domain, platform))
await async_process_integration_platforms(
hass, "platform_to_check", _process_platform, wait_for_platforms=True
)
# No block till done here, we want to make sure it waits for the platform
assert len(processed) == 1
assert processed[0][0] == "loaded"
assert processed[0][1] == loaded_platform
hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: "event"})
await hass.async_block_till_done()
assert len(processed) == 2
assert processed[1][0] == "event"
assert processed[1][1] == event_platform
hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: "event"})
await hass.async_block_till_done()
# Firing again should not check again
assert len(processed) == 2
async def test_process_integration_platforms(hass: HomeAssistant) -> None:
"""Test processing integrations."""
loaded_platform = Mock()
@ -34,6 +72,7 @@ async def test_process_integration_platforms(hass: HomeAssistant) -> None:
await async_process_integration_platforms(
hass, "platform_to_check", _process_platform
)
await hass.async_block_till_done()
assert len(processed) == 1
assert processed[0][0] == "loaded"
@ -77,6 +116,7 @@ async def test_process_integration_platforms_import_fails(
await async_process_integration_platforms(
hass, "platform_to_check", _process_platform
)
await hass.async_block_till_done()
assert len(processed) == 0
assert "Unexpected error importing platform_to_check for loaded" in caplog.text
@ -115,6 +155,7 @@ async def test_process_integration_platforms_import_fails_after_registered(
await async_process_integration_platforms(
hass, "platform_to_check", _process_platform
)
await hass.async_block_till_done()
assert len(processed) == 1
assert processed[0][0] == "loaded"
@ -166,6 +207,7 @@ async def test_process_integration_platforms_non_compliant(
await async_process_integration_platforms(
hass, "platform_to_check", process_platform
)
await hass.async_block_till_done()
assert len(processed) == 0
assert "Exception in " in caplog.text
@ -204,6 +246,7 @@ async def test_broken_integration(
await async_process_integration_platforms(
hass, "platform_to_check", _process_platform
)
await hass.async_block_till_done()
# This should never actually happen as the component cannot be
# in hass.config.components without a loaded manifest
@ -226,5 +269,6 @@ async def test_process_integration_platforms_no_integrations(
await async_process_integration_platforms(
hass, "platform_to_check", _process_platform
)
await hass.async_block_till_done()
assert len(processed) == 0