Improve after_dependencies handling (#36898)

This commit is contained in:
Paulus Schoutsen 2020-06-19 17:24:33 -07:00 committed by GitHub
parent 93272e3083
commit 5642027ffb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 377 additions and 166 deletions

View File

@ -1,6 +1,7 @@
"""Provide methods to bootstrap a Home Assistant instance."""
import asyncio
import contextlib
from datetime import datetime
import logging
import logging.handlers
import os
@ -20,7 +21,12 @@ from homeassistant.const import (
)
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.typing import ConfigType
from homeassistant.setup import DATA_SETUP, DATA_SETUP_STARTED, async_setup_component
from homeassistant.setup import (
DATA_SETUP,
DATA_SETUP_STARTED,
async_set_domains_to_be_loaded,
async_setup_component,
)
from homeassistant.util.logging import async_activate_log_queue_handler
from homeassistant.util.package import async_get_user_site, is_virtual_env
from homeassistant.util.yaml import clear_secret_cache
@ -36,10 +42,16 @@ LOG_SLOW_STARTUP_INTERVAL = 60
DEBUGGER_INTEGRATIONS = {"ptvsd"}
CORE_INTEGRATIONS = ("homeassistant", "persistent_notification")
LOGGING_INTEGRATIONS = {"logger", "system_log", "sentry"}
STAGE_1_INTEGRATIONS = {
LOGGING_INTEGRATIONS = {
# Set log levels
"logger",
# Error logging
"system_log",
"sentry",
# To record data
"recorder",
}
STAGE_1_INTEGRATIONS = {
# To make sure we forward data to other instances
"mqtt_eventstream",
# To provide account link implementations
@ -330,77 +342,130 @@ def _get_domains(hass: core.HomeAssistant, config: Dict[str, Any]) -> Set[str]:
return domains
async def _async_log_pending_setups(
domains: Set[str], setup_started: Dict[str, datetime]
) -> None:
"""Periodic log of setups that are pending for longer than LOG_SLOW_STARTUP_INTERVAL."""
while True:
await asyncio.sleep(LOG_SLOW_STARTUP_INTERVAL)
remaining = [domain for domain in domains if domain in setup_started]
if remaining:
_LOGGER.info(
"Waiting on integrations to complete setup: %s", ", ".join(remaining),
)
async def async_setup_multi_components(
hass: core.HomeAssistant,
domains: Set[str],
config: Dict[str, Any],
setup_started: Dict[str, datetime],
) -> None:
"""Set up multiple domains. Log on failure."""
futures = {
domain: hass.async_create_task(async_setup_component(hass, domain, config))
for domain in domains
}
log_task = asyncio.create_task(_async_log_pending_setups(domains, setup_started))
await asyncio.wait(futures.values())
log_task.cancel()
errors = [domain for domain in domains if futures[domain].exception()]
for domain in errors:
exception = futures[domain].exception()
assert exception is not None
_LOGGER.error(
"Error setting up integration %s - received exception",
domain,
exc_info=(type(exception), exception, exception.__traceback__),
)
async def _async_set_up_integrations(
hass: core.HomeAssistant, config: Dict[str, Any]
) -> None:
"""Set up all the integrations."""
setup_started = hass.data[DATA_SETUP_STARTED] = {}
domains_to_setup = _get_domains(hass, config)
async def async_setup_multi_components(domains: Set[str]) -> None:
"""Set up multiple domains. Log on failure."""
# Resolve all dependencies so we know all integrations
# that will have to be loaded and start rightaway
integration_cache: Dict[str, loader.Integration] = {}
to_resolve = domains_to_setup
while to_resolve:
old_to_resolve = to_resolve
to_resolve = set()
async def _async_log_pending_setups() -> None:
"""Periodic log of setups that are pending for longer than LOG_SLOW_STARTUP_INTERVAL."""
while True:
await asyncio.sleep(LOG_SLOW_STARTUP_INTERVAL)
remaining = [domain for domain in domains if domain in setup_started]
if remaining:
_LOGGER.info(
"Waiting on integrations to complete setup: %s",
", ".join(remaining),
)
futures = {
domain: hass.async_create_task(async_setup_component(hass, domain, config))
for domain in domains
}
log_task = asyncio.create_task(_async_log_pending_setups())
await asyncio.wait(futures.values())
log_task.cancel()
errors = [domain for domain in domains if futures[domain].exception()]
for domain in errors:
exception = futures[domain].exception()
assert exception is not None
_LOGGER.error(
"Error setting up integration %s - received exception",
domain,
exc_info=(type(exception), exception, exception.__traceback__),
integrations_to_process = [
int_or_exc
for int_or_exc in await asyncio.gather(
*(
loader.async_get_integration(hass, domain)
for domain in old_to_resolve
),
return_exceptions=True,
)
if isinstance(int_or_exc, loader.Integration)
]
resolve_dependencies_tasks = [
itg.resolve_dependencies()
for itg in integrations_to_process
if not itg.all_dependencies_resolved
]
domains = _get_domains(hass, config)
if resolve_dependencies_tasks:
await asyncio.gather(*resolve_dependencies_tasks)
for itg in integrations_to_process:
integration_cache[itg.domain] = itg
for dep in itg.all_dependencies:
if dep in domains_to_setup:
continue
domains_to_setup.add(dep)
to_resolve.add(dep)
_LOGGER.info("Domains to be set up: %s", domains_to_setup)
logging_domains = domains_to_setup & LOGGING_INTEGRATIONS
# Load logging as soon as possible
if logging_domains:
_LOGGER.info("Setting up logging: %s", logging_domains)
await async_setup_multi_components(hass, logging_domains, config, setup_started)
# Start up debuggers. Start these first in case they want to wait.
debuggers = domains & DEBUGGER_INTEGRATIONS
debuggers = domains_to_setup & DEBUGGER_INTEGRATIONS
if debuggers:
_LOGGER.debug("Starting up debuggers %s", debuggers)
await async_setup_multi_components(debuggers)
domains -= DEBUGGER_INTEGRATIONS
_LOGGER.debug("Setting up debuggers: %s", debuggers)
await async_setup_multi_components(hass, debuggers, config, setup_started)
# Resolve all dependencies of all components so we can find the logging
# and integrations that need faster initialization.
resolved_domains_task = asyncio.gather(
*(loader.async_component_dependencies(hass, domain) for domain in domains),
return_exceptions=True,
)
# calculate what components to setup in what stage
stage_1_domains = set()
# Finish resolving domains
for dep_domains in await resolved_domains_task:
# Result is either a set or an exception. We ignore exceptions
# It will be properly handled during setup of the domain.
if isinstance(dep_domains, set):
domains.update(dep_domains)
# Find all dependencies of any dependency of any stage 1 integration that
# we plan on loading and promote them to stage 1
deps_promotion = STAGE_1_INTEGRATIONS
while deps_promotion:
old_deps_promotion = deps_promotion
deps_promotion = set()
# setup components
logging_domains = domains & LOGGING_INTEGRATIONS
stage_1_domains = domains & STAGE_1_INTEGRATIONS
stage_2_domains = domains - logging_domains - stage_1_domains
for domain in old_deps_promotion:
if domain not in domains_to_setup or domain in stage_1_domains:
continue
if logging_domains:
_LOGGER.info("Setting up %s", logging_domains)
stage_1_domains.add(domain)
await async_setup_multi_components(logging_domains)
dep_itg = integration_cache.get(domain)
if dep_itg is None:
continue
deps_promotion.update(dep_itg.all_dependencies)
stage_2_domains = domains_to_setup - logging_domains - debuggers - stage_1_domains
# Kick off loading the registries. They don't need to be awaited.
asyncio.gather(
@ -409,49 +474,17 @@ async def _async_set_up_integrations(
hass.helpers.area_registry.async_get_registry(),
)
# Start setup
if stage_1_domains:
_LOGGER.info("Setting up %s", stage_1_domains)
_LOGGER.info("Setting up stage 1: %s", stage_1_domains)
await async_setup_multi_components(hass, stage_1_domains, config, setup_started)
await async_setup_multi_components(stage_1_domains)
# Enables after dependencies
async_set_domains_to_be_loaded(hass, stage_1_domains | stage_2_domains)
# Load all integrations
after_dependencies: Dict[str, Set[str]] = {}
for int_or_exc in await asyncio.gather(
*(loader.async_get_integration(hass, domain) for domain in stage_2_domains),
return_exceptions=True,
):
# Exceptions are handled in async_setup_component.
if isinstance(int_or_exc, loader.Integration) and int_or_exc.after_dependencies:
after_dependencies[int_or_exc.domain] = set(int_or_exc.after_dependencies)
last_load = None
while stage_2_domains:
domains_to_load = set()
for domain in stage_2_domains:
after_deps = after_dependencies.get(domain)
# Load if integration has no after_dependencies or they are
# all loaded
if not after_deps or not after_deps - hass.config.components:
domains_to_load.add(domain)
if not domains_to_load or domains_to_load == last_load:
break
_LOGGER.debug("Setting up %s", domains_to_load)
await async_setup_multi_components(domains_to_load)
last_load = domains_to_load
stage_2_domains -= domains_to_load
# These are stage 2 domains that never have their after_dependencies
# satisfied.
if stage_2_domains:
_LOGGER.debug("Final set up: %s", stage_2_domains)
await async_setup_multi_components(stage_2_domains)
_LOGGER.info("Setting up stage 2: %s", stage_2_domains)
await async_setup_multi_components(hass, stage_2_domains, config, setup_started)
# Wrap up startup
_LOGGER.debug("Waiting for startup to wrap up")

View File

@ -3,7 +3,6 @@
"name": "Auth",
"documentation": "https://www.home-assistant.io/integrations/auth",
"dependencies": ["http"],
"after_dependencies": ["onboarding"],
"codeowners": ["@home-assistant/core"],
"quality_scale": "internal"
}

View File

@ -203,6 +203,14 @@ class Integration:
self.file_path = file_path
self.manifest = manifest
manifest["is_built_in"] = self.is_built_in
if self.dependencies:
self._all_dependencies_resolved: Optional[bool] = None
self._all_dependencies: Optional[Set[str]] = None
else:
self._all_dependencies_resolved = True
self._all_dependencies = set()
_LOGGER.info("Loaded %s from %s", self.domain, pkg_path)
@property
@ -255,6 +263,49 @@ class Integration:
"""Test if package is a built-in integration."""
return self.pkg_path.startswith(PACKAGE_BUILTIN)
@property
def all_dependencies(self) -> Set[str]:
"""Return all dependencies including sub-dependencies."""
if self._all_dependencies is None:
raise RuntimeError("Dependencies not resolved!")
return self._all_dependencies
@property
def all_dependencies_resolved(self) -> bool:
"""Return if all dependencies have been resolved."""
return self._all_dependencies_resolved is not None
async def resolve_dependencies(self) -> bool:
"""Resolve all dependencies."""
if self._all_dependencies_resolved is not None:
return self._all_dependencies_resolved
try:
dependencies = await _async_component_dependencies(
self.hass, self.domain, self, set(), set()
)
dependencies.discard(self.domain)
self._all_dependencies = dependencies
self._all_dependencies_resolved = True
except IntegrationNotFound as err:
_LOGGER.error(
"Unable to resolve dependencies for %s: we are unable to resolve (sub)dependency %s",
self.domain,
err.domain,
)
self._all_dependencies_resolved = False
except CircularDependency as err:
_LOGGER.error(
"Unable to resolve dependencies for %s: it contains a circular dependency: %s -> %s",
self.domain,
err.from_domain,
err.to_domain,
)
self._all_dependencies_resolved = False
return self._all_dependencies_resolved
def get_component(self) -> ModuleType:
"""Return the component."""
cache = self.hass.data.setdefault(DATA_COMPONENTS, {})
@ -488,23 +539,18 @@ def bind_hass(func: CALLABLE_T) -> CALLABLE_T:
return func
async def async_component_dependencies(hass: "HomeAssistant", domain: str) -> Set[str]:
"""Return all dependencies and subdependencies of components.
Raises CircularDependency if a circular dependency is found.
"""
return await _async_component_dependencies(hass, domain, set(), set())
async def _async_component_dependencies(
hass: "HomeAssistant", domain: str, loaded: Set[str], loading: Set[str]
hass: "HomeAssistant",
start_domain: str,
integration: Integration,
loaded: Set[str],
loading: Set[str],
) -> Set[str]:
"""Recursive function to get component dependencies.
Async friendly.
"""
integration = await async_get_integration(hass, domain)
domain = integration.domain
loading.add(domain)
for dependency_domain in integration.dependencies:
@ -516,11 +562,19 @@ async def _async_component_dependencies(
if dependency_domain in loading:
raise CircularDependency(domain, dependency_domain)
dep_loaded = await _async_component_dependencies(
hass, dependency_domain, loaded, loading
)
loaded.add(dependency_domain)
loaded.update(dep_loaded)
dep_integration = await async_get_integration(hass, dependency_domain)
if start_domain in dep_integration.after_dependencies:
raise CircularDependency(start_domain, dependency_domain)
if dep_integration.dependencies:
dep_loaded = await _async_component_dependencies(
hass, start_domain, dep_integration, loaded, loading
)
loaded.update(dep_loaded)
loaded.add(domain)
loading.remove(domain)

View File

@ -3,7 +3,7 @@ import asyncio
import logging.handlers
from timeit import default_timer as timer
from types import ModuleType
from typing import Awaitable, Callable, List, Optional
from typing import Awaitable, Callable, Optional, Set
from homeassistant import config as conf_util, core, loader, requirements
from homeassistant.config import async_notify_setup_error
@ -16,6 +16,7 @@ _LOGGER = logging.getLogger(__name__)
ATTR_COMPONENT = "component"
DATA_SETUP_DONE = "setup_done"
DATA_SETUP_STARTED = "setup_started"
DATA_SETUP = "setup_tasks"
DATA_DEPS_REQS = "deps_reqs_processed"
@ -26,6 +27,15 @@ SLOW_SETUP_WARNING = 10
SLOW_SETUP_MAX_WAIT = 1800
@core.callback
def async_set_domains_to_be_loaded(hass: core.HomeAssistant, domains: Set[str]) -> None:
"""Set domains that are going to be loaded from the config.
This will allow us to properly handle after_dependencies.
"""
hass.data[DATA_SETUP_DONE] = {domain: asyncio.Event() for domain in domains}
def setup_component(hass: core.HomeAssistant, domain: str, config: ConfigType) -> bool:
"""Set up a component and all its dependencies."""
return asyncio.run_coroutine_threadsafe(
@ -52,26 +62,43 @@ async def async_setup_component(
_async_setup_component(hass, domain, config)
)
return await task # type: ignore
try:
return await task # type: ignore
finally:
if domain in hass.data.get(DATA_SETUP_DONE, {}):
hass.data[DATA_SETUP_DONE].pop(domain).set()
async def _async_process_dependencies(
hass: core.HomeAssistant, config: ConfigType, name: str, dependencies: List[str]
hass: core.HomeAssistant, config: ConfigType, integration: loader.Integration
) -> bool:
"""Ensure all dependencies are set up."""
tasks = [async_setup_component(hass, dep, config) for dep in dependencies]
tasks = {
dep: hass.loop.create_task(async_setup_component(hass, dep, config))
for dep in integration.dependencies
}
to_be_loaded = hass.data.get(DATA_SETUP_DONE, {})
for dep in integration.after_dependencies:
if dep in to_be_loaded and dep not in hass.config.components:
tasks[dep] = hass.loop.create_task(to_be_loaded[dep].wait())
if not tasks:
return True
results = await asyncio.gather(*tasks)
_LOGGER.debug("Dependency %s will wait for %s", integration.domain, list(tasks))
results = await asyncio.gather(*tasks.values())
failed = [dependencies[idx] for idx, res in enumerate(results) if not res]
failed = [
domain
for idx, domain in enumerate(integration.dependencies)
if not results[idx]
]
if failed:
_LOGGER.error(
"Unable to set up dependencies of %s. Setup failed for dependencies: %s",
name,
integration.domain,
", ".join(failed),
)
@ -99,22 +126,7 @@ async def _async_setup_component(
return False
# Validate all dependencies exist and there are no circular dependencies
try:
await loader.async_component_dependencies(hass, domain)
except loader.IntegrationNotFound as err:
_LOGGER.error(
"Not setting up %s because we are unable to resolve (sub)dependency %s",
domain,
err.domain,
)
return False
except loader.CircularDependency as err:
_LOGGER.error(
"Not setting up %s because it contains a circular dependency: %s -> %s",
domain,
err.from_domain,
err.to_domain,
)
if not await integration.resolve_dependencies():
return False
# Process requirements as soon as possible, so we can import the component
@ -301,9 +313,7 @@ async def async_process_deps_reqs(
elif integration.domain in processed:
return
if integration.dependencies and not await _async_process_dependencies(
hass, config, integration.domain, integration.dependencies
):
if not await _async_process_dependencies(hass, config, integration):
raise HomeAssistantError("Could not set up all dependencies.")
if not hass.config.skip_pip and integration.requirements:

View File

@ -103,6 +103,7 @@ ALLOWED_USED_COMPONENTS = {
"input_number",
"input_select",
"input_text",
"onboarding",
"persistent_notification",
"person",
"script",
@ -253,7 +254,14 @@ def validate(integrations: Dict[str, Integration], config):
continue
# check that all referenced dependencies exist
after_deps = integration.manifest.get("after_dependencies", [])
for dep in integration.manifest.get("dependencies", []):
if dep in after_deps:
integration.add_error(
"dependencies",
f"Dependency {dep} is both in dependencies and after_dependencies",
)
if dep not in integrations:
integration.add_error(
"dependencies", f"Dependency {dep} does not exist"

View File

@ -991,6 +991,8 @@ def mock_integration(hass, module):
hass.data.setdefault(loader.DATA_INTEGRATIONS, {})[module.DOMAIN] = integration
hass.data.setdefault(loader.DATA_COMPONENTS, {})[module.DOMAIN] = module
return integration
def mock_entity_platform(hass, platform_path, module):
"""Mock a entity platform.

View File

@ -7,7 +7,7 @@ from unittest.mock import Mock
import pytest
from homeassistant import bootstrap
from homeassistant import bootstrap, core
import homeassistant.config as config_util
from homeassistant.exceptions import HomeAssistantError
import homeassistant.util.dt as dt_util
@ -16,9 +16,11 @@ from tests.async_mock import patch
from tests.common import (
MockConfigEntry,
MockModule,
MockPlatform,
flush_store,
get_test_config_dir,
mock_coro,
mock_entity_platform,
mock_integration,
)
@ -81,7 +83,7 @@ async def test_core_failure_loads_safe_mode(hass, caplog):
assert "group" not in hass.config.components
async def test_setting_up_config(hass, caplog):
async def test_setting_up_config(hass):
"""Test we set up domains in config."""
await bootstrap._async_set_up_integrations(
hass, {"group hello": {}, "homeassistant": {}}
@ -90,9 +92,8 @@ async def test_setting_up_config(hass, caplog):
assert "group" in hass.config.components
async def test_setup_after_deps_all_present(hass, caplog):
async def test_setup_after_deps_all_present(hass):
"""Test after_dependencies when all present."""
caplog.set_level(logging.DEBUG)
order = []
def gen_domain_setup(domain):
@ -122,19 +123,115 @@ async def test_setup_after_deps_all_present(hass, caplog):
),
)
await bootstrap._async_set_up_integrations(
hass, {"root": {}, "first_dep": {}, "second_dep": {}}
)
with patch(
"homeassistant.components.logger.async_setup", gen_domain_setup("logger")
):
await bootstrap._async_set_up_integrations(
hass, {"root": {}, "first_dep": {}, "second_dep": {}, "logger": {}}
)
assert "root" in hass.config.components
assert "first_dep" in hass.config.components
assert "second_dep" in hass.config.components
assert order == ["root", "first_dep", "second_dep"]
assert order == ["logger", "root", "first_dep", "second_dep"]
async def test_setup_after_deps_not_trigger_load(hass, caplog):
async def test_setup_after_deps_in_stage_1_ignored(hass):
"""Test after_dependencies are ignored in stage 1."""
# This test relies on this
assert "cloud" in bootstrap.STAGE_1_INTEGRATIONS
order = []
def gen_domain_setup(domain):
async def async_setup(hass, config):
order.append(domain)
return True
return async_setup
mock_integration(
hass,
MockModule(
domain="normal_integration",
async_setup=gen_domain_setup("normal_integration"),
partial_manifest={"after_dependencies": ["an_after_dep"]},
),
)
mock_integration(
hass,
MockModule(
domain="an_after_dep", async_setup=gen_domain_setup("an_after_dep"),
),
)
mock_integration(
hass,
MockModule(
domain="cloud",
async_setup=gen_domain_setup("cloud"),
partial_manifest={"after_dependencies": ["normal_integration"]},
),
)
await bootstrap._async_set_up_integrations(
hass, {"cloud": {}, "normal_integration": {}, "an_after_dep": {}}
)
assert "normal_integration" in hass.config.components
assert "cloud" in hass.config.components
assert order == ["cloud", "an_after_dep", "normal_integration"]
async def test_setup_after_deps_via_platform(hass):
"""Test after_dependencies set up via platform."""
order = []
after_dep_event = asyncio.Event()
def gen_domain_setup(domain):
async def async_setup(hass, config):
if domain == "after_dep_of_platform_int":
await after_dep_event.wait()
order.append(domain)
return True
return async_setup
mock_integration(
hass,
MockModule(
domain="after_dep_of_platform_int",
async_setup=gen_domain_setup("after_dep_of_platform_int"),
),
)
mock_integration(
hass,
MockModule(
domain="platform_int",
async_setup=gen_domain_setup("platform_int"),
partial_manifest={"after_dependencies": ["after_dep_of_platform_int"]},
),
)
mock_entity_platform(hass, "light.platform_int", MockPlatform())
@core.callback
def continue_loading(_):
"""When light component loaded, continue other loading."""
after_dep_event.set()
hass.bus.async_listen_once("component_loaded", continue_loading)
await bootstrap._async_set_up_integrations(
hass, {"light": {"platform": "platform_int"}, "after_dep_of_platform_int": {}}
)
assert "light" in hass.config.components
assert "after_dep_of_platform_int" in hass.config.components
assert "platform_int" in hass.config.components
assert order == ["after_dep_of_platform_int", "platform_int"]
async def test_setup_after_deps_not_trigger_load(hass):
"""Test after_dependencies does not trigger loading it."""
caplog.set_level(logging.DEBUG)
order = []
def gen_domain_setup(domain):
@ -169,12 +266,10 @@ async def test_setup_after_deps_not_trigger_load(hass, caplog):
assert "root" in hass.config.components
assert "first_dep" not in hass.config.components
assert "second_dep" in hass.config.components
assert order == ["root", "second_dep"]
async def test_setup_after_deps_not_present(hass, caplog):
async def test_setup_after_deps_not_present(hass):
"""Test after_dependencies when referenced integration doesn't exist."""
caplog.set_level(logging.DEBUG)
order = []
def gen_domain_setup(domain):

View File

@ -13,27 +13,43 @@ async def test_component_dependencies(hass):
"""Test if we can get the proper load order of components."""
mock_integration(hass, MockModule("mod1"))
mock_integration(hass, MockModule("mod2", ["mod1"]))
mock_integration(hass, MockModule("mod3", ["mod2"]))
mod_3 = mock_integration(hass, MockModule("mod3", ["mod2"]))
assert {"mod1", "mod2", "mod3"} == await loader.async_component_dependencies(
hass, "mod3"
assert {"mod1", "mod2", "mod3"} == await loader._async_component_dependencies(
hass, "mod_3", mod_3, set(), set()
)
# Create circular dependency
mock_integration(hass, MockModule("mod1", ["mod3"]))
with pytest.raises(loader.CircularDependency):
print(await loader.async_component_dependencies(hass, "mod3"))
print(
await loader._async_component_dependencies(
hass, "mod_3", mod_3, set(), set()
)
)
# Depend on non-existing component
mock_integration(hass, MockModule("mod1", ["nonexisting"]))
mod_1 = mock_integration(hass, MockModule("mod1", ["nonexisting"]))
with pytest.raises(loader.IntegrationNotFound):
print(await loader.async_component_dependencies(hass, "mod1"))
print(
await loader._async_component_dependencies(
hass, "mod_1", mod_1, set(), set()
)
)
# Try to get dependencies for non-existing component
with pytest.raises(loader.IntegrationNotFound):
print(await loader.async_component_dependencies(hass, "nonexisting"))
# Having an after dependency 2 deps down that is circular
mod_1 = mock_integration(
hass, MockModule("mod1", partial_manifest={"after_dependencies": ["mod_3"]})
)
with pytest.raises(loader.CircularDependency):
print(
await loader._async_component_dependencies(
hass, "mod_3", mod_3, set(), set()
)
)
def test_component_loader(hass):

View File

@ -480,12 +480,6 @@ class TestSetup:
assert call_order == [1, 1, 2]
async def test_component_cannot_depend_config(hass):
"""Test config is not allowed to be a dependency."""
result = await setup._async_process_dependencies(hass, None, "test", ["config"])
assert not result
async def test_component_warn_slow_setup(hass):
"""Warn we log when a component setup takes a long time."""
mock_integration(hass, MockModule("test_component1"))