Don't overwrite setup state in async_set_domains_to_be_loaded (#137547)

pull/132882/merge
Erik Montnemery 2025-02-06 15:18:37 +01:00 committed by GitHub
parent 03d709f162
commit 7822e11894
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 78 additions and 6 deletions

View File

@ -132,7 +132,13 @@ def async_set_domains_to_be_loaded(hass: core.HomeAssistant, domains: set[str])
- Keep track of domains which will load but have not yet finished loading
"""
setup_done_futures = hass.data.setdefault(DATA_SETUP_DONE, {})
setup_done_futures.update({domain: hass.loop.create_future() for domain in domains})
setup_futures = hass.data.setdefault(DATA_SETUP, {})
old_domains = set(setup_futures) | set(setup_done_futures) | hass.config.components
if overlap := old_domains & domains:
_LOGGER.debug("Domains to be loaded %s already loaded or pending", overlap)
setup_done_futures.update(
{domain: hass.loop.create_future() for domain in domains - old_domains}
)
def setup_component(hass: core.HomeAssistant, domain: str, config: ConfigType) -> bool:

View File

@ -363,20 +363,24 @@ async def test_component_failing_setup(hass: HomeAssistant) -> None:
async def test_component_exception_setup(hass: HomeAssistant) -> None:
"""Test component that raises exception during setup."""
setup.async_set_domains_to_be_loaded(hass, {"comp"})
domain = "comp"
setup.async_set_domains_to_be_loaded(hass, {domain})
def exception_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Raise exception."""
raise Exception("fail!") # noqa: TRY002
mock_integration(hass, MockModule("comp", setup=exception_setup))
mock_integration(hass, MockModule(domain, setup=exception_setup))
assert not await setup.async_setup_component(hass, "comp", {})
assert "comp" not in hass.config.components
assert not await setup.async_setup_component(hass, domain, {})
assert domain in hass.data[setup.DATA_SETUP]
assert domain not in hass.data[setup.DATA_SETUP_DONE]
assert domain not in hass.config.components
async def test_component_base_exception_setup(hass: HomeAssistant) -> None:
"""Test component that raises exception during setup."""
domain = "comp"
setup.async_set_domains_to_be_loaded(hass, {"comp"})
def exception_setup(hass: HomeAssistant, config: ConfigType) -> bool:
@ -389,7 +393,69 @@ async def test_component_base_exception_setup(hass: HomeAssistant) -> None:
await setup.async_setup_component(hass, "comp", {})
assert str(exc_info.value) == "fail!"
assert "comp" not in hass.config.components
assert domain in hass.data[setup.DATA_SETUP]
assert domain not in hass.data[setup.DATA_SETUP_DONE]
assert domain not in hass.config.components
async def test_set_domains_to_be_loaded(hass: HomeAssistant) -> None:
"""Test async_set_domains_to_be_loaded."""
domain_good = "comp_good"
domain_bad = "comp_bad"
domain_base_exception = "comp_base_exception"
domain_exception = "comp_exception"
domains = {domain_good, domain_bad, domain_exception, domain_base_exception}
setup.async_set_domains_to_be_loaded(hass, domains)
assert set(hass.data[setup.DATA_SETUP_DONE]) == domains
setup_done = dict(hass.data[setup.DATA_SETUP_DONE])
# Calling async_set_domains_to_be_loaded again should not create new futures
setup.async_set_domains_to_be_loaded(hass, domains)
assert setup_done == hass.data[setup.DATA_SETUP_DONE]
def good_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Success."""
return True
def bad_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Fail."""
return False
def base_exception_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Raise exception."""
raise BaseException("fail!") # noqa: TRY002
def exception_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Raise exception."""
raise Exception("fail!") # noqa: TRY002
mock_integration(hass, MockModule(domain_good, setup=good_setup))
mock_integration(hass, MockModule(domain_bad, setup=bad_setup))
mock_integration(
hass, MockModule(domain_base_exception, setup=base_exception_setup)
)
mock_integration(hass, MockModule(domain_exception, setup=exception_setup))
# Set up the four components
assert await setup.async_setup_component(hass, domain_good, {})
assert not await setup.async_setup_component(hass, domain_bad, {})
assert not await setup.async_setup_component(hass, domain_exception, {})
with pytest.raises(BaseException, match="fail!"):
await setup.async_setup_component(hass, domain_base_exception, {})
# Check the result of the setup
assert not hass.data[setup.DATA_SETUP_DONE]
assert set(hass.data[setup.DATA_SETUP]) == {
domain_bad,
domain_exception,
domain_base_exception,
}
assert set(hass.config.components) == {domain_good}
# Calling async_set_domains_to_be_loaded again should not create any new futures
setup.async_set_domains_to_be_loaded(hass, domains)
assert not hass.data[setup.DATA_SETUP_DONE]
async def test_component_setup_with_validation_and_dependency(