Protect hass data keys in setup.py (#142589)

This commit is contained in:
Erik Montnemery
2025-04-09 15:27:52 +02:00
committed by GitHub
parent 075a0ad780
commit 170e6bdcab
3 changed files with 110 additions and 67 deletions

View File

@ -57,21 +57,21 @@ async def test_validate_component_config(hass: HomeAssistant) -> None:
with assert_setup_component(0):
assert not await setup.async_setup_component(hass, "comp_conf", {})
hass.data.pop(setup.DATA_SETUP)
hass.data.pop(setup._DATA_SETUP)
with assert_setup_component(0):
assert not await setup.async_setup_component(
hass, "comp_conf", {"comp_conf": None}
)
hass.data.pop(setup.DATA_SETUP)
hass.data.pop(setup._DATA_SETUP)
with assert_setup_component(0):
assert not await setup.async_setup_component(
hass, "comp_conf", {"comp_conf": {}}
)
hass.data.pop(setup.DATA_SETUP)
hass.data.pop(setup._DATA_SETUP)
with assert_setup_component(0):
assert not await setup.async_setup_component(
@ -80,7 +80,7 @@ async def test_validate_component_config(hass: HomeAssistant) -> None:
{"comp_conf": {"hello": "world", "invalid": "extra"}},
)
hass.data.pop(setup.DATA_SETUP)
hass.data.pop(setup._DATA_SETUP)
with assert_setup_component(1):
assert await setup.async_setup_component(
@ -111,7 +111,7 @@ async def test_validate_platform_config(
{"platform_conf": {"platform": "not_existing", "hello": "world"}},
)
hass.data.pop(setup.DATA_SETUP)
hass.data.pop(setup._DATA_SETUP)
hass.config.components.remove("platform_conf")
with assert_setup_component(1):
@ -121,7 +121,7 @@ async def test_validate_platform_config(
{"platform_conf": {"platform": "whatever", "hello": "world"}},
)
hass.data.pop(setup.DATA_SETUP)
hass.data.pop(setup._DATA_SETUP)
hass.config.components.remove("platform_conf")
with assert_setup_component(1):
@ -131,7 +131,7 @@ async def test_validate_platform_config(
{"platform_conf": [{"platform": "whatever", "hello": "world"}]},
)
hass.data.pop(setup.DATA_SETUP)
hass.data.pop(setup._DATA_SETUP)
hass.config.components.remove("platform_conf")
# Any falsey platform config will be ignored (None, {}, etc)
@ -240,7 +240,7 @@ async def test_validate_platform_config_4(hass: HomeAssistant) -> None:
},
)
hass.data.pop(setup.DATA_SETUP)
hass.data.pop(setup._DATA_SETUP)
hass.config.components.remove("platform_conf")
@ -345,7 +345,7 @@ async def test_component_not_setup_missing_dependencies(hass: HomeAssistant) ->
assert not await setup.async_setup_component(hass, "comp", {})
assert "comp" not in hass.config.components
hass.data.pop(setup.DATA_SETUP)
hass.data.pop(setup._DATA_SETUP)
mock_integration(hass, MockModule("comp2", dependencies=deps))
mock_integration(hass, MockModule("maybe_existing"))
@ -443,8 +443,8 @@ async def test_component_exception_setup(hass: HomeAssistant) -> None:
mock_integration(hass, MockModule(domain, setup=exception_setup))
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 in hass.data[setup._DATA_SETUP]
assert domain not in hass.data[setup._DATA_SETUP_DONE]
assert domain not in hass.config.components
@ -463,8 +463,8 @@ async def test_component_base_exception_setup(hass: HomeAssistant) -> None:
await setup.async_setup_component(hass, "comp", {})
assert str(exc_info.value) == "fail!"
assert domain in hass.data[setup.DATA_SETUP]
assert domain not in hass.data[setup.DATA_SETUP_DONE]
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
@ -477,12 +477,12 @@ async def test_set_domains_to_be_loaded(hass: HomeAssistant) -> None:
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])
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]
assert setup_done == hass.data[setup._DATA_SETUP_DONE]
def good_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Success."""
@ -515,8 +515,8 @@ async def test_set_domains_to_be_loaded(hass: HomeAssistant) -> None:
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]) == {
assert not hass.data[setup._DATA_SETUP_DONE]
assert set(hass.data[setup._DATA_SETUP]) == {
domain_bad,
domain_exception,
domain_base_exception,
@ -525,7 +525,7 @@ async def test_set_domains_to_be_loaded(hass: HomeAssistant) -> None:
# 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]
assert not hass.data[setup._DATA_SETUP_DONE]
async def test_component_setup_after_dependencies(hass: HomeAssistant) -> None:
@ -608,7 +608,7 @@ async def test_platform_specific_config_validation(hass: HomeAssistant) -> None:
assert mock_setup.call_count == 0
assert len(mock_notify.mock_calls) == 1
hass.data.pop(setup.DATA_SETUP)
hass.data.pop(setup._DATA_SETUP)
hass.config.components.remove("switch")
with (
@ -630,7 +630,7 @@ async def test_platform_specific_config_validation(hass: HomeAssistant) -> None:
assert mock_setup.call_count == 0
assert len(mock_notify.mock_calls) == 1
hass.data.pop(setup.DATA_SETUP)
hass.data.pop(setup._DATA_SETUP)
hass.config.components.remove("switch")
with (
@ -656,7 +656,7 @@ async def test_disable_component_if_invalid_return(hass: HomeAssistant) -> None:
assert not await setup.async_setup_component(hass, "disabled_component", {})
assert "disabled_component" not in hass.config.components
hass.data.pop(setup.DATA_SETUP)
hass.data.pop(setup._DATA_SETUP)
mock_integration(
hass,
MockModule("disabled_component", setup=lambda hass, config: False),
@ -665,7 +665,7 @@ async def test_disable_component_if_invalid_return(hass: HomeAssistant) -> None:
assert not await setup.async_setup_component(hass, "disabled_component", {})
assert "disabled_component" not in hass.config.components
hass.data.pop(setup.DATA_SETUP)
hass.data.pop(setup._DATA_SETUP)
mock_integration(
hass, MockModule("disabled_component", setup=lambda hass, config: True)
)
@ -939,7 +939,7 @@ async def test_integration_only_setup_entry(hass: HomeAssistant) -> None:
async def test_async_start_setup_running(hass: HomeAssistant) -> None:
"""Test setup started context manager does nothing when running."""
assert hass.state is CoreState.running
setup_started = hass.data.setdefault(setup.DATA_SETUP_STARTED, {})
setup_started = hass.data.setdefault(setup._DATA_SETUP_STARTED, {})
with setup.async_start_setup(
hass, integration="august", phase=setup.SetupPhases.SETUP
@ -952,7 +952,7 @@ async def test_async_start_setup_config_entry(
) -> None:
"""Test setup started keeps track of setup times with a config entry."""
hass.set_state(CoreState.not_running)
setup_started = hass.data.setdefault(setup.DATA_SETUP_STARTED, {})
setup_started = hass.data.setdefault(setup._DATA_SETUP_STARTED, {})
setup_time = setup._setup_times(hass)
with setup.async_start_setup(
@ -1062,7 +1062,7 @@ async def test_async_start_setup_config_entry_late_platform(
) -> None:
"""Test setup started tracks config entry time with a late platform load."""
hass.set_state(CoreState.not_running)
setup_started = hass.data.setdefault(setup.DATA_SETUP_STARTED, {})
setup_started = hass.data.setdefault(setup._DATA_SETUP_STARTED, {})
setup_time = setup._setup_times(hass)
with setup.async_start_setup(
@ -1116,7 +1116,7 @@ async def test_async_start_setup_config_entry_platform_wait(
) -> None:
"""Test setup started tracks wait time when a platform loads inside of config entry setup."""
hass.set_state(CoreState.not_running)
setup_started = hass.data.setdefault(setup.DATA_SETUP_STARTED, {})
setup_started = hass.data.setdefault(setup._DATA_SETUP_STARTED, {})
setup_time = setup._setup_times(hass)
with setup.async_start_setup(
@ -1158,7 +1158,7 @@ async def test_async_start_setup_config_entry_platform_wait(
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."""
hass.set_state(CoreState.not_running)
setup_started = hass.data.setdefault(setup.DATA_SETUP_STARTED, {})
setup_started = hass.data.setdefault(setup._DATA_SETUP_STARTED, {})
setup_time = setup._setup_times(hass)
with setup.async_start_setup(
@ -1174,7 +1174,7 @@ async def test_async_start_setup_top_level_yaml(hass: HomeAssistant) -> None:
async def test_async_start_setup_platform_integration(hass: HomeAssistant) -> None:
"""Test setup started keeps track of setup times a platform integration."""
hass.set_state(CoreState.not_running)
setup_started = hass.data.setdefault(setup.DATA_SETUP_STARTED, {})
setup_started = hass.data.setdefault(setup._DATA_SETUP_STARTED, {})
setup_time = setup._setup_times(hass)
with setup.async_start_setup(
@ -1208,7 +1208,7 @@ async def test_async_start_setup_legacy_platform_integration(
) -> None:
"""Test setup started keeps track of setup times for a legacy platform integration."""
hass.set_state(CoreState.not_running)
setup_started = hass.data.setdefault(setup.DATA_SETUP_STARTED, {})
setup_started = hass.data.setdefault(setup._DATA_SETUP_STARTED, {})
setup_time = setup._setup_times(hass)
with setup.async_start_setup(
@ -1330,7 +1330,7 @@ async def test_setup_config_entry_from_yaml(
assert await setup.async_setup_component(hass, "test_integration_only_entry", {})
assert expected_warning not in caplog.text
caplog.clear()
hass.data.pop(setup.DATA_SETUP)
hass.data.pop(setup._DATA_SETUP)
hass.config.components.remove("test_integration_only_entry")
# There should be a warning, but setup should not fail
@ -1339,7 +1339,7 @@ async def test_setup_config_entry_from_yaml(
)
assert expected_warning in caplog.text
caplog.clear()
hass.data.pop(setup.DATA_SETUP)
hass.data.pop(setup._DATA_SETUP)
hass.config.components.remove("test_integration_only_entry")
# There should be a warning, but setup should not fail
@ -1348,7 +1348,7 @@ async def test_setup_config_entry_from_yaml(
)
assert expected_warning in caplog.text
caplog.clear()
hass.data.pop(setup.DATA_SETUP)
hass.data.pop(setup._DATA_SETUP)
hass.config.components.remove("test_integration_only_entry")
# There should be a warning, but setup should not fail
@ -1359,7 +1359,7 @@ async def test_setup_config_entry_from_yaml(
)
assert expected_warning in caplog.text
caplog.clear()
hass.data.pop(setup.DATA_SETUP)
hass.data.pop(setup._DATA_SETUP)
hass.config.components.remove("test_integration_only_entry")
@ -1408,3 +1408,42 @@ async def test_async_prepare_setup_platform(
await setup.async_prepare_setup_platform(hass, {}, "button", "test") is None
)
assert button_platform is not None
async def test_async_wait_component(hass: HomeAssistant) -> None:
"""Test async_wait_component."""
setup_stall = asyncio.Event()
setup_started = asyncio.Event()
async def mock_setup(hass: HomeAssistant, _) -> bool:
setup_started.set()
await setup_stall.wait()
return True
mock_integration(hass, MockModule("test", async_setup=mock_setup))
# The integration not loaded, and is also not scheduled to load
assert await setup.async_wait_component(hass, "test") is False
# Mark the component as scheduled to be loaded
setup.async_set_domains_to_be_loaded(hass, {"test"})
# Start loading the component, including its config entries
hass.async_create_task(setup.async_setup_component(hass, "test", {}))
await setup_started.wait()
# The component is not yet loaded
assert "test" not in hass.config.components
# Allow setup to proceed
setup_stall.set()
# The component is scheduled to load, this will block until the config entry is loaded
assert await setup.async_wait_component(hass, "test") is True
# The component has been loaded
assert "test" in hass.config.components
# Clear the event, then call again to make sure we don't block
setup_stall.clear()
assert await setup.async_wait_component(hass, "test") is True