Fix setup timings when config entry platform loads are not awaited (#113959)

* Move setup time logging into the context manager

We were fetching the time twice but since the context
manager already has the timing, move it there

* remove log setup assertions from integration test

* tweak logging to give us better data for tracking issues

* redundant

* adjust

* preen

* fixes

* adjust

* make api change internal so nobody uses it

* coverage

* fix test

* fix more tests

* coverage

* more tests assuming internal calls

* fix more

* adjust

* adjust

* fix axis tests

* fix broadlink -- it does not call async_forward_entry_setup

* missed some

* remove useless patch

* rename, detect it both ways

* clear

* debug

* try to fix

* handle phase finishing out while paused

* where its set does not need to know its late as that is an implemenation detail of setup

* where its set does not need to know its late as that is an implemenation detail of setup

* tweak

* simplify

* reduce complexity

* revert order change as it makes review harder

* revert naming changes as it makes review harder

* improve comment

* improve debug

* late dispatch test

* test the other way as well

* Update setup.py

* Update setup.py

* Update setup.py

* simplify

* reduce
This commit is contained in:
J. Nick Koston 2024-03-23 09:26:38 -10:00 committed by GitHub
parent a4f52cc622
commit 4f18f0d902
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 305 additions and 116 deletions

View File

@ -1858,7 +1858,7 @@ class ConfigEntries:
await asyncio.gather( await asyncio.gather(
*( *(
create_eager_task( create_eager_task(
self.async_forward_entry_setup(entry, platform), self._async_forward_entry_setup(entry, platform, False),
name=f"config entry forward setup {entry.title} {entry.domain} {entry.entry_id} {platform}", name=f"config entry forward setup {entry.title} {entry.domain} {entry.entry_id} {platform}",
) )
for platform in platforms for platform in platforms
@ -1874,6 +1874,12 @@ class ConfigEntries:
component also has related platforms, the component will have to component also has related platforms, the component will have to
forward the entry to be setup by that component. forward the entry to be setup by that component.
""" """
return await self._async_forward_entry_setup(entry, domain, True)
async def _async_forward_entry_setup(
self, entry: ConfigEntry, domain: Platform | str, preload_platform: bool
) -> bool:
"""Forward the setup of an entry to a different component."""
# Setup Component if not set up yet # Setup Component if not set up yet
if domain not in self.hass.config.components: if domain not in self.hass.config.components:
with async_pause_setup(self.hass, SetupPhases.WAIT_BASE_PLATFORM_SETUP): with async_pause_setup(self.hass, SetupPhases.WAIT_BASE_PLATFORM_SETUP):
@ -1884,8 +1890,16 @@ class ConfigEntries:
if not result: if not result:
return False return False
integration = await loader.async_get_integration(self.hass, domain) if preload_platform:
# If this is a late setup, we need to make sure the platform is loaded
# so we do not end up waiting for when the EntityComponent calls
# async_prepare_setup_platform
integration = await loader.async_get_integration(self.hass, entry.domain)
if not integration.platforms_are_loaded((domain,)):
with async_pause_setup(self.hass, SetupPhases.WAIT_IMPORT_PLATFORMS):
await integration.async_get_platform(domain)
integration = await loader.async_get_integration(self.hass, domain)
await entry.async_setup(self.hass, integration=integration) await entry.async_setup(self.hass, integration=integration)
return True return True

View File

@ -10,7 +10,6 @@ import contextvars
from enum import StrEnum from enum import StrEnum
import logging.handlers import logging.handlers
import time import time
from timeit import default_timer as timer
from types import ModuleType from types import ModuleType
from typing import Any, Final, TypedDict from typing import Any, Final, TypedDict
@ -351,7 +350,6 @@ async def _async_setup_component( # noqa: C901
}, },
) )
start = timer()
_LOGGER.info("Setting up %s", domain) _LOGGER.info("Setting up %s", domain)
integration_set = {domain} integration_set = {domain}
@ -412,11 +410,8 @@ async def _async_setup_component( # noqa: C901
async_notify_setup_error(hass, domain, integration.documentation) async_notify_setup_error(hass, domain, integration.documentation)
return False return False
finally: finally:
end = timer()
if warn_task: if warn_task:
warn_task.cancel() warn_task.cancel()
_LOGGER.info("Setup of domain %s took %.1f seconds", domain, end - start)
if result is False: if result is False:
log_error("Integration failed to initialize.") log_error("Integration failed to initialize.")
return False return False
@ -663,6 +658,15 @@ class SetupPhases(StrEnum):
"""Wait time for the packages to import.""" """Wait time for the packages to import."""
def _setup_started(
hass: core.HomeAssistant,
) -> dict[tuple[str, str | None], float]:
"""Return the setup started dict."""
if DATA_SETUP_STARTED not in hass.data:
hass.data[DATA_SETUP_STARTED] = {}
return hass.data[DATA_SETUP_STARTED] # type: ignore[no-any-return]
@contextlib.contextmanager @contextlib.contextmanager
def async_pause_setup( def async_pause_setup(
hass: core.HomeAssistant, phase: SetupPhases hass: core.HomeAssistant, phase: SetupPhases
@ -673,7 +677,9 @@ def async_pause_setup(
setting up the base components so we can subtract it setting up the base components so we can subtract it
from the total setup time. from the total setup time.
""" """
if not (running := current_setup_group.get()): if not (running := current_setup_group.get()) or running not in _setup_started(
hass
):
# This means we are likely in a late platform setup # This means we are likely in a late platform setup
# that is running in a task so we do not want # that is running in a task so we do not want
# to subtract out the time later as nothing is waiting # to subtract out the time later as nothing is waiting
@ -689,6 +695,13 @@ def async_pause_setup(
integration, group = running integration, group = running
# Add negative time for the time we waited # Add negative time for the time we waited
_setup_times(hass)[integration][group][phase] = -time_taken _setup_times(hass)[integration][group][phase] = -time_taken
_LOGGER.debug(
"Adding wait for %s for %s (%s) of %.2f",
phase,
integration,
group,
time_taken,
)
def _setup_times( def _setup_times(
@ -726,8 +739,7 @@ def async_start_setup(
yield yield
return return
setup_started: dict[tuple[str, str | None], float] setup_started = _setup_started(hass)
setup_started = hass.data.setdefault(DATA_SETUP_STARTED, {})
current = (integration, group) current = (integration, group)
if current in setup_started: if current in setup_started:
# We are already inside another async_start_setup, this like means we # We are already inside another async_start_setup, this like means we
@ -745,7 +757,26 @@ def async_start_setup(
finally: finally:
time_taken = time.monotonic() - started time_taken = time.monotonic() - started
del setup_started[current] del setup_started[current]
_setup_times(hass)[integration][group][phase] = time_taken group_setup_times = _setup_times(hass)[integration][group]
# We may see the phase multiple times if there are multiple
# platforms, but we only care about the longest time.
group_setup_times[phase] = max(group_setup_times[phase], time_taken)
if group is None:
_LOGGER.info(
"Setup of domain %s took %.2f seconds", integration, time_taken
)
elif _LOGGER.isEnabledFor(logging.DEBUG):
wait_time = -sum(value for value in group_setup_times.values() if value < 0)
calculated_time = time_taken - wait_time
_LOGGER.debug(
"Phase %s for %s (%s) took %.2fs (elapsed=%.2fs) (wait_time=%.2fs)",
phase,
integration,
group,
calculated_time,
time_taken,
wait_time,
)
@callback @callback

View File

@ -35,16 +35,18 @@ from tests.common import async_fire_mqtt_message
from tests.typing import MqttMockHAClient from tests.typing import MqttMockHAClient
@pytest.fixture(name="forward_entry_setup") @pytest.fixture(name="forward_entry_setups")
def hass_mock_forward_entry_setup(hass): def hass_mock_forward_entry_setup(hass):
"""Mock async_forward_entry_setup.""" """Mock async_forward_entry_setups."""
with patch.object(hass.config_entries, "async_forward_entry_setup") as forward_mock: with patch.object(
hass.config_entries, "async_forward_entry_setups"
) as forward_mock:
yield forward_mock yield forward_mock
async def test_device_setup( async def test_device_setup(
hass: HomeAssistant, hass: HomeAssistant,
forward_entry_setup, forward_entry_setups,
config_entry_data, config_entry_data,
setup_config_entry, setup_config_entry,
device_registry: dr.DeviceRegistry, device_registry: dr.DeviceRegistry,
@ -57,11 +59,9 @@ async def test_device_setup(
assert hub.api.vapix.product_type == "Network Camera" assert hub.api.vapix.product_type == "Network Camera"
assert hub.api.vapix.serial_number == "00408C123456" assert hub.api.vapix.serial_number == "00408C123456"
assert len(forward_entry_setup.mock_calls) == 4 assert len(forward_entry_setups.mock_calls) == 1
assert forward_entry_setup.mock_calls[0][1][1] == "binary_sensor" platforms = set(forward_entry_setups.mock_calls[0][1][1])
assert forward_entry_setup.mock_calls[1][1][1] == "camera" assert platforms == {"binary_sensor", "camera", "light", "switch"}
assert forward_entry_setup.mock_calls[2][1][1] == "light"
assert forward_entry_setup.mock_calls[3][1][1] == "switch"
assert hub.config.host == config_entry_data[CONF_HOST] assert hub.config.host == config_entry_data[CONF_HOST]
assert hub.config.model == config_entry_data[CONF_MODEL] assert hub.config.model == config_entry_data[CONF_MODEL]

View File

@ -21,7 +21,7 @@ async def test_device_setup(hass: HomeAssistant) -> None:
device = get_device("Office") device = get_device("Office")
with patch.object( with patch.object(
hass.config_entries, "async_forward_entry_setup" hass.config_entries, "async_forward_entry_setups"
) as mock_forward, patch.object( ) as mock_forward, patch.object(
hass.config_entries.flow, "async_init" hass.config_entries.flow, "async_init"
) as mock_init: ) as mock_init:
@ -32,9 +32,9 @@ async def test_device_setup(hass: HomeAssistant) -> None:
assert mock_setup.api.get_fwversion.call_count == 1 assert mock_setup.api.get_fwversion.call_count == 1
assert mock_setup.factory.call_count == 1 assert mock_setup.factory.call_count == 1
forward_entries = {c[1][1] for c in mock_forward.mock_calls} forward_entries = set(mock_forward.mock_calls[0][1][1])
domains = get_domains(mock_setup.api.type) domains = get_domains(mock_setup.api.type)
assert mock_forward.call_count == len(domains) assert mock_forward.call_count == 1
assert forward_entries == domains assert forward_entries == domains
assert mock_init.call_count == 0 assert mock_init.call_count == 0
@ -46,7 +46,7 @@ async def test_device_setup_authentication_error(hass: HomeAssistant) -> None:
mock_api.auth.side_effect = blke.AuthenticationError() mock_api.auth.side_effect = blke.AuthenticationError()
with patch.object( with patch.object(
hass.config_entries, "async_forward_entry_setup" hass.config_entries, "async_forward_entry_setups"
) as mock_forward, patch.object( ) as mock_forward, patch.object(
hass.config_entries.flow, "async_init" hass.config_entries.flow, "async_init"
) as mock_init: ) as mock_init:
@ -70,7 +70,7 @@ async def test_device_setup_network_timeout(hass: HomeAssistant) -> None:
mock_api.auth.side_effect = blke.NetworkTimeoutError() mock_api.auth.side_effect = blke.NetworkTimeoutError()
with patch.object( with patch.object(
hass.config_entries, "async_forward_entry_setup" hass.config_entries, "async_forward_entry_setups"
) as mock_forward, patch.object( ) as mock_forward, patch.object(
hass.config_entries.flow, "async_init" hass.config_entries.flow, "async_init"
) as mock_init: ) as mock_init:
@ -89,7 +89,7 @@ async def test_device_setup_os_error(hass: HomeAssistant) -> None:
mock_api.auth.side_effect = OSError() mock_api.auth.side_effect = OSError()
with patch.object( with patch.object(
hass.config_entries, "async_forward_entry_setup" hass.config_entries, "async_forward_entry_setups"
) as mock_forward, patch.object( ) as mock_forward, patch.object(
hass.config_entries.flow, "async_init" hass.config_entries.flow, "async_init"
) as mock_init: ) as mock_init:
@ -108,7 +108,7 @@ async def test_device_setup_broadlink_exception(hass: HomeAssistant) -> None:
mock_api.auth.side_effect = blke.BroadlinkException() mock_api.auth.side_effect = blke.BroadlinkException()
with patch.object( with patch.object(
hass.config_entries, "async_forward_entry_setup" hass.config_entries, "async_forward_entry_setups"
) as mock_forward, patch.object( ) as mock_forward, patch.object(
hass.config_entries.flow, "async_init" hass.config_entries.flow, "async_init"
) as mock_init: ) as mock_init:
@ -127,7 +127,7 @@ async def test_device_setup_update_network_timeout(hass: HomeAssistant) -> None:
mock_api.check_sensors.side_effect = blke.NetworkTimeoutError() mock_api.check_sensors.side_effect = blke.NetworkTimeoutError()
with patch.object( with patch.object(
hass.config_entries, "async_forward_entry_setup" hass.config_entries, "async_forward_entry_setups"
) as mock_forward, patch.object( ) as mock_forward, patch.object(
hass.config_entries.flow, "async_init" hass.config_entries.flow, "async_init"
) as mock_init: ) as mock_init:
@ -150,7 +150,7 @@ async def test_device_setup_update_authorization_error(hass: HomeAssistant) -> N
) )
with patch.object( with patch.object(
hass.config_entries, "async_forward_entry_setup" hass.config_entries, "async_forward_entry_setups"
) as mock_forward, patch.object( ) as mock_forward, patch.object(
hass.config_entries.flow, "async_init" hass.config_entries.flow, "async_init"
) as mock_init: ) as mock_init:
@ -160,9 +160,9 @@ async def test_device_setup_update_authorization_error(hass: HomeAssistant) -> N
assert mock_setup.api.auth.call_count == 2 assert mock_setup.api.auth.call_count == 2
assert mock_setup.api.check_sensors.call_count == 2 assert mock_setup.api.check_sensors.call_count == 2
forward_entries = {c[1][1] for c in mock_forward.mock_calls} forward_entries = set(mock_forward.mock_calls[0][1][1])
domains = get_domains(mock_api.type) domains = get_domains(mock_api.type)
assert mock_forward.call_count == len(domains) assert mock_forward.call_count == 1
assert forward_entries == domains assert forward_entries == domains
assert mock_init.call_count == 0 assert mock_init.call_count == 0
@ -175,7 +175,7 @@ async def test_device_setup_update_authentication_error(hass: HomeAssistant) ->
mock_api.auth.side_effect = (None, blke.AuthenticationError()) mock_api.auth.side_effect = (None, blke.AuthenticationError())
with patch.object( with patch.object(
hass.config_entries, "async_forward_entry_setup" hass.config_entries, "async_forward_entry_setups"
) as mock_forward, patch.object( ) as mock_forward, patch.object(
hass.config_entries.flow, "async_init" hass.config_entries.flow, "async_init"
) as mock_init: ) as mock_init:
@ -200,7 +200,7 @@ async def test_device_setup_update_broadlink_exception(hass: HomeAssistant) -> N
mock_api.check_sensors.side_effect = blke.BroadlinkException() mock_api.check_sensors.side_effect = blke.BroadlinkException()
with patch.object( with patch.object(
hass.config_entries, "async_forward_entry_setup" hass.config_entries, "async_forward_entry_setups"
) as mock_forward, patch.object( ) as mock_forward, patch.object(
hass.config_entries.flow, "async_init" hass.config_entries.flow, "async_init"
) as mock_init: ) as mock_init:
@ -221,13 +221,15 @@ async def test_device_setup_get_fwversion_broadlink_exception(
mock_api = device.get_mock_api() mock_api = device.get_mock_api()
mock_api.get_fwversion.side_effect = blke.BroadlinkException() mock_api.get_fwversion.side_effect = blke.BroadlinkException()
with patch.object(hass.config_entries, "async_forward_entry_setup") as mock_forward: with patch.object(
hass.config_entries, "async_forward_entry_setups"
) as mock_forward:
mock_setup = await device.setup_entry(hass, mock_api=mock_api) mock_setup = await device.setup_entry(hass, mock_api=mock_api)
assert mock_setup.entry.state is ConfigEntryState.LOADED assert mock_setup.entry.state is ConfigEntryState.LOADED
forward_entries = {c[1][1] for c in mock_forward.mock_calls} forward_entries = set(mock_forward.mock_calls[0][1][1])
domains = get_domains(mock_setup.api.type) domains = get_domains(mock_setup.api.type)
assert mock_forward.call_count == len(domains) assert mock_forward.call_count == 1
assert forward_entries == domains assert forward_entries == domains
@ -237,13 +239,15 @@ async def test_device_setup_get_fwversion_os_error(hass: HomeAssistant) -> None:
mock_api = device.get_mock_api() mock_api = device.get_mock_api()
mock_api.get_fwversion.side_effect = OSError() mock_api.get_fwversion.side_effect = OSError()
with patch.object(hass.config_entries, "async_forward_entry_setup") as mock_forward: with patch.object(
hass.config_entries, "async_forward_entry_setups"
) as mock_forward:
mock_setup = await device.setup_entry(hass, mock_api=mock_api) mock_setup = await device.setup_entry(hass, mock_api=mock_api)
assert mock_setup.entry.state is ConfigEntryState.LOADED assert mock_setup.entry.state is ConfigEntryState.LOADED
forward_entries = {c[1][1] for c in mock_forward.mock_calls} forward_entries = set(mock_forward.mock_calls[0][1][1])
domains = get_domains(mock_setup.api.type) domains = get_domains(mock_setup.api.type)
assert mock_forward.call_count == len(domains) assert mock_forward.call_count == 1
assert forward_entries == domains assert forward_entries == domains
@ -281,7 +285,7 @@ async def test_device_unload_works(hass: HomeAssistant) -> None:
"""Test we unload the device.""" """Test we unload the device."""
device = get_device("Office") device = get_device("Office")
with patch.object(hass.config_entries, "async_forward_entry_setup"): with patch.object(hass.config_entries, "async_forward_entry_setups"):
mock_setup = await device.setup_entry(hass) mock_setup = await device.setup_entry(hass)
with patch.object( with patch.object(
@ -302,7 +306,7 @@ async def test_device_unload_authentication_error(hass: HomeAssistant) -> None:
mock_api = device.get_mock_api() mock_api = device.get_mock_api()
mock_api.auth.side_effect = blke.AuthenticationError() mock_api.auth.side_effect = blke.AuthenticationError()
with patch.object(hass.config_entries, "async_forward_entry_setup"), patch.object( with patch.object(hass.config_entries, "async_forward_entry_setups"), patch.object(
hass.config_entries.flow, "async_init" hass.config_entries.flow, "async_init"
): ):
mock_setup = await device.setup_entry(hass, mock_api=mock_api) mock_setup = await device.setup_entry(hass, mock_api=mock_api)
@ -322,7 +326,7 @@ async def test_device_unload_update_failed(hass: HomeAssistant) -> None:
mock_api = device.get_mock_api() mock_api = device.get_mock_api()
mock_api.check_sensors.side_effect = blke.NetworkTimeoutError() mock_api.check_sensors.side_effect = blke.NetworkTimeoutError()
with patch.object(hass.config_entries, "async_forward_entry_setup"): with patch.object(hass.config_entries, "async_forward_entry_setups"):
mock_setup = await device.setup_entry(hass, mock_api=mock_api) mock_setup = await device.setup_entry(hass, mock_api=mock_api)
with patch.object( with patch.object(

View File

@ -142,7 +142,7 @@ async def test_gateway_setup(
) -> None: ) -> None:
"""Successful setup.""" """Successful setup."""
with patch( with patch(
"homeassistant.config_entries.ConfigEntries.async_forward_entry_setup", "homeassistant.config_entries.ConfigEntries.async_forward_entry_setups",
return_value=True, return_value=True,
) as forward_entry_setup: ) as forward_entry_setup:
config_entry = await setup_deconz_integration(hass, aioclient_mock) config_entry = await setup_deconz_integration(hass, aioclient_mock)
@ -158,24 +158,23 @@ async def test_gateway_setup(
assert forward_entry_setup.mock_calls[0][1] == ( assert forward_entry_setup.mock_calls[0][1] == (
config_entry, config_entry,
ALARM_CONTROL_PANEL_DOMAIN, [
ALARM_CONTROL_PANEL_DOMAIN,
BINARY_SENSOR_DOMAIN,
BUTTON_DOMAIN,
CLIMATE_DOMAIN,
COVER_DOMAIN,
FAN_DOMAIN,
LIGHT_DOMAIN,
LOCK_DOMAIN,
NUMBER_DOMAIN,
SCENE_DOMAIN,
SELECT_DOMAIN,
SENSOR_DOMAIN,
SIREN_DOMAIN,
SWITCH_DOMAIN,
],
) )
assert forward_entry_setup.mock_calls[1][1] == (
config_entry,
BINARY_SENSOR_DOMAIN,
)
assert forward_entry_setup.mock_calls[2][1] == (config_entry, BUTTON_DOMAIN)
assert forward_entry_setup.mock_calls[3][1] == (config_entry, CLIMATE_DOMAIN)
assert forward_entry_setup.mock_calls[4][1] == (config_entry, COVER_DOMAIN)
assert forward_entry_setup.mock_calls[5][1] == (config_entry, FAN_DOMAIN)
assert forward_entry_setup.mock_calls[6][1] == (config_entry, LIGHT_DOMAIN)
assert forward_entry_setup.mock_calls[7][1] == (config_entry, LOCK_DOMAIN)
assert forward_entry_setup.mock_calls[8][1] == (config_entry, NUMBER_DOMAIN)
assert forward_entry_setup.mock_calls[9][1] == (config_entry, SCENE_DOMAIN)
assert forward_entry_setup.mock_calls[10][1] == (config_entry, SELECT_DOMAIN)
assert forward_entry_setup.mock_calls[11][1] == (config_entry, SENSOR_DOMAIN)
assert forward_entry_setup.mock_calls[12][1] == (config_entry, SIREN_DOMAIN)
assert forward_entry_setup.mock_calls[13][1] == (config_entry, SWITCH_DOMAIN)
gateway_entry = device_registry.async_get_device( gateway_entry = device_registry.async_get_device(
identifiers={(DECONZ_DOMAIN, gateway.bridgeid)} identifiers={(DECONZ_DOMAIN, gateway.bridgeid)}

View File

@ -80,7 +80,9 @@ async def test_async_setup_entry_loads_platforms(
) -> None: ) -> None:
"""Test load connects to heos, retrieves players, and loads platforms.""" """Test load connects to heos, retrieves players, and loads platforms."""
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
with patch.object(hass.config_entries, "async_forward_entry_setup") as forward_mock: with patch.object(
hass.config_entries, "async_forward_entry_setups"
) as forward_mock:
assert await async_setup_entry(hass, config_entry) assert await async_setup_entry(hass, config_entry)
# Assert platforms loaded # Assert platforms loaded
await hass.async_block_till_done() await hass.async_block_till_done()
@ -107,7 +109,9 @@ async def test_async_setup_entry_not_signed_in_loads_platforms(
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
controller.is_signed_in = False controller.is_signed_in = False
controller.signed_in_username = None controller.signed_in_username = None
with patch.object(hass.config_entries, "async_forward_entry_setup") as forward_mock: with patch.object(
hass.config_entries, "async_forward_entry_setups"
) as forward_mock:
assert await async_setup_entry(hass, config_entry) assert await async_setup_entry(hass, config_entry)
# Assert platforms loaded # Assert platforms loaded
await hass.async_block_till_done() await hass.async_block_till_done()

View File

@ -30,7 +30,7 @@ async def test_bridge_setup_v1(hass: HomeAssistant, mock_api_v1) -> None:
) )
with patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), patch.object( with patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), patch.object(
hass.config_entries, "async_forward_entry_setup" hass.config_entries, "async_forward_entry_setups"
) as mock_forward: ) as mock_forward:
hue_bridge = bridge.HueBridge(hass, config_entry) hue_bridge = bridge.HueBridge(hass, config_entry)
assert await hue_bridge.async_initialize_bridge() is True assert await hue_bridge.async_initialize_bridge() is True
@ -38,8 +38,8 @@ async def test_bridge_setup_v1(hass: HomeAssistant, mock_api_v1) -> None:
assert hue_bridge.api is mock_api_v1 assert hue_bridge.api is mock_api_v1
assert isinstance(hue_bridge.api, HueBridgeV1) assert isinstance(hue_bridge.api, HueBridgeV1)
assert hue_bridge.api_version == 1 assert hue_bridge.api_version == 1
assert len(mock_forward.mock_calls) == 3 assert len(mock_forward.mock_calls) == 1
forward_entries = {c[1][1] for c in mock_forward.mock_calls} forward_entries = set(mock_forward.mock_calls[0][1][1])
assert forward_entries == {"light", "binary_sensor", "sensor"} assert forward_entries == {"light", "binary_sensor", "sensor"}
@ -51,7 +51,7 @@ async def test_bridge_setup_v2(hass: HomeAssistant, mock_api_v2) -> None:
) )
with patch.object(bridge, "HueBridgeV2", return_value=mock_api_v2), patch.object( with patch.object(bridge, "HueBridgeV2", return_value=mock_api_v2), patch.object(
hass.config_entries, "async_forward_entry_setup" hass.config_entries, "async_forward_entry_setups"
) as mock_forward: ) as mock_forward:
hue_bridge = bridge.HueBridge(hass, config_entry) hue_bridge = bridge.HueBridge(hass, config_entry)
assert await hue_bridge.async_initialize_bridge() is True assert await hue_bridge.async_initialize_bridge() is True
@ -59,8 +59,8 @@ async def test_bridge_setup_v2(hass: HomeAssistant, mock_api_v2) -> None:
assert hue_bridge.api is mock_api_v2 assert hue_bridge.api is mock_api_v2
assert isinstance(hue_bridge.api, HueBridgeV2) assert isinstance(hue_bridge.api, HueBridgeV2)
assert hue_bridge.api_version == 2 assert hue_bridge.api_version == 2
assert len(mock_forward.mock_calls) == 6 assert len(mock_forward.mock_calls) == 1
forward_entries = {c[1][1] for c in mock_forward.mock_calls} forward_entries = set(mock_forward.mock_calls[0][1][1])
assert forward_entries == { assert forward_entries == {
"light", "light",
"binary_sensor", "binary_sensor",
@ -115,7 +115,7 @@ async def test_reset_unloads_entry_if_setup(hass: HomeAssistant, mock_api_v1) ->
) )
with patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), patch.object( with patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), patch.object(
hass.config_entries, "async_forward_entry_setup" hass.config_entries, "async_forward_entry_setups"
) as mock_forward: ) as mock_forward:
hue_bridge = bridge.HueBridge(hass, config_entry) hue_bridge = bridge.HueBridge(hass, config_entry)
assert await hue_bridge.async_initialize_bridge() is True assert await hue_bridge.async_initialize_bridge() is True
@ -123,7 +123,7 @@ async def test_reset_unloads_entry_if_setup(hass: HomeAssistant, mock_api_v1) ->
await asyncio.sleep(0) await asyncio.sleep(0)
assert len(hass.services.async_services()) == 0 assert len(hass.services.async_services()) == 0
assert len(mock_forward.mock_calls) == 3 assert len(mock_forward.mock_calls) == 1
with patch.object( with patch.object(
hass.config_entries, "async_forward_entry_unload", return_value=True hass.config_entries, "async_forward_entry_unload", return_value=True

View File

@ -63,7 +63,7 @@ async def test_hue_activate_scene(hass: HomeAssistant, mock_api_v1) -> None:
mock_api_v1.mock_scene_responses.append(SCENE_RESPONSE) mock_api_v1.mock_scene_responses.append(SCENE_RESPONSE)
with patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), patch.object( with patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), patch.object(
hass.config_entries, "async_forward_entry_setup" hass.config_entries, "async_forward_entry_setups"
): ):
hue_bridge = bridge.HueBridge(hass, config_entry) hue_bridge = bridge.HueBridge(hass, config_entry)
assert await hue_bridge.async_initialize_bridge() is True assert await hue_bridge.async_initialize_bridge() is True
@ -100,7 +100,7 @@ async def test_hue_activate_scene_transition(hass: HomeAssistant, mock_api_v1) -
mock_api_v1.mock_scene_responses.append(SCENE_RESPONSE) mock_api_v1.mock_scene_responses.append(SCENE_RESPONSE)
with patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), patch.object( with patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), patch.object(
hass.config_entries, "async_forward_entry_setup" hass.config_entries, "async_forward_entry_setups"
): ):
hue_bridge = bridge.HueBridge(hass, config_entry) hue_bridge = bridge.HueBridge(hass, config_entry)
assert await hue_bridge.async_initialize_bridge() is True assert await hue_bridge.async_initialize_bridge() is True
@ -139,7 +139,7 @@ async def test_hue_activate_scene_group_not_found(
mock_api_v1.mock_scene_responses.append(SCENE_RESPONSE) mock_api_v1.mock_scene_responses.append(SCENE_RESPONSE)
with patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), patch.object( with patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), patch.object(
hass.config_entries, "async_forward_entry_setup" hass.config_entries, "async_forward_entry_setups"
): ):
hue_bridge = bridge.HueBridge(hass, config_entry) hue_bridge = bridge.HueBridge(hass, config_entry)
assert await hue_bridge.async_initialize_bridge() is True assert await hue_bridge.async_initialize_bridge() is True
@ -173,7 +173,7 @@ async def test_hue_activate_scene_scene_not_found(
mock_api_v1.mock_scene_responses.append({}) mock_api_v1.mock_scene_responses.append({})
with patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), patch.object( with patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), patch.object(
hass.config_entries, "async_forward_entry_setup" hass.config_entries, "async_forward_entry_setups"
): ):
hue_bridge = bridge.HueBridge(hass, config_entry) hue_bridge = bridge.HueBridge(hass, config_entry)
assert await hue_bridge.async_initialize_bridge() is True assert await hue_bridge.async_initialize_bridge() is True

View File

@ -76,8 +76,6 @@ async def test_minio_services(
await hass.async_start() await hass.async_start()
await hass.async_block_till_done() await hass.async_block_till_done()
assert "Setup of domain minio took" in caplog.text
# Call services # Call services
await hass.services.async_call( await hass.services.async_call(
DOMAIN, DOMAIN,
@ -141,8 +139,6 @@ async def test_minio_listen(
await hass.async_start() await hass.async_start()
await hass.async_block_till_done() await hass.async_block_till_done()
assert "Setup of domain minio took" in caplog.text
while not events: while not events:
await asyncio.sleep(0) await asyncio.sleep(0)

View File

@ -126,7 +126,7 @@ async def test_unload(hass: HomeAssistant) -> None:
) )
with patch( with patch(
"homeassistant.config_entries.ConfigEntries.async_forward_entry_setup" "homeassistant.config_entries.ConfigEntries.async_forward_entry_setups"
) as mock_forward: ) as mock_forward:
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}, data={} DOMAIN, context={"source": config_entries.SOURCE_USER}, data={}
@ -135,8 +135,7 @@ async def test_unload(hass: HomeAssistant) -> None:
assert len(mock_forward.mock_calls) == 1 assert len(mock_forward.mock_calls) == 1
entry = result["result"] entry = result["result"]
assert mock_forward.mock_calls[0][1][0] is entry mock_forward.assert_called_once_with(entry, ["device_tracker"])
assert mock_forward.mock_calls[0][1][1] == "device_tracker"
assert entry.data["webhook_id"] in hass.data["webhook"] assert entry.data["webhook_id"] in hass.data["webhook"]
with patch( with patch(
@ -146,8 +145,7 @@ async def test_unload(hass: HomeAssistant) -> None:
assert await hass.config_entries.async_unload(entry.entry_id) assert await hass.config_entries.async_unload(entry.entry_id)
assert len(mock_unload.mock_calls) == 1 assert len(mock_unload.mock_calls) == 1
assert mock_forward.mock_calls[0][1][0] is entry mock_forward.assert_called_once_with(entry, ["device_tracker"])
assert mock_forward.mock_calls[0][1][1] == "device_tracker"
assert entry.data["webhook_id"] not in hass.data["webhook"] assert entry.data["webhook_id"] not in hass.data["webhook"]

View File

@ -179,11 +179,13 @@ async def test_scenes_unauthorized_loads_platforms(
] ]
smartthings_mock.subscriptions.return_value = subscriptions smartthings_mock.subscriptions.return_value = subscriptions
with patch.object(hass.config_entries, "async_forward_entry_setup") as forward_mock: with patch.object(
hass.config_entries, "async_forward_entry_setups"
) as forward_mock:
assert await hass.config_entries.async_setup(config_entry.entry_id) assert await hass.config_entries.async_setup(config_entry.entry_id)
# Assert platforms loaded # Assert platforms loaded
await hass.async_block_till_done() await hass.async_block_till_done()
assert forward_mock.call_count == len(PLATFORMS) forward_mock.assert_called_once_with(config_entry, PLATFORMS)
async def test_config_entry_loads_platforms( async def test_config_entry_loads_platforms(
@ -211,11 +213,13 @@ async def test_config_entry_loads_platforms(
] ]
smartthings_mock.subscriptions.return_value = subscriptions smartthings_mock.subscriptions.return_value = subscriptions
with patch.object(hass.config_entries, "async_forward_entry_setup") as forward_mock: with patch.object(
hass.config_entries, "async_forward_entry_setups"
) as forward_mock:
assert await hass.config_entries.async_setup(config_entry.entry_id) assert await hass.config_entries.async_setup(config_entry.entry_id)
# Assert platforms loaded # Assert platforms loaded
await hass.async_block_till_done() await hass.async_block_till_done()
assert forward_mock.call_count == len(PLATFORMS) forward_mock.assert_called_once_with(config_entry, PLATFORMS)
async def test_config_entry_loads_unconnected_cloud( async def test_config_entry_loads_unconnected_cloud(
@ -243,10 +247,12 @@ async def test_config_entry_loads_unconnected_cloud(
subscription_factory(capability) for capability in device.capabilities subscription_factory(capability) for capability in device.capabilities
] ]
smartthings_mock.subscriptions.return_value = subscriptions smartthings_mock.subscriptions.return_value = subscriptions
with patch.object(hass.config_entries, "async_forward_entry_setup") as forward_mock: with patch.object(
hass.config_entries, "async_forward_entry_setups"
) as forward_mock:
assert await hass.config_entries.async_setup(config_entry.entry_id) assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
assert forward_mock.call_count == len(PLATFORMS) forward_mock.assert_called_once_with(config_entry, PLATFORMS)
async def test_unload_entry(hass: HomeAssistant, config_entry) -> None: async def test_unload_entry(hass: HomeAssistant, config_entry) -> None:

View File

@ -148,6 +148,7 @@ async def test_service_without_cache_config(
with assert_setup_component(1, DOMAIN): with assert_setup_component(1, DOMAIN):
assert await async_setup_component(hass, DOMAIN, config) assert await async_setup_component(hass, DOMAIN, config)
await hass.async_block_till_done()
await hass.services.async_call( await hass.services.async_call(
DOMAIN, DOMAIN,

View File

@ -24,11 +24,11 @@ from homeassistant.components.unifi.const import (
DEFAULT_TRACK_DEVICES, DEFAULT_TRACK_DEVICES,
DEFAULT_TRACK_WIRED_CLIENTS, DEFAULT_TRACK_WIRED_CLIENTS,
DOMAIN as UNIFI_DOMAIN, DOMAIN as UNIFI_DOMAIN,
PLATFORMS,
UNIFI_WIRELESS_CLIENTS, UNIFI_WIRELESS_CLIENTS,
) )
from homeassistant.components.unifi.errors import AuthenticationRequired, CannotConnect from homeassistant.components.unifi.errors import AuthenticationRequired, CannotConnect
from homeassistant.components.unifi.hub import get_unifi_api from homeassistant.components.unifi.hub import get_unifi_api
from homeassistant.components.update import DOMAIN as UPDATE_DOMAIN
from homeassistant.const import ( from homeassistant.const import (
CONF_HOST, CONF_HOST,
CONF_PASSWORD, CONF_PASSWORD,
@ -248,7 +248,7 @@ async def test_hub_setup(
) -> None: ) -> None:
"""Successful setup.""" """Successful setup."""
with patch( with patch(
"homeassistant.config_entries.ConfigEntries.async_forward_entry_setup", "homeassistant.config_entries.ConfigEntries.async_forward_entry_setups",
return_value=True, return_value=True,
) as forward_entry_setup: ) as forward_entry_setup:
config_entry = await setup_unifi_integration( config_entry = await setup_unifi_integration(
@ -257,12 +257,18 @@ async def test_hub_setup(
hub = hass.data[UNIFI_DOMAIN][config_entry.entry_id] hub = hass.data[UNIFI_DOMAIN][config_entry.entry_id]
entry = hub.config.entry entry = hub.config.entry
assert len(forward_entry_setup.mock_calls) == len(PLATFORMS) assert len(forward_entry_setup.mock_calls) == 1
assert forward_entry_setup.mock_calls[0][1] == (entry, BUTTON_DOMAIN) assert forward_entry_setup.mock_calls[0][1] == (
assert forward_entry_setup.mock_calls[1][1] == (entry, TRACKER_DOMAIN) entry,
assert forward_entry_setup.mock_calls[2][1] == (entry, IMAGE_DOMAIN) [
assert forward_entry_setup.mock_calls[3][1] == (entry, SENSOR_DOMAIN) BUTTON_DOMAIN,
assert forward_entry_setup.mock_calls[4][1] == (entry, SWITCH_DOMAIN) TRACKER_DOMAIN,
IMAGE_DOMAIN,
SENSOR_DOMAIN,
SWITCH_DOMAIN,
UPDATE_DOMAIN,
],
)
assert hub.config.host == ENTRY_CONFIG[CONF_HOST] assert hub.config.host == ENTRY_CONFIG[CONF_HOST]
assert hub.is_admin == (SITE[0]["role"] == "admin") assert hub.is_admin == (SITE[0]["role"] == "admin")

View File

@ -30,15 +30,12 @@ async def test_async_setup_entry__not_login(
with patch.object( with patch.object(
hass.config_entries, "async_forward_entry_setups" hass.config_entries, "async_forward_entry_setups"
) as setups_mock, patch.object( ) as setups_mock, patch(
hass.config_entries, "async_forward_entry_setup"
) as setup_mock, patch(
"homeassistant.components.vesync.async_process_devices" "homeassistant.components.vesync.async_process_devices"
) as process_mock: ) as process_mock:
assert not await async_setup_entry(hass, config_entry) assert not await async_setup_entry(hass, config_entry)
await hass.async_block_till_done() await hass.async_block_till_done()
assert setups_mock.call_count == 0 assert setups_mock.call_count == 0
assert setup_mock.call_count == 0
assert process_mock.call_count == 0 assert process_mock.call_count == 0
assert manager.login.call_count == 1 assert manager.login.call_count == 1
@ -50,18 +47,13 @@ async def test_async_setup_entry__no_devices(
hass: HomeAssistant, config_entry: ConfigEntry, manager: VeSync hass: HomeAssistant, config_entry: ConfigEntry, manager: VeSync
) -> None: ) -> None:
"""Test setup connects to vesync and creates empty config when no devices.""" """Test setup connects to vesync and creates empty config when no devices."""
with patch.object( with patch.object(hass.config_entries, "async_forward_entry_setups") as setups_mock:
hass.config_entries, "async_forward_entry_setups"
) as setups_mock, patch.object(
hass.config_entries, "async_forward_entry_setup"
) as setup_mock:
assert await async_setup_entry(hass, config_entry) assert await async_setup_entry(hass, config_entry)
# Assert platforms loaded # Assert platforms loaded
await hass.async_block_till_done() await hass.async_block_till_done()
assert setups_mock.call_count == 1 assert setups_mock.call_count == 1
assert setups_mock.call_args.args[0] == config_entry assert setups_mock.call_args.args[0] == config_entry
assert setups_mock.call_args.args[1] == [] assert setups_mock.call_args.args[1] == []
assert setup_mock.call_count == 0
assert manager.login.call_count == 1 assert manager.login.call_count == 1
assert hass.data[DOMAIN][VS_MANAGER] == manager assert hass.data[DOMAIN][VS_MANAGER] == manager
@ -81,18 +73,13 @@ async def test_async_setup_entry__loads_fans(
"fans": fans, "fans": fans,
} }
with patch.object( with patch.object(hass.config_entries, "async_forward_entry_setups") as setups_mock:
hass.config_entries, "async_forward_entry_setups"
) as setups_mock, patch.object(
hass.config_entries, "async_forward_entry_setup"
) as setup_mock:
assert await async_setup_entry(hass, config_entry) assert await async_setup_entry(hass, config_entry)
# Assert platforms loaded # Assert platforms loaded
await hass.async_block_till_done() await hass.async_block_till_done()
assert setups_mock.call_count == 1 assert setups_mock.call_count == 1
assert setups_mock.call_args.args[0] == config_entry assert setups_mock.call_args.args[0] == config_entry
assert setups_mock.call_args.args[1] == [Platform.FAN, Platform.SENSOR] assert setups_mock.call_args.args[1] == [Platform.FAN, Platform.SENSOR]
assert setup_mock.call_count == 0
assert manager.login.call_count == 1 assert manager.login.call_count == 1
assert hass.data[DOMAIN][VS_MANAGER] == manager assert hass.data[DOMAIN][VS_MANAGER] == manager
assert not hass.data[DOMAIN][VS_SWITCHES] assert not hass.data[DOMAIN][VS_SWITCHES]

View File

@ -859,7 +859,7 @@ async def test_forward_entry_sets_up_component(hass: HomeAssistant) -> None:
entry = MockConfigEntry(domain="original") entry = MockConfigEntry(domain="original")
mock_original_setup_entry = AsyncMock(return_value=True) mock_original_setup_entry = AsyncMock(return_value=True)
mock_integration( integration = mock_integration(
hass, MockModule("original", async_setup_entry=mock_original_setup_entry) hass, MockModule("original", async_setup_entry=mock_original_setup_entry)
) )
@ -868,7 +868,10 @@ async def test_forward_entry_sets_up_component(hass: HomeAssistant) -> None:
hass, MockModule("forwarded", async_setup_entry=mock_forwarded_setup_entry) hass, MockModule("forwarded", async_setup_entry=mock_forwarded_setup_entry)
) )
await hass.config_entries.async_forward_entry_setup(entry, "forwarded") with patch.object(integration, "async_get_platform") as mock_async_get_platform:
await hass.config_entries.async_forward_entry_setup(entry, "forwarded")
mock_async_get_platform.assert_called_once_with("forwarded")
assert len(mock_original_setup_entry.mock_calls) == 0 assert len(mock_original_setup_entry.mock_calls) == 0
assert len(mock_forwarded_setup_entry.mock_calls) == 1 assert len(mock_forwarded_setup_entry.mock_calls) == 1

View File

@ -4,6 +4,7 @@ import asyncio
import threading import threading
from unittest.mock import ANY, AsyncMock, Mock, patch from unittest.mock import ANY, AsyncMock, Mock, patch
from freezegun.api import FrozenDateTimeFactory
import pytest import pytest
import voluptuous as vol import voluptuous as vol
@ -16,6 +17,10 @@ from homeassistant.helpers.config_validation import (
PLATFORM_SCHEMA, PLATFORM_SCHEMA,
PLATFORM_SCHEMA_BASE, PLATFORM_SCHEMA_BASE,
) )
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect,
async_dispatcher_send,
)
from .common import ( from .common import (
MockConfigEntry, MockConfigEntry,
@ -739,7 +744,9 @@ async def test_async_start_setup_running(hass: HomeAssistant) -> None:
assert not setup_started assert not setup_started
async def test_async_start_setup_config_entry(hass: HomeAssistant) -> None: async def test_async_start_setup_config_entry(
hass: HomeAssistant, freezer: FrozenDateTimeFactory
) -> None:
"""Test setup started keeps track of setup times with a config entry.""" """Test setup started keeps track of setup times with a config entry."""
hass.set_state(CoreState.not_running) hass.set_state(CoreState.not_running)
setup_started: dict[tuple[str, str | None], float] setup_started: dict[tuple[str, str | None], float]
@ -778,6 +785,7 @@ async def test_async_start_setup_config_entry(hass: HomeAssistant) -> None:
phase=setup.SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP, phase=setup.SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP,
): ):
assert isinstance(setup_started[("august", "entry_id")], float) assert isinstance(setup_started[("august", "entry_id")], float)
# Platforms outside of CONFIG_ENTRY_SETUP should be tracked # Platforms outside of CONFIG_ENTRY_SETUP should be tracked
# This simulates a late platform forward # This simulates a late platform forward
assert setup_time["august"] == { assert setup_time["august"] == {
@ -788,6 +796,38 @@ async def test_async_start_setup_config_entry(hass: HomeAssistant) -> None:
}, },
} }
shorter_time = setup_time["august"]["entry_id"][
setup.SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP
]
# Setup another platform, but make it take longer
with setup.async_start_setup(
hass,
integration="august",
group="entry_id",
phase=setup.SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP,
):
freezer.tick(10)
assert isinstance(setup_started[("august", "entry_id")], float)
longer_time = setup_time["august"]["entry_id"][
setup.SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP
]
assert longer_time > shorter_time
# Setup another platform, but make it take shorter
with setup.async_start_setup(
hass,
integration="august",
group="entry_id",
phase=setup.SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP,
):
assert isinstance(setup_started[("august", "entry_id")], float)
# Ensure we keep the longest time
assert (
setup_time["august"]["entry_id"][setup.SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP]
== longer_time
)
with setup.async_start_setup( with setup.async_start_setup(
hass, hass,
integration="august", integration="august",
@ -815,6 +855,106 @@ async def test_async_start_setup_config_entry(hass: HomeAssistant) -> None:
} }
async def test_async_start_setup_config_entry_late_platform(
hass: HomeAssistant, freezer: FrozenDateTimeFactory
) -> None:
"""Test setup started tracks config entry time with a late platform load."""
hass.set_state(CoreState.not_running)
setup_started: dict[tuple[str, str | None], float]
setup_started = hass.data.setdefault(setup.DATA_SETUP_STARTED, {})
setup_time = setup._setup_times(hass)
with setup.async_start_setup(
hass, integration="august", phase=setup.SetupPhases.SETUP
):
freezer.tick(10)
assert isinstance(setup_started[("august", None)], float)
with setup.async_start_setup(
hass,
integration="august",
group="entry_id",
phase=setup.SetupPhases.CONFIG_ENTRY_SETUP,
):
assert isinstance(setup_started[("august", "entry_id")], float)
@callback
def async_late_platform_load():
with setup.async_pause_setup(hass, setup.SetupPhases.WAIT_IMPORT_PLATFORMS):
freezer.tick(100)
with setup.async_start_setup(
hass,
integration="august",
group="entry_id",
phase=setup.SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP,
):
freezer.tick(20)
assert isinstance(setup_started[("august", "entry_id")], float)
disconnect = async_dispatcher_connect(
hass, "late_platform_load_test", async_late_platform_load
)
# Dispatch a late platform load
async_dispatcher_send(hass, "late_platform_load_test")
disconnect()
# CONFIG_ENTRY_PLATFORM_SETUP is late dispatched, so it should be tracked
# but any waiting time should not be because it's blocking the setup
assert setup_time["august"] == {
None: {setup.SetupPhases.SETUP: 10.0},
"entry_id": {
setup.SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP: 20.0,
setup.SetupPhases.CONFIG_ENTRY_SETUP: 0.0,
},
}
async def test_async_start_setup_config_entry_platform_wait(
hass: HomeAssistant, freezer: FrozenDateTimeFactory
) -> None:
"""Test setup started tracks wait time when a platform loads inside of config entry setup."""
hass.set_state(CoreState.not_running)
setup_started: dict[tuple[str, str | None], float]
setup_started = hass.data.setdefault(setup.DATA_SETUP_STARTED, {})
setup_time = setup._setup_times(hass)
with setup.async_start_setup(
hass, integration="august", phase=setup.SetupPhases.SETUP
):
freezer.tick(10)
assert isinstance(setup_started[("august", None)], float)
with setup.async_start_setup(
hass,
integration="august",
group="entry_id",
phase=setup.SetupPhases.CONFIG_ENTRY_SETUP,
):
assert isinstance(setup_started[("august", "entry_id")], float)
with setup.async_pause_setup(hass, setup.SetupPhases.WAIT_IMPORT_PLATFORMS):
freezer.tick(100)
with setup.async_start_setup(
hass,
integration="august",
group="entry_id",
phase=setup.SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP,
):
freezer.tick(20)
assert isinstance(setup_started[("august", "entry_id")], float)
# CONFIG_ENTRY_PLATFORM_SETUP is run inside of CONFIG_ENTRY_SETUP, so it should not
# be tracked, but any wait time should still be tracked because its blocking the setup
assert setup_time["august"] == {
None: {setup.SetupPhases.SETUP: 10.0},
"entry_id": {
setup.SetupPhases.WAIT_IMPORT_PLATFORMS: -100.0,
setup.SetupPhases.CONFIG_ENTRY_SETUP: 120.0,
},
}
async def test_async_start_setup_top_level_yaml(hass: HomeAssistant) -> None: async def test_async_start_setup_top_level_yaml(hass: HomeAssistant) -> None:
"""Test setup started context manager keeps track of setup times with modern yaml.""" """Test setup started context manager keeps track of setup times with modern yaml."""
hass.set_state(CoreState.not_running) hass.set_state(CoreState.not_running)