Remove async_late_forward_entry_setups
and instead implicitly hold the lock (#119088)
* Refactor config entry forwards to implictly obtain the lock instead of explictly This is a bit of a tradeoff to not need async_late_forward_entry_setups The downside is we can no longer detect non-awaited plastform setups as we will always implicitly obtain the lock instead of explictly. Note, this PR is incomplete and is only for discussion purposes at this point * preen * cover * cover * restore check for non-awaited platform setup * cleanup * fix missing word * make non-awaited test safer
This commit is contained in:
parent
4e121fcbe8
commit
dbd3147c9b
18 changed files with 381 additions and 188 deletions
|
@ -182,7 +182,7 @@ class AmbientStation:
|
||||||
# already been done):
|
# already been done):
|
||||||
if not self._entry_setup_complete:
|
if not self._entry_setup_complete:
|
||||||
self._hass.async_create_task(
|
self._hass.async_create_task(
|
||||||
self._hass.config_entries.async_late_forward_entry_setups(
|
self._hass.config_entries.async_forward_entry_setups(
|
||||||
self._entry, PLATFORMS
|
self._entry, PLATFORMS
|
||||||
),
|
),
|
||||||
eager_start=True,
|
eager_start=True,
|
||||||
|
|
|
@ -28,7 +28,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: CertExpiryConfigEntry) -
|
||||||
|
|
||||||
async def _async_finish_startup(_: HomeAssistant) -> None:
|
async def _async_finish_startup(_: HomeAssistant) -> None:
|
||||||
await coordinator.async_refresh()
|
await coordinator.async_refresh()
|
||||||
await hass.config_entries.async_late_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
async_at_started(hass, _async_finish_startup)
|
async_at_started(hass, _async_finish_startup)
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -248,15 +248,9 @@ class RuntimeEntryData:
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: ConfigEntry,
|
entry: ConfigEntry,
|
||||||
platforms: set[Platform],
|
platforms: set[Platform],
|
||||||
late: bool,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
async with self.platform_load_lock:
|
async with self.platform_load_lock:
|
||||||
if needed := platforms - self.loaded_platforms:
|
if needed := platforms - self.loaded_platforms:
|
||||||
if late:
|
|
||||||
await hass.config_entries.async_late_forward_entry_setups(
|
|
||||||
entry, needed
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, needed)
|
await hass.config_entries.async_forward_entry_setups(entry, needed)
|
||||||
self.loaded_platforms |= needed
|
self.loaded_platforms |= needed
|
||||||
|
|
||||||
|
@ -266,7 +260,6 @@ class RuntimeEntryData:
|
||||||
entry: ConfigEntry,
|
entry: ConfigEntry,
|
||||||
infos: list[EntityInfo],
|
infos: list[EntityInfo],
|
||||||
mac: str,
|
mac: str,
|
||||||
late: bool = False,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Distribute an update of static infos to all platforms."""
|
"""Distribute an update of static infos to all platforms."""
|
||||||
# First, load all platforms
|
# First, load all platforms
|
||||||
|
@ -296,7 +289,7 @@ class RuntimeEntryData:
|
||||||
):
|
):
|
||||||
ent_reg.async_update_entity(old_entry, new_unique_id=new_unique_id)
|
ent_reg.async_update_entity(old_entry, new_unique_id=new_unique_id)
|
||||||
|
|
||||||
await self._ensure_platforms_loaded(hass, entry, needed_platforms, late)
|
await self._ensure_platforms_loaded(hass, entry, needed_platforms)
|
||||||
|
|
||||||
# Make a dict of the EntityInfo by type and send
|
# Make a dict of the EntityInfo by type and send
|
||||||
# them to the listeners for each specific EntityInfo type
|
# them to the listeners for each specific EntityInfo type
|
||||||
|
|
|
@ -491,7 +491,7 @@ class ESPHomeManager:
|
||||||
|
|
||||||
entry_data.async_update_device_state()
|
entry_data.async_update_device_state()
|
||||||
await entry_data.async_update_static_infos(
|
await entry_data.async_update_static_infos(
|
||||||
hass, entry, entity_infos, device_info.mac_address, late=True
|
hass, entry, entity_infos, device_info.mac_address
|
||||||
)
|
)
|
||||||
_setup_services(hass, entry_data, services)
|
_setup_services(hass, entry_data, services)
|
||||||
|
|
||||||
|
|
|
@ -379,9 +379,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
new_config: list[ConfigType] = config_yaml.get(DOMAIN, [])
|
new_config: list[ConfigType] = config_yaml.get(DOMAIN, [])
|
||||||
platforms_used = platforms_from_config(new_config)
|
platforms_used = platforms_from_config(new_config)
|
||||||
new_platforms = platforms_used - mqtt_data.platforms_loaded
|
new_platforms = platforms_used - mqtt_data.platforms_loaded
|
||||||
await async_forward_entry_setup_and_setup_discovery(
|
await async_forward_entry_setup_and_setup_discovery(hass, entry, new_platforms)
|
||||||
hass, entry, new_platforms, late=True
|
|
||||||
)
|
|
||||||
# Check the schema before continuing reload
|
# Check the schema before continuing reload
|
||||||
await async_check_config_schema(hass, config_yaml)
|
await async_check_config_schema(hass, config_yaml)
|
||||||
|
|
||||||
|
|
|
@ -211,7 +211,7 @@ async def async_start( # noqa: C901
|
||||||
async with platform_setup_lock.setdefault(component, asyncio.Lock()):
|
async with platform_setup_lock.setdefault(component, asyncio.Lock()):
|
||||||
if component not in mqtt_data.platforms_loaded:
|
if component not in mqtt_data.platforms_loaded:
|
||||||
await async_forward_entry_setup_and_setup_discovery(
|
await async_forward_entry_setup_and_setup_discovery(
|
||||||
hass, config_entry, {component}, late=True
|
hass, config_entry, {component}
|
||||||
)
|
)
|
||||||
_async_add_component(discovery_payload)
|
_async_add_component(discovery_payload)
|
||||||
|
|
||||||
|
|
|
@ -72,11 +72,13 @@ async def async_forward_entry_setup_and_setup_discovery(
|
||||||
|
|
||||||
tasks.append(create_eager_task(tag.async_setup_entry(hass, config_entry)))
|
tasks.append(create_eager_task(tag.async_setup_entry(hass, config_entry)))
|
||||||
if new_entity_platforms := (new_platforms - {"tag", "device_automation"}):
|
if new_entity_platforms := (new_platforms - {"tag", "device_automation"}):
|
||||||
if late:
|
tasks.append(
|
||||||
coro = hass.config_entries.async_late_forward_entry_setups
|
create_eager_task(
|
||||||
else:
|
hass.config_entries.async_forward_entry_setups(
|
||||||
coro = hass.config_entries.async_forward_entry_setups
|
config_entry, new_entity_platforms
|
||||||
tasks.append(create_eager_task(coro(config_entry, new_entity_platforms)))
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
if not tasks:
|
if not tasks:
|
||||||
return
|
return
|
||||||
await asyncio.gather(*tasks)
|
await asyncio.gather(*tasks)
|
||||||
|
|
|
@ -200,9 +200,7 @@ class ShellyCoordinatorBase[_DeviceT: BlockDevice | RpcDevice](
|
||||||
self.hass.config_entries.async_update_entry(self.entry, data=data)
|
self.hass.config_entries.async_update_entry(self.entry, data=data)
|
||||||
|
|
||||||
# Resume platform setup
|
# Resume platform setup
|
||||||
await self.hass.config_entries.async_late_forward_entry_setups(
|
await self.hass.config_entries.async_forward_entry_setups(self.entry, platforms)
|
||||||
self.entry, platforms
|
|
||||||
)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
@ -330,7 +330,7 @@ class DriverEvents:
|
||||||
"""Set up platform if needed."""
|
"""Set up platform if needed."""
|
||||||
if platform not in self.platform_setup_tasks:
|
if platform not in self.platform_setup_tasks:
|
||||||
self.platform_setup_tasks[platform] = self.hass.async_create_task(
|
self.platform_setup_tasks[platform] = self.hass.async_create_task(
|
||||||
self.hass.config_entries.async_late_forward_entry_setups(
|
self.hass.config_entries.async_forward_entry_setups(
|
||||||
self.config_entry, [platform]
|
self.config_entry, [platform]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -1170,18 +1170,13 @@ class FlowCancelledError(Exception):
|
||||||
"""Error to indicate that a flow has been cancelled."""
|
"""Error to indicate that a flow has been cancelled."""
|
||||||
|
|
||||||
|
|
||||||
def _report_non_locked_platform_forwards(entry: ConfigEntry) -> None:
|
def _report_non_awaited_platform_forwards(entry: ConfigEntry, what: str) -> None:
|
||||||
"""Report non awaited and non-locked platform forwards."""
|
"""Report non awaited platform forwards."""
|
||||||
report(
|
report(
|
||||||
f"calls async_forward_entry_setup after the entry for "
|
f"calls {what} for integration {entry.domain} with "
|
||||||
f"integration, {entry.domain} with title: {entry.title} "
|
f"title: {entry.title} and entry_id: {entry.entry_id}, "
|
||||||
f"and entry_id: {entry.entry_id}, has been set up, "
|
f"during setup without awaiting {what}, which can cause "
|
||||||
"without holding the setup lock that prevents the config "
|
"the setup lock to be released before the setup is done. "
|
||||||
"entry from being set up multiple times. "
|
|
||||||
"Instead await hass.config_entries.async_forward_entry_setup "
|
|
||||||
"during setup of the config entry or call "
|
|
||||||
"hass.config_entries.async_late_forward_entry_setups "
|
|
||||||
"in a tracked task. "
|
|
||||||
"This will stop working in Home Assistant 2025.1",
|
"This will stop working in Home Assistant 2025.1",
|
||||||
error_if_integration=False,
|
error_if_integration=False,
|
||||||
error_if_core=False,
|
error_if_core=False,
|
||||||
|
@ -2041,9 +2036,6 @@ class ConfigEntries:
|
||||||
before the entry is set up. This ensures that the config entry cannot
|
before the entry is set up. This ensures that the config entry cannot
|
||||||
be unloaded before all platforms are loaded.
|
be unloaded before all platforms are loaded.
|
||||||
|
|
||||||
If platforms must be loaded late (after the config entry is setup),
|
|
||||||
use async_late_forward_entry_setup instead.
|
|
||||||
|
|
||||||
This method is more efficient than async_forward_entry_setup as
|
This method is more efficient than async_forward_entry_setup as
|
||||||
it can load multiple platforms at once and does not require a separate
|
it can load multiple platforms at once and does not require a separate
|
||||||
import executor job for each platform.
|
import executor job for each platform.
|
||||||
|
@ -2052,14 +2044,32 @@ class ConfigEntries:
|
||||||
if not integration.platforms_are_loaded(platforms):
|
if not integration.platforms_are_loaded(platforms):
|
||||||
with async_pause_setup(self.hass, SetupPhases.WAIT_IMPORT_PLATFORMS):
|
with async_pause_setup(self.hass, SetupPhases.WAIT_IMPORT_PLATFORMS):
|
||||||
await integration.async_get_platforms(platforms)
|
await integration.async_get_platforms(platforms)
|
||||||
if non_locked_platform_forwards := not entry.setup_lock.locked():
|
|
||||||
_report_non_locked_platform_forwards(entry)
|
if not entry.setup_lock.locked():
|
||||||
|
async with entry.setup_lock:
|
||||||
|
if entry.state is not ConfigEntryState.LOADED:
|
||||||
|
raise OperationNotAllowed(
|
||||||
|
f"The config entry {entry.title} ({entry.domain}) with entry_id"
|
||||||
|
f" {entry.entry_id} cannot forward setup for {platforms} because it"
|
||||||
|
f" is not loaded in the {entry.state} state"
|
||||||
|
)
|
||||||
|
await self._async_forward_entry_setups_locked(entry, platforms)
|
||||||
|
else:
|
||||||
|
await self._async_forward_entry_setups_locked(entry, platforms)
|
||||||
|
# If the lock was held when we stated, and it was released during
|
||||||
|
# the platform setup, it means they did not await the setup call.
|
||||||
|
if not entry.setup_lock.locked():
|
||||||
|
_report_non_awaited_platform_forwards(
|
||||||
|
entry, "async_forward_entry_setups"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _async_forward_entry_setups_locked(
|
||||||
|
self, entry: ConfigEntry, platforms: Iterable[Platform | str]
|
||||||
|
) -> None:
|
||||||
await asyncio.gather(
|
await asyncio.gather(
|
||||||
*(
|
*(
|
||||||
create_eager_task(
|
create_eager_task(
|
||||||
self._async_forward_entry_setup(
|
self._async_forward_entry_setup(entry, platform, False),
|
||||||
entry, platform, False, non_locked_platform_forwards
|
|
||||||
),
|
|
||||||
name=(
|
name=(
|
||||||
f"config entry forward setup {entry.title} "
|
f"config entry forward setup {entry.title} "
|
||||||
f"{entry.domain} {entry.entry_id} {platform}"
|
f"{entry.domain} {entry.entry_id} {platform}"
|
||||||
|
@ -2070,25 +2080,6 @@ class ConfigEntries:
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_late_forward_entry_setups(
|
|
||||||
self, entry: ConfigEntry, platforms: Iterable[Platform | str]
|
|
||||||
) -> None:
|
|
||||||
"""Forward the setup of an entry to platforms after setup.
|
|
||||||
|
|
||||||
If platforms must be loaded late (after the config entry is setup),
|
|
||||||
use this method instead of async_forward_entry_setups as it holds
|
|
||||||
the setup lock until the platforms are loaded to ensure that the
|
|
||||||
config entry cannot be unloaded while platforms are loaded.
|
|
||||||
"""
|
|
||||||
async with entry.setup_lock:
|
|
||||||
if entry.state is not ConfigEntryState.LOADED:
|
|
||||||
raise OperationNotAllowed(
|
|
||||||
f"The config entry {entry.title} ({entry.domain}) with entry_id"
|
|
||||||
f" {entry.entry_id} cannot forward setup for {platforms} "
|
|
||||||
f"because it is not loaded in the {entry.state} state"
|
|
||||||
)
|
|
||||||
await self.async_forward_entry_setups(entry, platforms)
|
|
||||||
|
|
||||||
async def async_forward_entry_setup(
|
async def async_forward_entry_setup(
|
||||||
self, entry: ConfigEntry, domain: Platform | str
|
self, entry: ConfigEntry, domain: Platform | str
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
@ -2103,13 +2094,7 @@ class ConfigEntries:
|
||||||
Instead, await async_forward_entry_setups as it can load
|
Instead, await async_forward_entry_setups as it can load
|
||||||
multiple platforms at once and is more efficient since it
|
multiple platforms at once and is more efficient since it
|
||||||
does not require a separate import executor job for each platform.
|
does not require a separate import executor job for each platform.
|
||||||
|
|
||||||
If platforms must be loaded late (after the config entry is setup),
|
|
||||||
use async_late_forward_entry_setup instead.
|
|
||||||
"""
|
"""
|
||||||
if non_locked_platform_forwards := not entry.setup_lock.locked():
|
|
||||||
_report_non_locked_platform_forwards(entry)
|
|
||||||
else:
|
|
||||||
report(
|
report(
|
||||||
"calls async_forward_entry_setup for "
|
"calls async_forward_entry_setup for "
|
||||||
f"integration, {entry.domain} with title: {entry.title} "
|
f"integration, {entry.domain} with title: {entry.title} "
|
||||||
|
@ -2119,16 +2104,27 @@ class ConfigEntries:
|
||||||
error_if_core=False,
|
error_if_core=False,
|
||||||
error_if_integration=False,
|
error_if_integration=False,
|
||||||
)
|
)
|
||||||
return await self._async_forward_entry_setup(
|
if not entry.setup_lock.locked():
|
||||||
entry, domain, True, non_locked_platform_forwards
|
async with entry.setup_lock:
|
||||||
|
if entry.state is not ConfigEntryState.LOADED:
|
||||||
|
raise OperationNotAllowed(
|
||||||
|
f"The config entry {entry.title} ({entry.domain}) with entry_id"
|
||||||
|
f" {entry.entry_id} cannot forward setup for {domain} because it"
|
||||||
|
f" is not loaded in the {entry.state} state"
|
||||||
)
|
)
|
||||||
|
return await self._async_forward_entry_setup(entry, domain, True)
|
||||||
|
result = await self._async_forward_entry_setup(entry, domain, True)
|
||||||
|
# If the lock was held when we stated, and it was released during
|
||||||
|
# the platform setup, it means they did not await the setup call.
|
||||||
|
if not entry.setup_lock.locked():
|
||||||
|
_report_non_awaited_platform_forwards(entry, "async_forward_entry_setup")
|
||||||
|
return result
|
||||||
|
|
||||||
async def _async_forward_entry_setup(
|
async def _async_forward_entry_setup(
|
||||||
self,
|
self,
|
||||||
entry: ConfigEntry,
|
entry: ConfigEntry,
|
||||||
domain: Platform | str,
|
domain: Platform | str,
|
||||||
preload_platform: bool,
|
preload_platform: bool,
|
||||||
non_locked_platform_forwards: bool,
|
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Forward the setup of an entry to a different component."""
|
"""Forward the setup of an entry to a different component."""
|
||||||
# Setup Component if not set up yet
|
# Setup Component if not set up yet
|
||||||
|
@ -2152,12 +2148,6 @@ class ConfigEntries:
|
||||||
|
|
||||||
integration = loader.async_get_loaded_integration(self.hass, domain)
|
integration = loader.async_get_loaded_integration(self.hass, domain)
|
||||||
await entry.async_setup(self.hass, integration=integration)
|
await entry.async_setup(self.hass, integration=integration)
|
||||||
|
|
||||||
# Check again after setup to make sure the lock
|
|
||||||
# is still there because it could have been released
|
|
||||||
# unless we already reported it.
|
|
||||||
if not non_locked_platform_forwards and not entry.setup_lock.locked():
|
|
||||||
_report_non_locked_platform_forwards(entry)
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def async_unload_platforms(
|
async def async_unload_platforms(
|
||||||
|
@ -2221,7 +2211,7 @@ class ConfigEntries:
|
||||||
# The component was not loaded.
|
# The component was not loaded.
|
||||||
if entry.domain not in self.hass.config.components:
|
if entry.domain not in self.hass.config.components:
|
||||||
return False
|
return False
|
||||||
return entry.state == ConfigEntryState.LOADED
|
return entry.state is ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
|
|
|
@ -53,7 +53,7 @@ async def init_select(hass: HomeAssistant, init_components) -> ConfigEntry:
|
||||||
domain="assist_pipeline", state=ConfigEntryState.LOADED
|
domain="assist_pipeline", state=ConfigEntryState.LOADED
|
||||||
)
|
)
|
||||||
config_entry.add_to_hass(hass)
|
config_entry.add_to_hass(hass)
|
||||||
await hass.config_entries.async_late_forward_entry_setups(config_entry, ["select"])
|
await hass.config_entries.async_forward_entry_setups(config_entry, ["select"])
|
||||||
return config_entry
|
return config_entry
|
||||||
|
|
||||||
|
|
||||||
|
@ -161,7 +161,7 @@ async def test_select_entity_changing_pipelines(
|
||||||
|
|
||||||
# Reload config entry to test selected option persists
|
# Reload config entry to test selected option persists
|
||||||
assert await hass.config_entries.async_forward_entry_unload(config_entry, "select")
|
assert await hass.config_entries.async_forward_entry_unload(config_entry, "select")
|
||||||
await hass.config_entries.async_late_forward_entry_setups(config_entry, ["select"])
|
await hass.config_entries.async_forward_entry_setups(config_entry, ["select"])
|
||||||
|
|
||||||
state = hass.states.get("select.assist_pipeline_test_prefix_pipeline")
|
state = hass.states.get("select.assist_pipeline_test_prefix_pipeline")
|
||||||
assert state is not None
|
assert state is not None
|
||||||
|
@ -209,7 +209,7 @@ async def test_select_entity_changing_vad_sensitivity(
|
||||||
|
|
||||||
# Reload config entry to test selected option persists
|
# Reload config entry to test selected option persists
|
||||||
assert await hass.config_entries.async_forward_entry_unload(config_entry, "select")
|
assert await hass.config_entries.async_forward_entry_unload(config_entry, "select")
|
||||||
await hass.config_entries.async_late_forward_entry_setups(config_entry, ["select"])
|
await hass.config_entries.async_forward_entry_setups(config_entry, ["select"])
|
||||||
|
|
||||||
state = hass.states.get("select.assist_pipeline_test_vad_sensitivity")
|
state = hass.states.get("select.assist_pipeline_test_vad_sensitivity")
|
||||||
assert state is not None
|
assert state is not None
|
||||||
|
|
|
@ -278,7 +278,7 @@ async def setup_platform(
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
config_entry.mock_state(hass, ConfigEntryState.LOADED)
|
config_entry.mock_state(hass, ConfigEntryState.LOADED)
|
||||||
await hass.config_entries.async_late_forward_entry_setups(config_entry, platforms)
|
await hass.config_entries.async_forward_entry_setups(config_entry, platforms)
|
||||||
|
|
||||||
# and make sure it completes before going further
|
# and make sure it completes before going further
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
|
@ -186,7 +186,7 @@ async def setup_bridge(hass: HomeAssistant, mock_bridge_v1):
|
||||||
config_entry.mock_state(hass, ConfigEntryState.LOADED)
|
config_entry.mock_state(hass, ConfigEntryState.LOADED)
|
||||||
mock_bridge_v1.config_entry = config_entry
|
mock_bridge_v1.config_entry = config_entry
|
||||||
hass.data[hue.DOMAIN] = {config_entry.entry_id: mock_bridge_v1}
|
hass.data[hue.DOMAIN] = {config_entry.entry_id: mock_bridge_v1}
|
||||||
await hass.config_entries.async_late_forward_entry_setups(config_entry, ["light"])
|
await hass.config_entries.async_forward_entry_setups(config_entry, ["light"])
|
||||||
# To flush out the service call to update the group
|
# To flush out the service call to update the group
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
|
|
@ -75,7 +75,7 @@ async def test_enable_sensor(
|
||||||
|
|
||||||
assert await async_setup_component(hass, hue.DOMAIN, {}) is True
|
assert await async_setup_component(hass, hue.DOMAIN, {}) is True
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
await hass.config_entries.async_late_forward_entry_setups(
|
await hass.config_entries.async_forward_entry_setups(
|
||||||
mock_config_entry_v2, ["sensor"]
|
mock_config_entry_v2, ["sensor"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ async def test_enable_sensor(
|
||||||
|
|
||||||
# reload platform and check if entity is correctly there
|
# reload platform and check if entity is correctly there
|
||||||
await hass.config_entries.async_forward_entry_unload(mock_config_entry_v2, "sensor")
|
await hass.config_entries.async_forward_entry_unload(mock_config_entry_v2, "sensor")
|
||||||
await hass.config_entries.async_late_forward_entry_setups(
|
await hass.config_entries.async_forward_entry_setups(
|
||||||
mock_config_entry_v2, ["sensor"]
|
mock_config_entry_v2, ["sensor"]
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
|
@ -104,7 +104,7 @@ async def test_restoring_location(
|
||||||
|
|
||||||
# mobile app doesn't support unloading, so we just reload device tracker
|
# mobile app doesn't support unloading, so we just reload device tracker
|
||||||
await hass.config_entries.async_forward_entry_unload(config_entry, "device_tracker")
|
await hass.config_entries.async_forward_entry_unload(config_entry, "device_tracker")
|
||||||
await hass.config_entries.async_late_forward_entry_setups(
|
await hass.config_entries.async_forward_entry_setups(
|
||||||
config_entry, ["device_tracker"]
|
config_entry, ["device_tracker"]
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
|
@ -71,7 +71,7 @@ async def setup_platform(hass, platform: str, *, devices=None, scenes=None):
|
||||||
|
|
||||||
hass.data[DOMAIN] = {DATA_BROKERS: {config_entry.entry_id: broker}}
|
hass.data[DOMAIN] = {DATA_BROKERS: {config_entry.entry_id: broker}}
|
||||||
config_entry.mock_state(hass, ConfigEntryState.LOADED)
|
config_entry.mock_state(hass, ConfigEntryState.LOADED)
|
||||||
await hass.config_entries.async_late_forward_entry_setups(config_entry, [platform])
|
await hass.config_entries.async_forward_entry_setups(config_entry, [platform])
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
return config_entry
|
return config_entry
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,12 @@ IGNORE_UNCAUGHT_EXCEPTIONS = [
|
||||||
"tests.helpers.test_event",
|
"tests.helpers.test_event",
|
||||||
"test_track_point_in_time_repr",
|
"test_track_point_in_time_repr",
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
# This test explicitly throws an uncaught exception
|
||||||
|
# and should not be removed.
|
||||||
|
"tests.test_config_entries",
|
||||||
|
"test_config_entry_unloaded_during_platform_setups",
|
||||||
|
),
|
||||||
(
|
(
|
||||||
# This test explicitly throws an uncaught exception
|
# This test explicitly throws an uncaught exception
|
||||||
# and should not be removed.
|
# and should not be removed.
|
||||||
|
|
|
@ -35,6 +35,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
from homeassistant.setup import async_set_domains_to_be_loaded, async_setup_component
|
from homeassistant.setup import async_set_domains_to_be_loaded, async_setup_component
|
||||||
|
from homeassistant.util.async_ import create_eager_task
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from .common import (
|
from .common import (
|
||||||
|
@ -971,7 +972,7 @@ async def test_forward_entry_sets_up_component(hass: HomeAssistant) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch.object(integration, "async_get_platforms") as mock_async_get_platforms:
|
with patch.object(integration, "async_get_platforms") as mock_async_get_platforms:
|
||||||
await hass.config_entries.async_late_forward_entry_setups(entry, ["forwarded"])
|
await hass.config_entries.async_forward_entry_setups(entry, ["forwarded"])
|
||||||
|
|
||||||
mock_async_get_platforms.assert_called_once_with(["forwarded"])
|
mock_async_get_platforms.assert_called_once_with(["forwarded"])
|
||||||
assert len(mock_original_setup_entry.mock_calls) == 0
|
assert len(mock_original_setup_entry.mock_calls) == 0
|
||||||
|
@ -1001,7 +1002,7 @@ async def test_forward_entry_does_not_setup_entry_if_setup_fails(
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch.object(integration, "async_get_platforms"):
|
with patch.object(integration, "async_get_platforms"):
|
||||||
await hass.config_entries.async_late_forward_entry_setups(entry, ["forwarded"])
|
await hass.config_entries.async_forward_entry_setups(entry, ["forwarded"])
|
||||||
assert len(mock_setup.mock_calls) == 1
|
assert len(mock_setup.mock_calls) == 1
|
||||||
assert len(mock_setup_entry.mock_calls) == 0
|
assert len(mock_setup_entry.mock_calls) == 0
|
||||||
|
|
||||||
|
@ -1028,23 +1029,7 @@ async def test_async_forward_entry_setup_deprecated(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch.object(integration, "async_get_platforms"):
|
|
||||||
await hass.config_entries.async_forward_entry_setup(entry, "forwarded")
|
|
||||||
assert len(mock_setup.mock_calls) == 1
|
|
||||||
assert len(mock_setup_entry.mock_calls) == 0
|
|
||||||
entry_id = entry.entry_id
|
entry_id = entry.entry_id
|
||||||
assert (
|
|
||||||
"Detected code that calls async_forward_entry_setup after the entry "
|
|
||||||
"for integration, original with title: Mock Title and entry_id: "
|
|
||||||
f"{entry_id}, has been set up, without holding the setup lock that "
|
|
||||||
"prevents the config entry from being set up multiple times. "
|
|
||||||
"Instead await hass.config_entries.async_forward_entry_setup "
|
|
||||||
"during setup of the config entry or call "
|
|
||||||
"hass.config_entries.async_late_forward_entry_setups "
|
|
||||||
"in a tracked task. This will stop working in Home Assistant "
|
|
||||||
"2025.1. Please report this issue."
|
|
||||||
) in caplog.text
|
|
||||||
|
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
with patch.object(integration, "async_get_platforms"):
|
with patch.object(integration, "async_get_platforms"):
|
||||||
async with entry.setup_lock:
|
async with entry.setup_lock:
|
||||||
|
@ -5553,77 +5538,7 @@ async def test_raise_wrong_exception_in_forwarded_platform(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_non_awaited_async_forward_entry_setups(
|
async def test_config_entry_unloaded_during_platform_setups(
|
||||||
hass: HomeAssistant,
|
|
||||||
manager: config_entries.ConfigEntries,
|
|
||||||
caplog: pytest.LogCaptureFixture,
|
|
||||||
) -> None:
|
|
||||||
"""Test async_forward_entry_setups not being awaited."""
|
|
||||||
|
|
||||||
async def mock_setup_entry(
|
|
||||||
hass: HomeAssistant, entry: config_entries.ConfigEntry
|
|
||||||
) -> bool:
|
|
||||||
"""Mock setting up entry."""
|
|
||||||
# Call async_forward_entry_setups without awaiting it
|
|
||||||
# This is not allowed and will raise a warning
|
|
||||||
hass.async_create_task(
|
|
||||||
hass.config_entries.async_forward_entry_setups(entry, ["light"])
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
|
|
||||||
async def mock_unload_entry(
|
|
||||||
hass: HomeAssistant, entry: config_entries.ConfigEntry
|
|
||||||
) -> bool:
|
|
||||||
"""Mock unloading an entry."""
|
|
||||||
result = await hass.config_entries.async_unload_platforms(entry, ["light"])
|
|
||||||
assert result
|
|
||||||
return result
|
|
||||||
|
|
||||||
mock_remove_entry = AsyncMock(return_value=None)
|
|
||||||
|
|
||||||
async def mock_setup_entry_platform(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
entry: config_entries.ConfigEntry,
|
|
||||||
async_add_entities: AddEntitiesCallback,
|
|
||||||
) -> None:
|
|
||||||
"""Mock setting up platform."""
|
|
||||||
await asyncio.sleep(0)
|
|
||||||
|
|
||||||
mock_integration(
|
|
||||||
hass,
|
|
||||||
MockModule(
|
|
||||||
"test",
|
|
||||||
async_setup_entry=mock_setup_entry,
|
|
||||||
async_unload_entry=mock_unload_entry,
|
|
||||||
async_remove_entry=mock_remove_entry,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
mock_platform(
|
|
||||||
hass, "test.light", MockPlatform(async_setup_entry=mock_setup_entry_platform)
|
|
||||||
)
|
|
||||||
mock_platform(hass, "test.config_flow", None)
|
|
||||||
|
|
||||||
entry = MockConfigEntry(domain="test", entry_id="test2")
|
|
||||||
entry.add_to_manager(manager)
|
|
||||||
|
|
||||||
# Setup entry
|
|
||||||
await manager.async_setup(entry.entry_id)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
assert (
|
|
||||||
"Detected code that calls async_forward_entry_setup after the "
|
|
||||||
"entry for integration, test with title: Mock Title and entry_id:"
|
|
||||||
" test2, has been set up, without holding the setup lock that "
|
|
||||||
"prevents the config entry from being set up multiple times. "
|
|
||||||
"Instead await hass.config_entries.async_forward_entry_setup "
|
|
||||||
"during setup of the config entry or call "
|
|
||||||
"hass.config_entries.async_late_forward_entry_setups "
|
|
||||||
"in a tracked task. This will stop working in Home Assistant"
|
|
||||||
" 2025.1. Please report this issue."
|
|
||||||
) in caplog.text
|
|
||||||
|
|
||||||
|
|
||||||
async def test_config_entry_unloaded_during_platform_setup(
|
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
manager: config_entries.ConfigEntries,
|
manager: config_entries.ConfigEntries,
|
||||||
caplog: pytest.LogCaptureFixture,
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
@ -5636,12 +5551,12 @@ async def test_config_entry_unloaded_during_platform_setup(
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Mock setting up entry."""
|
"""Mock setting up entry."""
|
||||||
|
|
||||||
# Call async_late_forward_entry_setups in a non-tracked task
|
# Call async_forward_entry_setups in a non-tracked task
|
||||||
# so we can unload the config entry during the setup
|
# so we can unload the config entry during the setup
|
||||||
def _late_setup():
|
def _late_setup():
|
||||||
nonlocal task
|
nonlocal task
|
||||||
task = asyncio.create_task(
|
task = asyncio.create_task(
|
||||||
hass.config_entries.async_late_forward_entry_setups(entry, ["light"])
|
hass.config_entries.async_forward_entry_setups(entry, ["light"])
|
||||||
)
|
)
|
||||||
|
|
||||||
hass.loop.call_soon(_late_setup)
|
hass.loop.call_soon(_late_setup)
|
||||||
|
@ -5695,3 +5610,294 @@ async def test_config_entry_unloaded_during_platform_setup(
|
||||||
"entry_id test2 cannot forward setup for ['light'] because it is "
|
"entry_id test2 cannot forward setup for ['light'] because it is "
|
||||||
"not loaded in the ConfigEntryState.NOT_LOADED state"
|
"not loaded in the ConfigEntryState.NOT_LOADED state"
|
||||||
) in caplog.text
|
) in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_non_awaited_async_forward_entry_setups(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
manager: config_entries.ConfigEntries,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test async_forward_entry_setups not being awaited."""
|
||||||
|
forward_event = asyncio.Event()
|
||||||
|
task: asyncio.Task | None = None
|
||||||
|
|
||||||
|
async def mock_setup_entry(
|
||||||
|
hass: HomeAssistant, entry: config_entries.ConfigEntry
|
||||||
|
) -> bool:
|
||||||
|
"""Mock setting up entry."""
|
||||||
|
# Call async_forward_entry_setups without awaiting it
|
||||||
|
# This is not allowed and will raise a warning
|
||||||
|
nonlocal task
|
||||||
|
task = create_eager_task(
|
||||||
|
hass.config_entries.async_forward_entry_setups(entry, ["light"])
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def mock_unload_entry(
|
||||||
|
hass: HomeAssistant, entry: config_entries.ConfigEntry
|
||||||
|
) -> bool:
|
||||||
|
"""Mock unloading an entry."""
|
||||||
|
result = await hass.config_entries.async_unload_platforms(entry, ["light"])
|
||||||
|
assert result
|
||||||
|
return result
|
||||||
|
|
||||||
|
mock_remove_entry = AsyncMock(return_value=None)
|
||||||
|
|
||||||
|
async def mock_setup_entry_platform(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry: config_entries.ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Mock setting up platform."""
|
||||||
|
await forward_event.wait()
|
||||||
|
|
||||||
|
mock_integration(
|
||||||
|
hass,
|
||||||
|
MockModule(
|
||||||
|
"test",
|
||||||
|
async_setup_entry=mock_setup_entry,
|
||||||
|
async_unload_entry=mock_unload_entry,
|
||||||
|
async_remove_entry=mock_remove_entry,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
mock_platform(
|
||||||
|
hass, "test.light", MockPlatform(async_setup_entry=mock_setup_entry_platform)
|
||||||
|
)
|
||||||
|
mock_platform(hass, "test.config_flow", None)
|
||||||
|
|
||||||
|
entry = MockConfigEntry(domain="test", entry_id="test2")
|
||||||
|
entry.add_to_manager(manager)
|
||||||
|
|
||||||
|
# Setup entry
|
||||||
|
await manager.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
forward_event.set()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
await task
|
||||||
|
|
||||||
|
assert (
|
||||||
|
"Detected code that calls async_forward_entry_setups for integration "
|
||||||
|
"test with title: Mock Title and entry_id: test2, during setup without "
|
||||||
|
"awaiting async_forward_entry_setups, which can cause the setup lock "
|
||||||
|
"to be released before the setup is done. This will stop working in "
|
||||||
|
"Home Assistant 2025.1. Please report this issue."
|
||||||
|
) in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_non_awaited_async_forward_entry_setup(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
manager: config_entries.ConfigEntries,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test async_forward_entry_setup not being awaited."""
|
||||||
|
forward_event = asyncio.Event()
|
||||||
|
task: asyncio.Task | None = None
|
||||||
|
|
||||||
|
async def mock_setup_entry(
|
||||||
|
hass: HomeAssistant, entry: config_entries.ConfigEntry
|
||||||
|
) -> bool:
|
||||||
|
"""Mock setting up entry."""
|
||||||
|
# Call async_forward_entry_setup without awaiting it
|
||||||
|
# This is not allowed and will raise a warning
|
||||||
|
nonlocal task
|
||||||
|
task = create_eager_task(
|
||||||
|
hass.config_entries.async_forward_entry_setup(entry, "light")
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def mock_unload_entry(
|
||||||
|
hass: HomeAssistant, entry: config_entries.ConfigEntry
|
||||||
|
) -> bool:
|
||||||
|
"""Mock unloading an entry."""
|
||||||
|
result = await hass.config_entries.async_unload_platforms(entry, ["light"])
|
||||||
|
assert result
|
||||||
|
return result
|
||||||
|
|
||||||
|
mock_remove_entry = AsyncMock(return_value=None)
|
||||||
|
|
||||||
|
async def mock_setup_entry_platform(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry: config_entries.ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Mock setting up platform."""
|
||||||
|
await forward_event.wait()
|
||||||
|
|
||||||
|
mock_integration(
|
||||||
|
hass,
|
||||||
|
MockModule(
|
||||||
|
"test",
|
||||||
|
async_setup_entry=mock_setup_entry,
|
||||||
|
async_unload_entry=mock_unload_entry,
|
||||||
|
async_remove_entry=mock_remove_entry,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
mock_platform(
|
||||||
|
hass, "test.light", MockPlatform(async_setup_entry=mock_setup_entry_platform)
|
||||||
|
)
|
||||||
|
mock_platform(hass, "test.config_flow", None)
|
||||||
|
|
||||||
|
entry = MockConfigEntry(domain="test", entry_id="test2")
|
||||||
|
entry.add_to_manager(manager)
|
||||||
|
|
||||||
|
# Setup entry
|
||||||
|
await manager.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
forward_event.set()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
await task
|
||||||
|
|
||||||
|
assert (
|
||||||
|
"Detected code that calls async_forward_entry_setup for integration "
|
||||||
|
"test with title: Mock Title and entry_id: test2, during setup without "
|
||||||
|
"awaiting async_forward_entry_setup, which can cause the setup lock "
|
||||||
|
"to be released before the setup is done. This will stop working in "
|
||||||
|
"Home Assistant 2025.1. Please report this issue."
|
||||||
|
) in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_entry_unloaded_during_platform_setup(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
manager: config_entries.ConfigEntries,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test async_forward_entry_setup not being awaited."""
|
||||||
|
task = None
|
||||||
|
|
||||||
|
async def mock_setup_entry(
|
||||||
|
hass: HomeAssistant, entry: config_entries.ConfigEntry
|
||||||
|
) -> bool:
|
||||||
|
"""Mock setting up entry."""
|
||||||
|
|
||||||
|
# Call async_forward_entry_setup in a non-tracked task
|
||||||
|
# so we can unload the config entry during the setup
|
||||||
|
def _late_setup():
|
||||||
|
nonlocal task
|
||||||
|
task = asyncio.create_task(
|
||||||
|
hass.config_entries.async_forward_entry_setup(entry, "light")
|
||||||
|
)
|
||||||
|
|
||||||
|
hass.loop.call_soon(_late_setup)
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def mock_unload_entry(
|
||||||
|
hass: HomeAssistant, entry: config_entries.ConfigEntry
|
||||||
|
) -> bool:
|
||||||
|
"""Mock unloading an entry."""
|
||||||
|
result = await hass.config_entries.async_unload_platforms(entry, ["light"])
|
||||||
|
assert result
|
||||||
|
return result
|
||||||
|
|
||||||
|
mock_remove_entry = AsyncMock(return_value=None)
|
||||||
|
|
||||||
|
async def mock_setup_entry_platform(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry: config_entries.ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Mock setting up platform."""
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
|
||||||
|
mock_integration(
|
||||||
|
hass,
|
||||||
|
MockModule(
|
||||||
|
"test",
|
||||||
|
async_setup_entry=mock_setup_entry,
|
||||||
|
async_unload_entry=mock_unload_entry,
|
||||||
|
async_remove_entry=mock_remove_entry,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
mock_platform(
|
||||||
|
hass, "test.light", MockPlatform(async_setup_entry=mock_setup_entry_platform)
|
||||||
|
)
|
||||||
|
mock_platform(hass, "test.config_flow", None)
|
||||||
|
|
||||||
|
entry = MockConfigEntry(domain="test", entry_id="test2")
|
||||||
|
entry.add_to_manager(manager)
|
||||||
|
|
||||||
|
# Setup entry
|
||||||
|
await manager.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
await manager.async_unload(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
del task
|
||||||
|
|
||||||
|
assert (
|
||||||
|
"OperationNotAllowed: The config entry Mock Title (test) with "
|
||||||
|
"entry_id test2 cannot forward setup for light because it is "
|
||||||
|
"not loaded in the ConfigEntryState.NOT_LOADED state"
|
||||||
|
) in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_entry_late_platform_setup(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
manager: config_entries.ConfigEntries,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test async_forward_entry_setup not being awaited."""
|
||||||
|
task = None
|
||||||
|
|
||||||
|
async def mock_setup_entry(
|
||||||
|
hass: HomeAssistant, entry: config_entries.ConfigEntry
|
||||||
|
) -> bool:
|
||||||
|
"""Mock setting up entry."""
|
||||||
|
|
||||||
|
# Call async_forward_entry_setup in a non-tracked task
|
||||||
|
# so we can unload the config entry during the setup
|
||||||
|
def _late_setup():
|
||||||
|
nonlocal task
|
||||||
|
task = asyncio.create_task(
|
||||||
|
hass.config_entries.async_forward_entry_setup(entry, "light")
|
||||||
|
)
|
||||||
|
|
||||||
|
hass.loop.call_soon(_late_setup)
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def mock_unload_entry(
|
||||||
|
hass: HomeAssistant, entry: config_entries.ConfigEntry
|
||||||
|
) -> bool:
|
||||||
|
"""Mock unloading an entry."""
|
||||||
|
result = await hass.config_entries.async_unload_platforms(entry, ["light"])
|
||||||
|
assert result
|
||||||
|
return result
|
||||||
|
|
||||||
|
mock_remove_entry = AsyncMock(return_value=None)
|
||||||
|
|
||||||
|
async def mock_setup_entry_platform(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry: config_entries.ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Mock setting up platform."""
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
|
||||||
|
mock_integration(
|
||||||
|
hass,
|
||||||
|
MockModule(
|
||||||
|
"test",
|
||||||
|
async_setup_entry=mock_setup_entry,
|
||||||
|
async_unload_entry=mock_unload_entry,
|
||||||
|
async_remove_entry=mock_remove_entry,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
mock_platform(
|
||||||
|
hass, "test.light", MockPlatform(async_setup_entry=mock_setup_entry_platform)
|
||||||
|
)
|
||||||
|
mock_platform(hass, "test.config_flow", None)
|
||||||
|
|
||||||
|
entry = MockConfigEntry(domain="test", entry_id="test2")
|
||||||
|
entry.add_to_manager(manager)
|
||||||
|
|
||||||
|
# Setup entry
|
||||||
|
await manager.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
await task
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert (
|
||||||
|
"OperationNotAllowed: The config entry Mock Title (test) with "
|
||||||
|
"entry_id test2 cannot forward setup for light because it is "
|
||||||
|
"not loaded in the ConfigEntryState.NOT_LOADED state"
|
||||||
|
) not in caplog.text
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue